blob: 682c94544c817e8623a1bdb6a87f31ee7f39ea0d [file] [log] [blame]
epoger@google.comec3ed6a2011-07-28 14:26:00 +00001/*
2 * Copyright 2006 The Android Open Source Project
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
djsollen@google.com94e75ee2012-06-08 18:30:46 +00008#include "SkBuffer.h"
humper@google.com75e3ca12013-04-08 21:44:11 +00009#include "SkErrorInternals.h"
reed220f9262014-12-17 08:21:04 -080010#include "SkGeometry.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000011#include "SkMath.h"
humper@google.com75e3ca12013-04-08 21:44:11 +000012#include "SkPath.h"
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +000013#include "SkPathRef.h"
reed@google.com4ed0fb72012-12-12 20:48:18 +000014#include "SkRRect.h"
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +000015#include "SkThread.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000016
17////////////////////////////////////////////////////////////////////////////
18
reed@google.com3563c9e2011-11-14 19:34:57 +000019/**
20 * Path.bounds is defined to be the bounds of all the control points.
21 * If we called bounds.join(r) we would skip r if r was empty, which breaks
22 * our promise. Hence we have a custom joiner that doesn't look at emptiness
23 */
24static void joinNoEmptyChecks(SkRect* dst, const SkRect& src) {
25 dst->fLeft = SkMinScalar(dst->fLeft, src.fLeft);
26 dst->fTop = SkMinScalar(dst->fTop, src.fTop);
27 dst->fRight = SkMaxScalar(dst->fRight, src.fRight);
28 dst->fBottom = SkMaxScalar(dst->fBottom, src.fBottom);
29}
30
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000031static bool is_degenerate(const SkPath& path) {
32 SkPath::Iter iter(path, false);
33 SkPoint pts[4];
34 return SkPath::kDone_Verb == iter.next(pts);
35}
36
bsalomon@google.com30c174b2012-11-13 14:36:42 +000037class SkAutoDisableDirectionCheck {
38public:
39 SkAutoDisableDirectionCheck(SkPath* path) : fPath(path) {
40 fSaved = static_cast<SkPath::Direction>(fPath->fDirection);
41 }
42
43 ~SkAutoDisableDirectionCheck() {
44 fPath->fDirection = fSaved;
45 }
46
47private:
48 SkPath* fPath;
49 SkPath::Direction fSaved;
50};
commit-bot@chromium.orge61a86c2013-11-18 16:03:59 +000051#define SkAutoDisableDirectionCheck(...) SK_REQUIRE_LOCAL_VAR(SkAutoDisableDirectionCheck)
bsalomon@google.com30c174b2012-11-13 14:36:42 +000052
reed@android.com8a1c16f2008-12-17 15:59:43 +000053/* This guy's constructor/destructor bracket a path editing operation. It is
54 used when we know the bounds of the amount we are going to add to the path
55 (usually a new contour, but not required).
reed@google.comabf15c12011-01-18 20:35:51 +000056
reed@android.com8a1c16f2008-12-17 15:59:43 +000057 It captures some state about the path up front (i.e. if it already has a
robertphillips@google.comca0c8382013-09-26 12:18:23 +000058 cached bounds), and then if it can, it updates the cache bounds explicitly,
reed@android.comd252db02009-04-01 18:31:44 +000059 avoiding the need to revisit all of the points in getBounds().
reed@google.comabf15c12011-01-18 20:35:51 +000060
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000061 It also notes if the path was originally degenerate, and if so, sets
62 isConvex to true. Thus it can only be used if the contour being added is
robertphillips@google.com466310d2013-12-03 16:43:54 +000063 convex.
reed@android.com8a1c16f2008-12-17 15:59:43 +000064 */
65class SkAutoPathBoundsUpdate {
66public:
67 SkAutoPathBoundsUpdate(SkPath* path, const SkRect& r) : fRect(r) {
68 this->init(path);
69 }
70
71 SkAutoPathBoundsUpdate(SkPath* path, SkScalar left, SkScalar top,
72 SkScalar right, SkScalar bottom) {
73 fRect.set(left, top, right, bottom);
74 this->init(path);
75 }
reed@google.comabf15c12011-01-18 20:35:51 +000076
reed@android.com8a1c16f2008-12-17 15:59:43 +000077 ~SkAutoPathBoundsUpdate() {
reed@google.com44699382013-10-31 17:28:30 +000078 fPath->setConvexity(fDegenerate ? SkPath::kConvex_Convexity
79 : SkPath::kUnknown_Convexity);
robertphillips@google.comca0c8382013-09-26 12:18:23 +000080 if (fEmpty || fHasValidBounds) {
81 fPath->setBounds(fRect);
reed@android.com8a1c16f2008-12-17 15:59:43 +000082 }
83 }
reed@google.comabf15c12011-01-18 20:35:51 +000084
reed@android.com8a1c16f2008-12-17 15:59:43 +000085private:
reed@android.com6b82d1a2009-06-03 02:35:01 +000086 SkPath* fPath;
87 SkRect fRect;
robertphillips@google.comca0c8382013-09-26 12:18:23 +000088 bool fHasValidBounds;
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000089 bool fDegenerate;
reed@android.com6b82d1a2009-06-03 02:35:01 +000090 bool fEmpty;
reed@google.comabf15c12011-01-18 20:35:51 +000091
reed@android.com6b82d1a2009-06-03 02:35:01 +000092 void init(SkPath* path) {
robertphillips@google.comca0c8382013-09-26 12:18:23 +000093 // Cannot use fRect for our bounds unless we know it is sorted
94 fRect.sort();
reed@android.com8a1c16f2008-12-17 15:59:43 +000095 fPath = path;
reed@google.coma8790de2012-10-24 21:04:04 +000096 // Mark the path's bounds as dirty if (1) they are, or (2) the path
97 // is non-finite, and therefore its bounds are not meaningful
robertphillips@google.comca0c8382013-09-26 12:18:23 +000098 fHasValidBounds = path->hasComputedBounds() && path->isFinite();
reed@android.com8a1c16f2008-12-17 15:59:43 +000099 fEmpty = path->isEmpty();
robertphillips@google.comca0c8382013-09-26 12:18:23 +0000100 if (fHasValidBounds && !fEmpty) {
101 joinNoEmptyChecks(&fRect, fPath->getBounds());
102 }
103 fDegenerate = is_degenerate(*path);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000104 }
105};
commit-bot@chromium.orge61a86c2013-11-18 16:03:59 +0000106#define SkAutoPathBoundsUpdate(...) SK_REQUIRE_LOCAL_VAR(SkAutoPathBoundsUpdate)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000107
reed@android.com8a1c16f2008-12-17 15:59:43 +0000108////////////////////////////////////////////////////////////////////////////
109
110/*
111 Stores the verbs and points as they are given to us, with exceptions:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000112 - we only record "Close" if it was immediately preceeded by Move | Line | Quad | Cubic
reed@android.com8a1c16f2008-12-17 15:59:43 +0000113 - we insert a Move(0,0) if Line | Quad | Cubic is our first command
114
115 The iterator does more cleanup, especially if forceClose == true
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000116 1. If we encounter degenerate segments, remove them
117 2. if we encounter Close, return a cons'd up Line() first (if the curr-pt != start-pt)
118 3. if we encounter Move without a preceeding Close, and forceClose is true, goto #2
119 4. if we encounter Line | Quad | Cubic after Close, cons up a Move
reed@android.com8a1c16f2008-12-17 15:59:43 +0000120*/
121
122////////////////////////////////////////////////////////////////////////////
123
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000124// flag to require a moveTo if we begin with something else, like lineTo etc.
125#define INITIAL_LASTMOVETOINDEX_VALUE ~0
126
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000127SkPath::SkPath()
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000128 : fPathRef(SkPathRef::CreateEmpty())
bungeman@google.coma5809a32013-06-21 15:13:34 +0000129#ifdef SK_BUILD_FOR_ANDROID
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000130 , fSourcePath(NULL)
bungeman@google.coma5809a32013-06-21 15:13:34 +0000131#endif
132{
133 this->resetFields();
jvanverthb3eb6872014-10-24 07:12:51 -0700134 fIsVolatile = false;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000135}
136
137void SkPath::resetFields() {
138 //fPathRef is assumed to have been emptied by the caller.
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000139 fLastMoveToIndex = INITIAL_LASTMOVETOINDEX_VALUE;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000140 fFillType = kWinding_FillType;
reed@google.com04863fa2011-05-15 04:08:24 +0000141 fConvexity = kUnknown_Convexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000142 fDirection = kUnknown_Direction;
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000143
144 // We don't touch Android's fSourcePath. It's used to track texture garbage collection, so we
145 // don't want to muck with it if it's been set to something non-NULL.
reed@android.com6b82d1a2009-06-03 02:35:01 +0000146}
reed@android.com8a1c16f2008-12-17 15:59:43 +0000147
bungeman@google.coma5809a32013-06-21 15:13:34 +0000148SkPath::SkPath(const SkPath& that)
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000149 : fPathRef(SkRef(that.fPathRef.get())) {
bungeman@google.coma5809a32013-06-21 15:13:34 +0000150 this->copyFields(that);
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000151#ifdef SK_BUILD_FOR_ANDROID
mtklein@google.comcb8b0ee2013-08-15 21:14:51 +0000152 fSourcePath = that.fSourcePath;
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000153#endif
bungeman@google.coma5809a32013-06-21 15:13:34 +0000154 SkDEBUGCODE(that.validate();)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000155}
156
157SkPath::~SkPath() {
158 SkDEBUGCODE(this->validate();)
159}
160
bungeman@google.coma5809a32013-06-21 15:13:34 +0000161SkPath& SkPath::operator=(const SkPath& that) {
162 SkDEBUGCODE(that.validate();)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000163
bungeman@google.coma5809a32013-06-21 15:13:34 +0000164 if (this != &that) {
165 fPathRef.reset(SkRef(that.fPathRef.get()));
166 this->copyFields(that);
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000167#ifdef SK_BUILD_FOR_ANDROID
mtklein@google.comcb8b0ee2013-08-15 21:14:51 +0000168 fSourcePath = that.fSourcePath;
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000169#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000170 }
171 SkDEBUGCODE(this->validate();)
172 return *this;
173}
174
bungeman@google.coma5809a32013-06-21 15:13:34 +0000175void SkPath::copyFields(const SkPath& that) {
176 //fPathRef is assumed to have been set by the caller.
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000177 fLastMoveToIndex = that.fLastMoveToIndex;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000178 fFillType = that.fFillType;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000179 fConvexity = that.fConvexity;
180 fDirection = that.fDirection;
jvanverthb3eb6872014-10-24 07:12:51 -0700181 fIsVolatile = that.fIsVolatile;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000182}
183
djsollen@google.com9c1a9672013-08-09 13:49:13 +0000184bool operator==(const SkPath& a, const SkPath& b) {
reed@android.com6b82d1a2009-06-03 02:35:01 +0000185 // note: don't need to look at isConvex or bounds, since just comparing the
186 // raw data is sufficient.
reed@android.com3abec1d2009-03-02 05:36:20 +0000187 return &a == &b ||
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000188 (a.fFillType == b.fFillType && *a.fPathRef.get() == *b.fPathRef.get());
reed@android.com3abec1d2009-03-02 05:36:20 +0000189}
190
bungeman@google.coma5809a32013-06-21 15:13:34 +0000191void SkPath::swap(SkPath& that) {
192 SkASSERT(&that != NULL);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000193
bungeman@google.coma5809a32013-06-21 15:13:34 +0000194 if (this != &that) {
195 fPathRef.swap(&that.fPathRef);
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000196 SkTSwap<int>(fLastMoveToIndex, that.fLastMoveToIndex);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000197 SkTSwap<uint8_t>(fFillType, that.fFillType);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000198 SkTSwap<uint8_t>(fConvexity, that.fConvexity);
199 SkTSwap<uint8_t>(fDirection, that.fDirection);
jvanverthb3eb6872014-10-24 07:12:51 -0700200 SkTSwap<SkBool8>(fIsVolatile, that.fIsVolatile);
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000201#ifdef SK_BUILD_FOR_ANDROID
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000202 SkTSwap<const SkPath*>(fSourcePath, that.fSourcePath);
203#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000204 }
205}
206
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000207static inline bool check_edge_against_rect(const SkPoint& p0,
208 const SkPoint& p1,
209 const SkRect& rect,
210 SkPath::Direction dir) {
211 const SkPoint* edgeBegin;
212 SkVector v;
213 if (SkPath::kCW_Direction == dir) {
214 v = p1 - p0;
215 edgeBegin = &p0;
216 } else {
217 v = p0 - p1;
218 edgeBegin = &p1;
219 }
220 if (v.fX || v.fY) {
221 // check the cross product of v with the vec from edgeBegin to each rect corner
222 SkScalar yL = SkScalarMul(v.fY, rect.fLeft - edgeBegin->fX);
223 SkScalar xT = SkScalarMul(v.fX, rect.fTop - edgeBegin->fY);
224 SkScalar yR = SkScalarMul(v.fY, rect.fRight - edgeBegin->fX);
225 SkScalar xB = SkScalarMul(v.fX, rect.fBottom - edgeBegin->fY);
226 if ((xT < yL) || (xT < yR) || (xB < yL) || (xB < yR)) {
227 return false;
228 }
229 }
230 return true;
231}
232
233bool SkPath::conservativelyContainsRect(const SkRect& rect) const {
234 // This only handles non-degenerate convex paths currently.
235 if (kConvex_Convexity != this->getConvexity()) {
236 return false;
237 }
skia.committer@gmail.comcec8de62012-11-14 02:01:22 +0000238
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000239 Direction direction;
240 if (!this->cheapComputeDirection(&direction)) {
241 return false;
242 }
243
244 SkPoint firstPt;
245 SkPoint prevPt;
246 RawIter iter(*this);
247 SkPath::Verb verb;
248 SkPoint pts[4];
249 SkDEBUGCODE(int moveCnt = 0;)
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000250 SkDEBUGCODE(int segmentCount = 0;)
251 SkDEBUGCODE(int closeCount = 0;)
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000252
253 while ((verb = iter.next(pts)) != kDone_Verb) {
254 int nextPt = -1;
255 switch (verb) {
256 case kMove_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000257 SkASSERT(!segmentCount && !closeCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000258 SkDEBUGCODE(++moveCnt);
259 firstPt = prevPt = pts[0];
260 break;
261 case kLine_Verb:
262 nextPt = 1;
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000263 SkASSERT(moveCnt && !closeCount);
264 SkDEBUGCODE(++segmentCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000265 break;
266 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000267 case kConic_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000268 SkASSERT(moveCnt && !closeCount);
269 SkDEBUGCODE(++segmentCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000270 nextPt = 2;
271 break;
272 case kCubic_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000273 SkASSERT(moveCnt && !closeCount);
274 SkDEBUGCODE(++segmentCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000275 nextPt = 3;
276 break;
277 case kClose_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000278 SkDEBUGCODE(++closeCount;)
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000279 break;
280 default:
281 SkDEBUGFAIL("unknown verb");
282 }
283 if (-1 != nextPt) {
reed220f9262014-12-17 08:21:04 -0800284 if (SkPath::kConic_Verb == verb) {
285 SkConic orig;
286 orig.set(pts, iter.conicWeight());
287 SkPoint quadPts[5];
288 int count = orig.chopIntoQuadsPOW2(quadPts, 1);
289 SK_ALWAYSBREAK(2 == count);
290
291 if (!check_edge_against_rect(quadPts[0], quadPts[2], rect, direction)) {
292 return false;
293 }
294 if (!check_edge_against_rect(quadPts[2], quadPts[4], rect, direction)) {
295 return false;
296 }
297 } else {
298 if (!check_edge_against_rect(prevPt, pts[nextPt], rect, direction)) {
299 return false;
300 }
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000301 }
302 prevPt = pts[nextPt];
303 }
304 }
305
306 return check_edge_against_rect(prevPt, firstPt, rect, direction);
307}
308
robertphillips@google.com7101abe2013-10-29 22:45:37 +0000309uint32_t SkPath::getGenerationID() const {
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000310 uint32_t genID = fPathRef->genID();
311#ifdef SK_BUILD_FOR_ANDROID
312 SkASSERT((unsigned)fFillType < (1 << (32 - kPathRefGenIDBitCnt)));
313 genID |= static_cast<uint32_t>(fFillType) << kPathRefGenIDBitCnt;
314#endif
315 return genID;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000316}
djsollen@google.come63793a2012-03-21 15:39:03 +0000317
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000318#ifdef SK_BUILD_FOR_ANDROID
djsollen@google.come63793a2012-03-21 15:39:03 +0000319const SkPath* SkPath::getSourcePath() const {
320 return fSourcePath;
321}
322
323void SkPath::setSourcePath(const SkPath* path) {
324 fSourcePath = path;
325}
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000326#endif
327
reed@android.com8a1c16f2008-12-17 15:59:43 +0000328void SkPath::reset() {
329 SkDEBUGCODE(this->validate();)
330
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000331 fPathRef.reset(SkPathRef::CreateEmpty());
bungeman@google.coma5809a32013-06-21 15:13:34 +0000332 this->resetFields();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000333}
334
335void SkPath::rewind() {
336 SkDEBUGCODE(this->validate();)
337
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000338 SkPathRef::Rewind(&fPathRef);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000339 this->resetFields();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000340}
341
reed@google.com7e6c4d12012-05-10 14:05:43 +0000342bool SkPath::isLine(SkPoint line[2]) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000343 int verbCount = fPathRef->countVerbs();
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000344
commit-bot@chromium.orga62efcc2013-08-05 13:23:13 +0000345 if (2 == verbCount) {
346 SkASSERT(kMove_Verb == fPathRef->atVerb(0));
347 if (kLine_Verb == fPathRef->atVerb(1)) {
348 SkASSERT(2 == fPathRef->countPoints());
reed@google.com7e6c4d12012-05-10 14:05:43 +0000349 if (line) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000350 const SkPoint* pts = fPathRef->points();
reed@google.com7e6c4d12012-05-10 14:05:43 +0000351 line[0] = pts[0];
352 line[1] = pts[1];
353 }
354 return true;
355 }
356 }
357 return false;
358}
359
caryclark@google.comf1316942011-07-26 19:54:45 +0000360/*
361 Determines if path is a rect by keeping track of changes in direction
362 and looking for a loop either clockwise or counterclockwise.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000363
caryclark@google.comf1316942011-07-26 19:54:45 +0000364 The direction is computed such that:
365 0: vertical up
caryclark@google.comf68154a2012-11-21 15:18:06 +0000366 1: horizontal left
caryclark@google.comf1316942011-07-26 19:54:45 +0000367 2: vertical down
caryclark@google.comf68154a2012-11-21 15:18:06 +0000368 3: horizontal right
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000369
caryclark@google.comf1316942011-07-26 19:54:45 +0000370A rectangle cycles up/right/down/left or up/left/down/right.
371
372The test fails if:
373 The path is closed, and followed by a line.
374 A second move creates a new endpoint.
375 A diagonal line is parsed.
376 There's more than four changes of direction.
377 There's a discontinuity on the line (e.g., a move in the middle)
378 The line reverses direction.
caryclark@google.comf1316942011-07-26 19:54:45 +0000379 The path contains a quadratic or cubic.
380 The path contains fewer than four points.
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000381 *The rectangle doesn't complete a cycle.
382 *The final point isn't equal to the first point.
383
384 *These last two conditions we relax if we have a 3-edge path that would
385 form a rectangle if it were closed (as we do when we fill a path)
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000386
caryclark@google.comf1316942011-07-26 19:54:45 +0000387It's OK if the path has:
388 Several colinear line segments composing a rectangle side.
389 Single points on the rectangle side.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000390
caryclark@google.comf1316942011-07-26 19:54:45 +0000391The direction takes advantage of the corners found since opposite sides
392must travel in opposite directions.
393
394FIXME: Allow colinear quads and cubics to be treated like lines.
395FIXME: If the API passes fill-only, return true if the filled stroke
396 is a rectangle, though the caller failed to close the path.
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000397
398 first,last,next direction state-machine:
399 0x1 is set if the segment is horizontal
400 0x2 is set if the segment is moving to the right or down
401 thus:
402 two directions are opposites iff (dirA ^ dirB) == 0x2
403 two directions are perpendicular iff (dirA ^ dirB) == 0x1
skia.committer@gmail.comf5e67c12014-01-16 07:01:48 +0000404
caryclark@google.comf1316942011-07-26 19:54:45 +0000405 */
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000406static int rect_make_dir(SkScalar dx, SkScalar dy) {
407 return ((0 != dx) << 0) | ((dx > 0 || dy > 0) << 1);
408}
caryclark@google.comf68154a2012-11-21 15:18:06 +0000409bool SkPath::isRectContour(bool allowPartial, int* currVerb, const SkPoint** ptsPtr,
410 bool* isClosed, Direction* direction) const {
caryclark@google.comf1316942011-07-26 19:54:45 +0000411 int corners = 0;
412 SkPoint first, last;
caryclark@google.com56f233a2012-11-19 13:06:06 +0000413 const SkPoint* pts = *ptsPtr;
caryclark@google.combfe90372012-11-21 13:56:20 +0000414 const SkPoint* savePts = NULL;
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000415 first.set(0, 0);
416 last.set(0, 0);
417 int firstDirection = 0;
418 int lastDirection = 0;
419 int nextDirection = 0;
420 bool closedOrMoved = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000421 bool autoClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000422 int verbCnt = fPathRef->countVerbs();
caryclark@google.com56f233a2012-11-19 13:06:06 +0000423 while (*currVerb < verbCnt && (!allowPartial || !autoClose)) {
424 switch (fPathRef->atVerb(*currVerb)) {
caryclark@google.comf1316942011-07-26 19:54:45 +0000425 case kClose_Verb:
caryclark@google.com56f233a2012-11-19 13:06:06 +0000426 savePts = pts;
427 pts = *ptsPtr;
caryclark@google.comf1316942011-07-26 19:54:45 +0000428 autoClose = true;
429 case kLine_Verb: {
430 SkScalar left = last.fX;
431 SkScalar top = last.fY;
432 SkScalar right = pts->fX;
433 SkScalar bottom = pts->fY;
434 ++pts;
435 if (left != right && top != bottom) {
436 return false; // diagonal
437 }
438 if (left == right && top == bottom) {
439 break; // single point on side OK
440 }
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000441 nextDirection = rect_make_dir(right - left, bottom - top);
caryclark@google.comf1316942011-07-26 19:54:45 +0000442 if (0 == corners) {
443 firstDirection = nextDirection;
444 first = last;
445 last = pts[-1];
446 corners = 1;
447 closedOrMoved = false;
448 break;
449 }
450 if (closedOrMoved) {
451 return false; // closed followed by a line
452 }
caryclark@google.combfe90372012-11-21 13:56:20 +0000453 if (autoClose && nextDirection == firstDirection) {
454 break; // colinear with first
455 }
caryclark@google.comf1316942011-07-26 19:54:45 +0000456 closedOrMoved = autoClose;
457 if (lastDirection != nextDirection) {
458 if (++corners > 4) {
459 return false; // too many direction changes
460 }
461 }
462 last = pts[-1];
463 if (lastDirection == nextDirection) {
464 break; // colinear segment
465 }
466 // Possible values for corners are 2, 3, and 4.
467 // When corners == 3, nextDirection opposes firstDirection.
468 // Otherwise, nextDirection at corner 2 opposes corner 4.
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000469 int turn = firstDirection ^ (corners - 1);
caryclark@google.comf1316942011-07-26 19:54:45 +0000470 int directionCycle = 3 == corners ? 0 : nextDirection ^ turn;
471 if ((directionCycle ^ turn) != nextDirection) {
472 return false; // direction didn't follow cycle
473 }
474 break;
475 }
476 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000477 case kConic_Verb:
caryclark@google.comf1316942011-07-26 19:54:45 +0000478 case kCubic_Verb:
479 return false; // quadratic, cubic not allowed
480 case kMove_Verb:
481 last = *pts++;
482 closedOrMoved = true;
483 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000484 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000485 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000486 break;
caryclark@google.comf1316942011-07-26 19:54:45 +0000487 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000488 *currVerb += 1;
caryclark@google.comf1316942011-07-26 19:54:45 +0000489 lastDirection = nextDirection;
490 }
491 // Success if 4 corners and first point equals last
caryclark@google.combfe90372012-11-21 13:56:20 +0000492 bool result = 4 == corners && (first == last || autoClose);
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000493 if (!result) {
494 // check if we are just an incomplete rectangle, in which case we can
495 // return true, but not claim to be closed.
496 // e.g.
497 // 3 sided rectangle
498 // 4 sided but the last edge is not long enough to reach the start
499 //
500 SkScalar closeX = first.x() - last.x();
501 SkScalar closeY = first.y() - last.y();
502 if (closeX && closeY) {
503 return false; // we're diagonal, abort (can we ever reach this?)
504 }
505 int closeDirection = rect_make_dir(closeX, closeY);
506 // make sure the close-segment doesn't double-back on itself
507 if (3 == corners || (4 == corners && closeDirection == lastDirection)) {
508 result = true;
509 autoClose = false; // we are not closed
510 }
511 }
caryclark@google.combfe90372012-11-21 13:56:20 +0000512 if (savePts) {
513 *ptsPtr = savePts;
514 }
caryclark@google.comf68154a2012-11-21 15:18:06 +0000515 if (result && isClosed) {
516 *isClosed = autoClose;
517 }
518 if (result && direction) {
sugoi@google.com12b4e272012-12-06 20:13:11 +0000519 *direction = firstDirection == ((lastDirection + 1) & 3) ? kCCW_Direction : kCW_Direction;
caryclark@google.comf68154a2012-11-21 15:18:06 +0000520 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000521 return result;
522}
523
robertphillips4f662e62014-12-29 14:06:51 -0800524bool SkPath::isRect(SkRect* rect, bool* isClosed, Direction* direction) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000525 SkDEBUGCODE(this->validate();)
526 int currVerb = 0;
527 const SkPoint* pts = fPathRef->points();
robertphillipsfe7c4272014-12-29 11:36:39 -0800528 const SkPoint* first = pts;
robertphillips4f662e62014-12-29 14:06:51 -0800529 if (!this->isRectContour(false, &currVerb, &pts, isClosed, direction)) {
robertphillipsfe7c4272014-12-29 11:36:39 -0800530 return false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000531 }
robertphillipsfe7c4272014-12-29 11:36:39 -0800532 if (rect) {
robertphillips4f662e62014-12-29 14:06:51 -0800533 int32_t num = SkToS32(pts - first);
534 if (num) {
535 rect->set(first, num);
robertphillipsfe7c4272014-12-29 11:36:39 -0800536 } else {
537 // 'pts' isn't updated for open rects
538 *rect = this->getBounds();
539 }
540 }
541 return true;
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000542}
skia.committer@gmail.com020b25b2013-06-22 07:00:58 +0000543
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000544bool SkPath::isNestedRects(SkRect rects[2], Direction dirs[2]) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000545 SkDEBUGCODE(this->validate();)
546 int currVerb = 0;
547 const SkPoint* pts = fPathRef->points();
548 const SkPoint* first = pts;
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000549 Direction testDirs[2];
550 if (!isRectContour(true, &currVerb, &pts, NULL, &testDirs[0])) {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000551 return false;
552 }
553 const SkPoint* last = pts;
554 SkRect testRects[2];
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000555 if (isRectContour(false, &currVerb, &pts, NULL, &testDirs[1])) {
scroggo@google.com614f9e32013-05-09 18:05:32 +0000556 testRects[0].set(first, SkToS32(last - first));
557 testRects[1].set(last, SkToS32(pts - last));
caryclark@google.com56f233a2012-11-19 13:06:06 +0000558 if (testRects[0].contains(testRects[1])) {
559 if (rects) {
560 rects[0] = testRects[0];
561 rects[1] = testRects[1];
562 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000563 if (dirs) {
564 dirs[0] = testDirs[0];
565 dirs[1] = testDirs[1];
566 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000567 return true;
568 }
569 if (testRects[1].contains(testRects[0])) {
570 if (rects) {
571 rects[0] = testRects[1];
572 rects[1] = testRects[0];
573 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000574 if (dirs) {
575 dirs[0] = testDirs[1];
576 dirs[1] = testDirs[0];
577 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000578 return true;
579 }
580 }
581 return false;
582}
583
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000584int SkPath::countPoints() const {
585 return fPathRef->countPoints();
586}
587
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000588int SkPath::getPoints(SkPoint dst[], int max) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000589 SkDEBUGCODE(this->validate();)
590
591 SkASSERT(max >= 0);
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000592 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000593 int count = SkMin32(max, fPathRef->countPoints());
594 memcpy(dst, fPathRef->points(), count * sizeof(SkPoint));
595 return fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000596}
597
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000598SkPoint SkPath::getPoint(int index) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000599 if ((unsigned)index < (unsigned)fPathRef->countPoints()) {
600 return fPathRef->atPoint(index);
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000601 }
602 return SkPoint::Make(0, 0);
603}
604
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000605int SkPath::countVerbs() const {
606 return fPathRef->countVerbs();
607}
608
609static inline void copy_verbs_reverse(uint8_t* inorderDst,
610 const uint8_t* reversedSrc,
611 int count) {
612 for (int i = 0; i < count; ++i) {
613 inorderDst[i] = reversedSrc[~i];
614 }
615}
616
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000617int SkPath::getVerbs(uint8_t dst[], int max) const {
618 SkDEBUGCODE(this->validate();)
619
620 SkASSERT(max >= 0);
621 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000622 int count = SkMin32(max, fPathRef->countVerbs());
623 copy_verbs_reverse(dst, fPathRef->verbs(), count);
624 return fPathRef->countVerbs();
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000625}
626
reed@google.com294dd7b2011-10-11 11:58:32 +0000627bool SkPath::getLastPt(SkPoint* lastPt) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000628 SkDEBUGCODE(this->validate();)
629
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000630 int count = fPathRef->countPoints();
reed@google.com294dd7b2011-10-11 11:58:32 +0000631 if (count > 0) {
632 if (lastPt) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000633 *lastPt = fPathRef->atPoint(count - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000634 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000635 return true;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000636 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000637 if (lastPt) {
638 lastPt->set(0, 0);
639 }
640 return false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000641}
642
643void SkPath::setLastPt(SkScalar x, SkScalar y) {
644 SkDEBUGCODE(this->validate();)
645
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000646 int count = fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000647 if (count == 0) {
648 this->moveTo(x, y);
649 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000650 SkPathRef::Editor ed(&fPathRef);
651 ed.atPoint(count-1)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000652 }
653}
654
reed@google.com04863fa2011-05-15 04:08:24 +0000655void SkPath::setConvexity(Convexity c) {
656 if (fConvexity != c) {
657 fConvexity = c;
reed@google.com04863fa2011-05-15 04:08:24 +0000658 }
659}
660
reed@android.com8a1c16f2008-12-17 15:59:43 +0000661//////////////////////////////////////////////////////////////////////////////
662// Construction methods
663
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000664#define DIRTY_AFTER_EDIT \
665 do { \
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000666 fConvexity = kUnknown_Convexity; \
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000667 fDirection = kUnknown_Direction; \
reed@google.comb54455e2011-05-16 14:16:04 +0000668 } while (0)
669
reed@android.com8a1c16f2008-12-17 15:59:43 +0000670void SkPath::incReserve(U16CPU inc) {
671 SkDEBUGCODE(this->validate();)
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000672 SkPathRef::Editor(&fPathRef, inc, inc);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000673 SkDEBUGCODE(this->validate();)
674}
675
676void SkPath::moveTo(SkScalar x, SkScalar y) {
677 SkDEBUGCODE(this->validate();)
678
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000679 SkPathRef::Editor ed(&fPathRef);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000680
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000681 // remember our index
682 fLastMoveToIndex = fPathRef->countPoints();
683
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000684 ed.growForVerb(kMove_Verb)->set(x, y);
bsalomonb17c1292014-08-28 14:04:55 -0700685
686 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000687}
688
689void SkPath::rMoveTo(SkScalar x, SkScalar y) {
690 SkPoint pt;
691 this->getLastPt(&pt);
692 this->moveTo(pt.fX + x, pt.fY + y);
693}
694
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000695void SkPath::injectMoveToIfNeeded() {
696 if (fLastMoveToIndex < 0) {
697 SkScalar x, y;
698 if (fPathRef->countVerbs() == 0) {
699 x = y = 0;
700 } else {
701 const SkPoint& pt = fPathRef->atPoint(~fLastMoveToIndex);
702 x = pt.fX;
703 y = pt.fY;
704 }
705 this->moveTo(x, y);
706 }
707}
708
reed@android.com8a1c16f2008-12-17 15:59:43 +0000709void SkPath::lineTo(SkScalar x, SkScalar y) {
710 SkDEBUGCODE(this->validate();)
711
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000712 this->injectMoveToIfNeeded();
713
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000714 SkPathRef::Editor ed(&fPathRef);
715 ed.growForVerb(kLine_Verb)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000716
reed@google.comb54455e2011-05-16 14:16:04 +0000717 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000718}
719
720void SkPath::rLineTo(SkScalar x, SkScalar y) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000721 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000722 SkPoint pt;
723 this->getLastPt(&pt);
724 this->lineTo(pt.fX + x, pt.fY + y);
725}
726
727void SkPath::quadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
728 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000729
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000730 this->injectMoveToIfNeeded();
731
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000732 SkPathRef::Editor ed(&fPathRef);
733 SkPoint* pts = ed.growForVerb(kQuad_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000734 pts[0].set(x1, y1);
735 pts[1].set(x2, y2);
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000736
reed@google.comb54455e2011-05-16 14:16:04 +0000737 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000738}
739
740void SkPath::rQuadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000741 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000742 SkPoint pt;
743 this->getLastPt(&pt);
744 this->quadTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2);
745}
746
reed@google.com277c3f82013-05-31 15:17:50 +0000747void SkPath::conicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
748 SkScalar w) {
749 // check for <= 0 or NaN with this test
750 if (!(w > 0)) {
751 this->lineTo(x2, y2);
752 } else if (!SkScalarIsFinite(w)) {
753 this->lineTo(x1, y1);
754 this->lineTo(x2, y2);
755 } else if (SK_Scalar1 == w) {
756 this->quadTo(x1, y1, x2, y2);
757 } else {
758 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000759
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000760 this->injectMoveToIfNeeded();
761
reed@google.com277c3f82013-05-31 15:17:50 +0000762 SkPathRef::Editor ed(&fPathRef);
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000763 SkPoint* pts = ed.growForVerb(kConic_Verb, w);
reed@google.com277c3f82013-05-31 15:17:50 +0000764 pts[0].set(x1, y1);
765 pts[1].set(x2, y2);
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000766
reed@google.com277c3f82013-05-31 15:17:50 +0000767 DIRTY_AFTER_EDIT;
768 }
769}
770
771void SkPath::rConicTo(SkScalar dx1, SkScalar dy1, SkScalar dx2, SkScalar dy2,
772 SkScalar w) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000773 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@google.com277c3f82013-05-31 15:17:50 +0000774 SkPoint pt;
775 this->getLastPt(&pt);
776 this->conicTo(pt.fX + dx1, pt.fY + dy1, pt.fX + dx2, pt.fY + dy2, w);
777}
778
reed@android.com8a1c16f2008-12-17 15:59:43 +0000779void SkPath::cubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
780 SkScalar x3, SkScalar y3) {
781 SkDEBUGCODE(this->validate();)
782
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000783 this->injectMoveToIfNeeded();
784
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000785 SkPathRef::Editor ed(&fPathRef);
786 SkPoint* pts = ed.growForVerb(kCubic_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000787 pts[0].set(x1, y1);
788 pts[1].set(x2, y2);
789 pts[2].set(x3, y3);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000790
reed@google.comb54455e2011-05-16 14:16:04 +0000791 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000792}
793
794void SkPath::rCubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
795 SkScalar x3, SkScalar y3) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000796 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000797 SkPoint pt;
798 this->getLastPt(&pt);
799 this->cubicTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2,
800 pt.fX + x3, pt.fY + y3);
801}
802
803void SkPath::close() {
804 SkDEBUGCODE(this->validate();)
805
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000806 int count = fPathRef->countVerbs();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000807 if (count > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000808 switch (fPathRef->atVerb(count - 1)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000809 case kLine_Verb:
810 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000811 case kConic_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +0000812 case kCubic_Verb:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000813 case kMove_Verb: {
814 SkPathRef::Editor ed(&fPathRef);
815 ed.growForVerb(kClose_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000816 break;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000817 }
reed@google.com277c3f82013-05-31 15:17:50 +0000818 case kClose_Verb:
reed@google.comfa2f2a42013-05-30 15:29:48 +0000819 // don't add a close if it's the first verb or a repeat
reed@google.com7950a9e2013-05-30 14:57:55 +0000820 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000821 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000822 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000823 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000824 }
825 }
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000826
827 // signal that we need a moveTo to follow us (unless we're done)
828#if 0
829 if (fLastMoveToIndex >= 0) {
830 fLastMoveToIndex = ~fLastMoveToIndex;
831 }
832#else
833 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
834#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000835}
836
837///////////////////////////////////////////////////////////////////////////////
reed@google.comabf15c12011-01-18 20:35:51 +0000838
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000839static void assert_known_direction(int dir) {
840 SkASSERT(SkPath::kCW_Direction == dir || SkPath::kCCW_Direction == dir);
841}
842
reed@android.com8a1c16f2008-12-17 15:59:43 +0000843void SkPath::addRect(const SkRect& rect, Direction dir) {
844 this->addRect(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, dir);
845}
846
847void SkPath::addRect(SkScalar left, SkScalar top, SkScalar right,
848 SkScalar bottom, Direction dir) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000849 assert_known_direction(dir);
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000850 fDirection = this->hasOnlyMoveTos() ? dir : kUnknown_Direction;
851 SkAutoDisableDirectionCheck addc(this);
852
reed@android.com8a1c16f2008-12-17 15:59:43 +0000853 SkAutoPathBoundsUpdate apbu(this, left, top, right, bottom);
854
855 this->incReserve(5);
856
857 this->moveTo(left, top);
858 if (dir == kCCW_Direction) {
859 this->lineTo(left, bottom);
860 this->lineTo(right, bottom);
861 this->lineTo(right, top);
862 } else {
863 this->lineTo(right, top);
864 this->lineTo(right, bottom);
865 this->lineTo(left, bottom);
866 }
867 this->close();
868}
869
reed@google.com744faba2012-05-29 19:54:52 +0000870void SkPath::addPoly(const SkPoint pts[], int count, bool close) {
871 SkDEBUGCODE(this->validate();)
872 if (count <= 0) {
873 return;
874 }
875
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000876 fLastMoveToIndex = fPathRef->countPoints();
877
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000878 // +close makes room for the extra kClose_Verb
879 SkPathRef::Editor ed(&fPathRef, count+close, count);
880
881 ed.growForVerb(kMove_Verb)->set(pts[0].fX, pts[0].fY);
reed@google.com744faba2012-05-29 19:54:52 +0000882 if (count > 1) {
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000883 SkPoint* p = ed.growForRepeatedVerb(kLine_Verb, count - 1);
884 memcpy(p, &pts[1], (count-1) * sizeof(SkPoint));
reed@google.com744faba2012-05-29 19:54:52 +0000885 }
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000886
reed@google.com744faba2012-05-29 19:54:52 +0000887 if (close) {
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000888 ed.growForVerb(kClose_Verb);
reed@google.com744faba2012-05-29 19:54:52 +0000889 }
890
reed@google.com744faba2012-05-29 19:54:52 +0000891 DIRTY_AFTER_EDIT;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000892 SkDEBUGCODE(this->validate();)
reed@google.com744faba2012-05-29 19:54:52 +0000893}
894
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000895#include "SkGeometry.h"
896
reedf90ea012015-01-29 12:03:58 -0800897static bool arc_is_lone_point(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
898 SkPoint* pt) {
899 if (0 == sweepAngle && (0 == startAngle || SkIntToScalar(360) == startAngle)) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000900 // Chrome uses this path to move into and out of ovals. If not
901 // treated as a special case the moves can distort the oval's
902 // bounding box (and break the circle special case).
reedf90ea012015-01-29 12:03:58 -0800903 pt->set(oval.fRight, oval.centerY());
904 return true;
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000905 } else if (0 == oval.width() && 0 == oval.height()) {
906 // Chrome will sometimes create 0 radius round rects. Having degenerate
907 // quad segments in the path prevents the path from being recognized as
908 // a rect.
909 // TODO: optimizing the case where only one of width or height is zero
910 // should also be considered. This case, however, doesn't seem to be
911 // as common as the single point case.
reedf90ea012015-01-29 12:03:58 -0800912 pt->set(oval.fRight, oval.fTop);
913 return true;
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000914 }
reedf90ea012015-01-29 12:03:58 -0800915 return false;
916}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000917
reedf90ea012015-01-29 12:03:58 -0800918static int build_arc_points(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
919 SkPoint pts[kSkBuildQuadArcStorage]) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000920 SkVector start, stop;
921
922 start.fY = SkScalarSinCos(SkDegreesToRadians(startAngle), &start.fX);
923 stop.fY = SkScalarSinCos(SkDegreesToRadians(startAngle + sweepAngle),
924 &stop.fX);
925
926 /* If the sweep angle is nearly (but less than) 360, then due to precision
927 loss in radians-conversion and/or sin/cos, we may end up with coincident
928 vectors, which will fool SkBuildQuadArc into doing nothing (bad) instead
929 of drawing a nearly complete circle (good).
930 e.g. canvas.drawArc(0, 359.99, ...)
931 -vs- canvas.drawArc(0, 359.9, ...)
932 We try to detect this edge case, and tweak the stop vector
933 */
934 if (start == stop) {
935 SkScalar sw = SkScalarAbs(sweepAngle);
936 if (sw < SkIntToScalar(360) && sw > SkIntToScalar(359)) {
937 SkScalar stopRad = SkDegreesToRadians(startAngle + sweepAngle);
938 // make a guess at a tiny angle (in radians) to tweak by
939 SkScalar deltaRad = SkScalarCopySign(SK_Scalar1/512, sweepAngle);
940 // not sure how much will be enough, so we use a loop
941 do {
942 stopRad -= deltaRad;
943 stop.fY = SkScalarSinCos(stopRad, &stop.fX);
944 } while (start == stop);
945 }
946 }
947
948 SkMatrix matrix;
949
950 matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height()));
951 matrix.postTranslate(oval.centerX(), oval.centerY());
952
953 return SkBuildQuadArc(start, stop,
954 sweepAngle > 0 ? kCW_SkRotationDirection :
955 kCCW_SkRotationDirection,
956 &matrix, pts);
957}
958
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +0000959void SkPath::addRoundRect(const SkRect& rect, const SkScalar radii[],
reed@android.com8a1c16f2008-12-17 15:59:43 +0000960 Direction dir) {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +0000961 SkRRect rrect;
962 rrect.setRectRadii(rect, (const SkVector*) radii);
963 this->addRRect(rrect, dir);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000964}
965
reed1b28a3a2014-12-17 14:42:09 -0800966#ifdef SK_SUPPORT_LEGACY_ADDRRECT
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +0000967/* The inline clockwise and counterclockwise round rect quad approximations
968 make it easier to see the symmetry patterns used by add corner quads.
969Clockwise corner value
970 path->lineTo(rect.fLeft, rect.fTop + ry); 0 upper left
971 path->quadTo(rect.fLeft, rect.fTop + offPtY,
972 rect.fLeft + midPtX, rect.fTop + midPtY);
973 path->quadTo(rect.fLeft + offPtX, rect.fTop,
974 rect.fLeft + rx, rect.fTop);
975
976 path->lineTo(rect.fRight - rx, rect.fTop); 1 upper right
977 path->quadTo(rect.fRight - offPtX, rect.fTop,
978 rect.fRight - midPtX, rect.fTop + midPtY);
979 path->quadTo(rect.fRight, rect.fTop + offPtY,
980 rect.fRight, rect.fTop + ry);
981
982 path->lineTo(rect.fRight, rect.fBottom - ry); 2 lower right
983 path->quadTo(rect.fRight, rect.fBottom - offPtY,
984 rect.fRight - midPtX, rect.fBottom - midPtY);
985 path->quadTo(rect.fRight - offPtX, rect.fBottom,
986 rect.fRight - rx, rect.fBottom);
987
988 path->lineTo(rect.fLeft + rx, rect.fBottom); 3 lower left
989 path->quadTo(rect.fLeft + offPtX, rect.fBottom,
990 rect.fLeft + midPtX, rect.fBottom - midPtY);
991 path->quadTo(rect.fLeft, rect.fBottom - offPtY,
992 rect.fLeft, rect.fBottom - ry);
993
994Counterclockwise
995 path->lineTo(rect.fLeft, rect.fBottom - ry); 3 lower left
996 path->quadTo(rect.fLeft, rect.fBottom - offPtY,
997 rect.fLeft + midPtX, rect.fBottom - midPtY);
998 path->quadTo(rect.fLeft + offPtX, rect.fBottom,
999 rect.fLeft + rx, rect.fBottom);
1000
1001 path->lineTo(rect.fRight - rx, rect.fBottom); 2 lower right
1002 path->quadTo(rect.fRight - offPtX, rect.fBottom,
1003 rect.fRight - midPtX, rect.fBottom - midPtY);
1004 path->quadTo(rect.fRight, rect.fBottom - offPtY,
1005 rect.fRight, rect.fBottom - ry);
1006
1007 path->lineTo(rect.fRight, rect.fTop + ry); 1 upper right
1008 path->quadTo(rect.fRight, rect.fTop + offPtY,
1009 rect.fRight - midPtX, rect.fTop + midPtY);
1010 path->quadTo(rect.fRight - offPtX, rect.fTop,
1011 rect.fRight - rx, rect.fTop);
1012
1013 path->lineTo(rect.fLeft + rx, rect.fTop); 0 upper left
1014 path->quadTo(rect.fLeft + offPtX, rect.fTop,
1015 rect.fLeft + midPtX, rect.fTop + midPtY);
1016 path->quadTo(rect.fLeft, rect.fTop + offPtY,
1017 rect.fLeft, rect.fTop + ry);
1018*/
1019static void add_corner_quads(SkPath* path, const SkRRect& rrect,
1020 SkRRect::Corner corner, SkPath::Direction dir) {
1021 const SkRect& rect = rrect.rect();
1022 const SkVector& radii = rrect.radii(corner);
1023 SkScalar rx = radii.fX;
1024 SkScalar ry = radii.fY;
1025 // The mid point of the quadratic arc approximation is half way between the two
1026 // control points.
caryclark@google.com2e1b99e2013-11-08 18:05:02 +00001027 const SkScalar mid = 1 - (SK_Scalar1 + SK_ScalarTanPIOver8) / 2;
1028 SkScalar midPtX = rx * mid;
1029 SkScalar midPtY = ry * mid;
1030 const SkScalar control = 1 - SK_ScalarTanPIOver8;
1031 SkScalar offPtX = rx * control;
1032 SkScalar offPtY = ry * control;
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001033 static const int kCornerPts = 5;
1034 SkScalar xOff[kCornerPts];
1035 SkScalar yOff[kCornerPts];
1036
1037 if ((corner & 1) == (dir == SkPath::kCCW_Direction)) { // corners always alternate direction
1038 SkASSERT(dir == SkPath::kCCW_Direction
1039 ? corner == SkRRect::kLowerLeft_Corner || corner == SkRRect::kUpperRight_Corner
1040 : corner == SkRRect::kUpperLeft_Corner || corner == SkRRect::kLowerRight_Corner);
1041 xOff[0] = xOff[1] = 0;
1042 xOff[2] = midPtX;
1043 xOff[3] = offPtX;
1044 xOff[4] = rx;
1045 yOff[0] = ry;
1046 yOff[1] = offPtY;
1047 yOff[2] = midPtY;
1048 yOff[3] = yOff[4] = 0;
1049 } else {
1050 xOff[0] = rx;
1051 xOff[1] = offPtX;
1052 xOff[2] = midPtX;
1053 xOff[3] = xOff[4] = 0;
1054 yOff[0] = yOff[1] = 0;
1055 yOff[2] = midPtY;
1056 yOff[3] = offPtY;
1057 yOff[4] = ry;
1058 }
1059 if ((corner - 1) & 2) {
1060 SkASSERT(corner == SkRRect::kLowerLeft_Corner || corner == SkRRect::kUpperLeft_Corner);
1061 for (int i = 0; i < kCornerPts; ++i) {
1062 xOff[i] = rect.fLeft + xOff[i];
1063 }
1064 } else {
1065 SkASSERT(corner == SkRRect::kLowerRight_Corner || corner == SkRRect::kUpperRight_Corner);
1066 for (int i = 0; i < kCornerPts; ++i) {
1067 xOff[i] = rect.fRight - xOff[i];
1068 }
1069 }
1070 if (corner < SkRRect::kLowerRight_Corner) {
1071 for (int i = 0; i < kCornerPts; ++i) {
1072 yOff[i] = rect.fTop + yOff[i];
1073 }
1074 } else {
1075 for (int i = 0; i < kCornerPts; ++i) {
1076 yOff[i] = rect.fBottom - yOff[i];
1077 }
1078 }
1079
1080 SkPoint lastPt;
1081 SkAssertResult(path->getLastPt(&lastPt));
1082 if (lastPt.fX != xOff[0] || lastPt.fY != yOff[0]) {
1083 path->lineTo(xOff[0], yOff[0]);
1084 }
1085 if (rx || ry) {
1086 path->quadTo(xOff[1], yOff[1], xOff[2], yOff[2]);
1087 path->quadTo(xOff[3], yOff[3], xOff[4], yOff[4]);
1088 } else {
1089 path->lineTo(xOff[2], yOff[2]);
1090 path->lineTo(xOff[4], yOff[4]);
1091 }
1092}
reed1b28a3a2014-12-17 14:42:09 -08001093#endif
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001094
reed@google.com4ed0fb72012-12-12 20:48:18 +00001095void SkPath::addRRect(const SkRRect& rrect, Direction dir) {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001096 assert_known_direction(dir);
1097
1098 if (rrect.isEmpty()) {
1099 return;
1100 }
1101
reed@google.com4ed0fb72012-12-12 20:48:18 +00001102 const SkRect& bounds = rrect.getBounds();
1103
1104 if (rrect.isRect()) {
1105 this->addRect(bounds, dir);
1106 } else if (rrect.isOval()) {
1107 this->addOval(bounds, dir);
reed@google.com4ed0fb72012-12-12 20:48:18 +00001108 } else {
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001109 fDirection = this->hasOnlyMoveTos() ? dir : kUnknown_Direction;
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001110
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001111 SkAutoPathBoundsUpdate apbu(this, bounds);
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001112 SkAutoDisableDirectionCheck addc(this);
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001113
reed1b28a3a2014-12-17 14:42:09 -08001114#ifdef SK_SUPPORT_LEGACY_ADDRRECT
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001115 this->incReserve(21);
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001116 if (kCW_Direction == dir) {
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001117 this->moveTo(bounds.fLeft,
1118 bounds.fBottom - rrect.fRadii[SkRRect::kLowerLeft_Corner].fY);
1119 add_corner_quads(this, rrect, SkRRect::kUpperLeft_Corner, dir);
1120 add_corner_quads(this, rrect, SkRRect::kUpperRight_Corner, dir);
1121 add_corner_quads(this, rrect, SkRRect::kLowerRight_Corner, dir);
1122 add_corner_quads(this, rrect, SkRRect::kLowerLeft_Corner, dir);
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001123 } else {
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001124 this->moveTo(bounds.fLeft,
1125 bounds.fTop + rrect.fRadii[SkRRect::kUpperLeft_Corner].fY);
1126 add_corner_quads(this, rrect, SkRRect::kLowerLeft_Corner, dir);
1127 add_corner_quads(this, rrect, SkRRect::kLowerRight_Corner, dir);
1128 add_corner_quads(this, rrect, SkRRect::kUpperRight_Corner, dir);
1129 add_corner_quads(this, rrect, SkRRect::kUpperLeft_Corner, dir);
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001130 }
reed1b28a3a2014-12-17 14:42:09 -08001131#else
1132 const SkScalar L = bounds.fLeft;
1133 const SkScalar T = bounds.fTop;
1134 const SkScalar R = bounds.fRight;
1135 const SkScalar B = bounds.fBottom;
1136 const SkScalar W = SK_ScalarRoot2Over2;
1137
1138 this->incReserve(13);
1139 if (kCW_Direction == dir) {
1140 this->moveTo(L, B - rrect.fRadii[SkRRect::kLowerLeft_Corner].fY);
1141
1142 this->lineTo(L, T + rrect.fRadii[SkRRect::kUpperLeft_Corner].fY);
1143 this->conicTo(L, T, L + rrect.fRadii[SkRRect::kUpperLeft_Corner].fX, T, W);
1144
1145 this->lineTo(R - rrect.fRadii[SkRRect::kUpperRight_Corner].fX, T);
1146 this->conicTo(R, T, R, T + rrect.fRadii[SkRRect::kUpperRight_Corner].fY, W);
1147
1148 this->lineTo(R, B - rrect.fRadii[SkRRect::kLowerRight_Corner].fY);
1149 this->conicTo(R, B, R - rrect.fRadii[SkRRect::kLowerRight_Corner].fX, B, W);
1150
1151 this->lineTo(L + rrect.fRadii[SkRRect::kLowerLeft_Corner].fX, B);
1152 this->conicTo(L, B, L, B - rrect.fRadii[SkRRect::kLowerLeft_Corner].fY, W);
1153 } else {
1154 this->moveTo(L, T + rrect.fRadii[SkRRect::kUpperLeft_Corner].fY);
1155
1156 this->lineTo(L, B - rrect.fRadii[SkRRect::kLowerLeft_Corner].fY);
1157 this->conicTo(L, B, L + rrect.fRadii[SkRRect::kLowerLeft_Corner].fX, B, W);
1158
1159 this->lineTo(R - rrect.fRadii[SkRRect::kLowerRight_Corner].fX, B);
1160 this->conicTo(R, B, R, B - rrect.fRadii[SkRRect::kLowerRight_Corner].fY, W);
1161
1162 this->lineTo(R, T + rrect.fRadii[SkRRect::kUpperRight_Corner].fY);
1163 this->conicTo(R, T, R - rrect.fRadii[SkRRect::kUpperRight_Corner].fX, T, W);
1164
1165 this->lineTo(L + rrect.fRadii[SkRRect::kUpperLeft_Corner].fX, T);
1166 this->conicTo(L, T, L, T + rrect.fRadii[SkRRect::kUpperLeft_Corner].fY, W);
1167 }
1168#endif
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001169 this->close();
reed@google.com4ed0fb72012-12-12 20:48:18 +00001170 }
reed5bcbe912014-12-15 12:28:33 -08001171 SkDEBUGCODE(fPathRef->validate();)
reed@google.com4ed0fb72012-12-12 20:48:18 +00001172}
1173
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001174bool SkPath::hasOnlyMoveTos() const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001175 int count = fPathRef->countVerbs();
1176 const uint8_t* verbs = const_cast<const SkPathRef*>(fPathRef.get())->verbsMemBegin();
1177 for (int i = 0; i < count; ++i) {
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001178 if (*verbs == kLine_Verb ||
1179 *verbs == kQuad_Verb ||
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001180 *verbs == kConic_Verb ||
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001181 *verbs == kCubic_Verb) {
1182 return false;
1183 }
1184 ++verbs;
1185 }
1186 return true;
1187}
1188
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001189void SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
1190 Direction dir) {
1191 assert_known_direction(dir);
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001192
humper@google.com75e3ca12013-04-08 21:44:11 +00001193 if (rx < 0 || ry < 0) {
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001194 SkErrorInternals::SetError( kInvalidArgument_SkError,
humper@google.com75e3ca12013-04-08 21:44:11 +00001195 "I got %f and %f as radii to SkPath::AddRoundRect, "
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001196 "but negative radii are not allowed.",
humper@google.com75e3ca12013-04-08 21:44:11 +00001197 SkScalarToDouble(rx), SkScalarToDouble(ry) );
1198 return;
1199 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001200
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001201 SkRRect rrect;
1202 rrect.setRectXY(rect, rx, ry);
1203 this->addRRect(rrect, dir);
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001204}
1205
reed@android.com8a1c16f2008-12-17 15:59:43 +00001206void SkPath::addOval(const SkRect& oval, Direction dir) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +00001207 assert_known_direction(dir);
1208
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001209 /* If addOval() is called after previous moveTo(),
1210 this path is still marked as an oval. This is used to
1211 fit into WebKit's calling sequences.
1212 We can't simply check isEmpty() in this case, as additional
1213 moveTo() would mark the path non empty.
1214 */
robertphillips@google.com466310d2013-12-03 16:43:54 +00001215 bool isOval = hasOnlyMoveTos();
1216 if (isOval) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001217 fDirection = dir;
1218 } else {
1219 fDirection = kUnknown_Direction;
1220 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001221
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001222 SkAutoDisableDirectionCheck addc(this);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001223
reed@android.com8a1c16f2008-12-17 15:59:43 +00001224 SkAutoPathBoundsUpdate apbu(this, oval);
1225
reed220f9262014-12-17 08:21:04 -08001226#ifdef SK_SUPPORT_LEGACY_ADDOVAL
reed@android.com8a1c16f2008-12-17 15:59:43 +00001227 SkScalar cx = oval.centerX();
1228 SkScalar cy = oval.centerY();
1229 SkScalar rx = SkScalarHalf(oval.width());
1230 SkScalar ry = SkScalarHalf(oval.height());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001231
reed@android.com8a1c16f2008-12-17 15:59:43 +00001232 SkScalar sx = SkScalarMul(rx, SK_ScalarTanPIOver8);
1233 SkScalar sy = SkScalarMul(ry, SK_ScalarTanPIOver8);
1234 SkScalar mx = SkScalarMul(rx, SK_ScalarRoot2Over2);
1235 SkScalar my = SkScalarMul(ry, SK_ScalarRoot2Over2);
1236
1237 /*
reed220f9262014-12-17 08:21:04 -08001238 To handle imprecision in computing the center and radii, we revert to
1239 the provided bounds when we can (i.e. use oval.fLeft instead of cx-rx)
1240 to ensure that we don't exceed the oval's bounds *ever*, since we want
1241 to use oval for our fast-bounds, rather than have to recompute it.
1242 */
reed@android.com8a1c16f2008-12-17 15:59:43 +00001243 const SkScalar L = oval.fLeft; // cx - rx
1244 const SkScalar T = oval.fTop; // cy - ry
1245 const SkScalar R = oval.fRight; // cx + rx
1246 const SkScalar B = oval.fBottom; // cy + ry
1247
1248 this->incReserve(17); // 8 quads + close
1249 this->moveTo(R, cy);
1250 if (dir == kCCW_Direction) {
1251 this->quadTo( R, cy - sy, cx + mx, cy - my);
1252 this->quadTo(cx + sx, T, cx , T);
1253 this->quadTo(cx - sx, T, cx - mx, cy - my);
1254 this->quadTo( L, cy - sy, L, cy );
1255 this->quadTo( L, cy + sy, cx - mx, cy + my);
1256 this->quadTo(cx - sx, B, cx , B);
1257 this->quadTo(cx + sx, B, cx + mx, cy + my);
1258 this->quadTo( R, cy + sy, R, cy );
1259 } else {
1260 this->quadTo( R, cy + sy, cx + mx, cy + my);
1261 this->quadTo(cx + sx, B, cx , B);
1262 this->quadTo(cx - sx, B, cx - mx, cy + my);
1263 this->quadTo( L, cy + sy, L, cy );
1264 this->quadTo( L, cy - sy, cx - mx, cy - my);
1265 this->quadTo(cx - sx, T, cx , T);
1266 this->quadTo(cx + sx, T, cx + mx, cy - my);
1267 this->quadTo( R, cy - sy, R, cy );
1268 }
reed220f9262014-12-17 08:21:04 -08001269#else
1270 const SkScalar L = oval.fLeft;
1271 const SkScalar T = oval.fTop;
1272 const SkScalar R = oval.fRight;
1273 const SkScalar B = oval.fBottom;
1274 const SkScalar cx = oval.centerX();
1275 const SkScalar cy = oval.centerY();
1276 const SkScalar weight = SK_ScalarRoot2Over2;
1277
1278 this->incReserve(9); // move + 4 conics
1279 this->moveTo(R, cy);
1280 if (dir == kCCW_Direction) {
1281 this->conicTo(R, T, cx, T, weight);
1282 this->conicTo(L, T, L, cy, weight);
1283 this->conicTo(L, B, cx, B, weight);
1284 this->conicTo(R, B, R, cy, weight);
1285 } else {
1286 this->conicTo(R, B, cx, B, weight);
1287 this->conicTo(L, B, L, cy, weight);
1288 this->conicTo(L, T, cx, T, weight);
1289 this->conicTo(R, T, R, cy, weight);
1290 }
1291#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +00001292 this->close();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001293
robertphillips@google.com466310d2013-12-03 16:43:54 +00001294 SkPathRef::Editor ed(&fPathRef);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001295
robertphillips@google.com466310d2013-12-03 16:43:54 +00001296 ed.setIsOval(isOval);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001297}
1298
reed@android.com8a1c16f2008-12-17 15:59:43 +00001299void SkPath::addCircle(SkScalar x, SkScalar y, SkScalar r, Direction dir) {
1300 if (r > 0) {
1301 SkRect rect;
1302 rect.set(x - r, y - r, x + r, y + r);
1303 this->addOval(rect, dir);
1304 }
1305}
1306
reed@android.com8a1c16f2008-12-17 15:59:43 +00001307void SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
1308 bool forceMoveTo) {
1309 if (oval.width() < 0 || oval.height() < 0) {
1310 return;
1311 }
1312
reedf90ea012015-01-29 12:03:58 -08001313 if (fPathRef->countVerbs() == 0) {
1314 forceMoveTo = true;
1315 }
1316
1317 SkPoint lonePt;
1318 if (arc_is_lone_point(oval, startAngle, sweepAngle, &lonePt)) {
1319 forceMoveTo ? this->moveTo(lonePt) : this->lineTo(lonePt);
1320 return;
1321 }
1322
reed@android.com8a1c16f2008-12-17 15:59:43 +00001323 SkPoint pts[kSkBuildQuadArcStorage];
1324 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
1325 SkASSERT((count & 1) == 1);
1326
reed@android.com8a1c16f2008-12-17 15:59:43 +00001327 this->incReserve(count);
1328 forceMoveTo ? this->moveTo(pts[0]) : this->lineTo(pts[0]);
1329 for (int i = 1; i < count; i += 2) {
1330 this->quadTo(pts[i], pts[i+1]);
1331 }
1332}
1333
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001334void SkPath::addArc(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001335 if (oval.isEmpty() || 0 == sweepAngle) {
1336 return;
1337 }
1338
1339 const SkScalar kFullCircleAngle = SkIntToScalar(360);
1340
1341 if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
1342 this->addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction);
1343 return;
1344 }
1345
reedf90ea012015-01-29 12:03:58 -08001346 SkPoint lonePt;
1347 if (arc_is_lone_point(oval, startAngle, sweepAngle, &lonePt)) {
1348 this->moveTo(lonePt);
1349 return;
1350 }
1351
reed@android.com8a1c16f2008-12-17 15:59:43 +00001352 SkPoint pts[kSkBuildQuadArcStorage];
1353 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
1354
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001355 SkDEBUGCODE(this->validate();)
1356 SkASSERT(count & 1);
1357
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +00001358 fLastMoveToIndex = fPathRef->countPoints();
1359
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001360 SkPathRef::Editor ed(&fPathRef, 1+(count-1)/2, count);
1361
1362 ed.growForVerb(kMove_Verb)->set(pts[0].fX, pts[0].fY);
1363 if (count > 1) {
1364 SkPoint* p = ed.growForRepeatedVerb(kQuad_Verb, (count-1)/2);
1365 memcpy(p, &pts[1], (count-1) * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001366 }
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001367
1368 DIRTY_AFTER_EDIT;
1369 SkDEBUGCODE(this->validate();)
reed@android.com8a1c16f2008-12-17 15:59:43 +00001370}
1371
1372/*
1373 Need to handle the case when the angle is sharp, and our computed end-points
1374 for the arc go behind pt1 and/or p2...
1375*/
1376void SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
1377 SkScalar radius) {
reeda8b326c2014-12-09 11:50:32 -08001378 if (radius == 0) {
1379 this->lineTo(x1, y1);
1380 return;
1381 }
1382
1383 SkVector before, after;
reed@google.comabf15c12011-01-18 20:35:51 +00001384
reed@android.com8a1c16f2008-12-17 15:59:43 +00001385 // need to know our prev pt so we can construct tangent vectors
1386 {
1387 SkPoint start;
1388 this->getLastPt(&start);
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001389 // Handle degenerate cases by adding a line to the first point and
1390 // bailing out.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001391 before.setNormalize(x1 - start.fX, y1 - start.fY);
1392 after.setNormalize(x2 - x1, y2 - y1);
1393 }
reed@google.comabf15c12011-01-18 20:35:51 +00001394
reed@android.com8a1c16f2008-12-17 15:59:43 +00001395 SkScalar cosh = SkPoint::DotProduct(before, after);
1396 SkScalar sinh = SkPoint::CrossProduct(before, after);
1397
1398 if (SkScalarNearlyZero(sinh)) { // angle is too tight
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001399 this->lineTo(x1, y1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001400 return;
1401 }
reed@google.comabf15c12011-01-18 20:35:51 +00001402
reed@android.com8a1c16f2008-12-17 15:59:43 +00001403 SkScalar dist = SkScalarMulDiv(radius, SK_Scalar1 - cosh, sinh);
1404 if (dist < 0) {
1405 dist = -dist;
1406 }
1407
1408 SkScalar xx = x1 - SkScalarMul(dist, before.fX);
1409 SkScalar yy = y1 - SkScalarMul(dist, before.fY);
1410 SkRotationDirection arcDir;
1411
1412 // now turn before/after into normals
1413 if (sinh > 0) {
1414 before.rotateCCW();
1415 after.rotateCCW();
1416 arcDir = kCW_SkRotationDirection;
1417 } else {
1418 before.rotateCW();
1419 after.rotateCW();
1420 arcDir = kCCW_SkRotationDirection;
1421 }
1422
1423 SkMatrix matrix;
1424 SkPoint pts[kSkBuildQuadArcStorage];
reed@google.comabf15c12011-01-18 20:35:51 +00001425
reed@android.com8a1c16f2008-12-17 15:59:43 +00001426 matrix.setScale(radius, radius);
1427 matrix.postTranslate(xx - SkScalarMul(radius, before.fX),
1428 yy - SkScalarMul(radius, before.fY));
reed@google.comabf15c12011-01-18 20:35:51 +00001429
reed@android.com8a1c16f2008-12-17 15:59:43 +00001430 int count = SkBuildQuadArc(before, after, arcDir, &matrix, pts);
reed@google.comabf15c12011-01-18 20:35:51 +00001431
reed@android.com8a1c16f2008-12-17 15:59:43 +00001432 this->incReserve(count);
1433 // [xx,yy] == pts[0]
1434 this->lineTo(xx, yy);
1435 for (int i = 1; i < count; i += 2) {
1436 this->quadTo(pts[i], pts[i+1]);
1437 }
1438}
1439
1440///////////////////////////////////////////////////////////////////////////////
1441
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001442void SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy, AddPathMode mode) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001443 SkMatrix matrix;
1444
1445 matrix.setTranslate(dx, dy);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001446 this->addPath(path, matrix, mode);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001447}
1448
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001449void SkPath::addPath(const SkPath& path, const SkMatrix& matrix, AddPathMode mode) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001450 SkPathRef::Editor(&fPathRef, path.countVerbs(), path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001451
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001452 RawIter iter(path);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001453 SkPoint pts[4];
1454 Verb verb;
1455
1456 SkMatrix::MapPtsProc proc = matrix.getMapPtsProc();
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001457 bool firstVerb = true;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001458 while ((verb = iter.next(pts)) != kDone_Verb) {
1459 switch (verb) {
1460 case kMove_Verb:
1461 proc(matrix, &pts[0], &pts[0], 1);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001462 if (firstVerb && mode == kExtend_AddPathMode && !isEmpty()) {
1463 injectMoveToIfNeeded(); // In case last contour is closed
1464 this->lineTo(pts[0]);
1465 } else {
1466 this->moveTo(pts[0]);
1467 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001468 break;
1469 case kLine_Verb:
1470 proc(matrix, &pts[1], &pts[1], 1);
1471 this->lineTo(pts[1]);
1472 break;
1473 case kQuad_Verb:
1474 proc(matrix, &pts[1], &pts[1], 2);
1475 this->quadTo(pts[1], pts[2]);
1476 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001477 case kConic_Verb:
1478 proc(matrix, &pts[1], &pts[1], 2);
1479 this->conicTo(pts[1], pts[2], iter.conicWeight());
1480 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001481 case kCubic_Verb:
1482 proc(matrix, &pts[1], &pts[1], 3);
1483 this->cubicTo(pts[1], pts[2], pts[3]);
1484 break;
1485 case kClose_Verb:
1486 this->close();
1487 break;
1488 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001489 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001490 }
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001491 firstVerb = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001492 }
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
reed@android.com8a1c16f2008-12-17 15:59:43 +00001512// ignore the last point of the 1st contour
1513void SkPath::reversePathTo(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.com1dfe88e2012-10-03 13:46:20 +00001522 const uint8_t* verbs = path.fPathRef->verbs();
1523 const SkPoint* pts = path.fPathRef->points();
reed@google.com277c3f82013-05-31 15:17:50 +00001524 const SkScalar* conicWeights = path.fPathRef->conicWeights();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001525
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001526 SkASSERT(verbs[~0] == kMove_Verb);
1527 for (i = 1; i < vcount; ++i) {
reed@google.com277c3f82013-05-31 15:17:50 +00001528 unsigned v = verbs[~i];
1529 int n = pts_in_verb(v);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001530 if (n == 0) {
1531 break;
1532 }
1533 pts += n;
reed@google.com277c3f82013-05-31 15:17:50 +00001534 conicWeights += (SkPath::kConic_Verb == v);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001535 }
1536
1537 while (--i > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001538 switch (verbs[~i]) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001539 case kLine_Verb:
1540 this->lineTo(pts[-1].fX, pts[-1].fY);
1541 break;
1542 case kQuad_Verb:
1543 this->quadTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY);
1544 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001545 case kConic_Verb:
1546 this->conicTo(pts[-1], pts[-2], *--conicWeights);
1547 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001548 case kCubic_Verb:
1549 this->cubicTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY,
1550 pts[-3].fX, pts[-3].fY);
1551 break;
1552 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001553 SkDEBUGFAIL("bad verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001554 break;
1555 }
reed@google.com277c3f82013-05-31 15:17:50 +00001556 pts -= pts_in_verb(verbs[~i]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001557 }
1558}
1559
reed@google.com63d73742012-01-10 15:33:12 +00001560void SkPath::reverseAddPath(const SkPath& src) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001561 SkPathRef::Editor ed(&fPathRef, src.fPathRef->countPoints(), src.fPathRef->countVerbs());
reed@google.com63d73742012-01-10 15:33:12 +00001562
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001563 const SkPoint* pts = src.fPathRef->pointsEnd();
1564 // we will iterator through src's verbs backwards
1565 const uint8_t* verbs = src.fPathRef->verbsMemBegin(); // points at the last verb
1566 const uint8_t* verbsEnd = src.fPathRef->verbs(); // points just past the first verb
reed@google.com277c3f82013-05-31 15:17:50 +00001567 const SkScalar* conicWeights = src.fPathRef->conicWeightsEnd();
reed@google.com63d73742012-01-10 15:33:12 +00001568
1569 bool needMove = true;
1570 bool needClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001571 while (verbs < verbsEnd) {
1572 uint8_t v = *(verbs++);
reed@google.com277c3f82013-05-31 15:17:50 +00001573 int n = pts_in_verb(v);
reed@google.com63d73742012-01-10 15:33:12 +00001574
1575 if (needMove) {
1576 --pts;
1577 this->moveTo(pts->fX, pts->fY);
1578 needMove = false;
1579 }
1580 pts -= n;
1581 switch (v) {
1582 case kMove_Verb:
1583 if (needClose) {
1584 this->close();
1585 needClose = false;
1586 }
1587 needMove = true;
1588 pts += 1; // so we see the point in "if (needMove)" above
1589 break;
1590 case kLine_Verb:
1591 this->lineTo(pts[0]);
1592 break;
1593 case kQuad_Verb:
1594 this->quadTo(pts[1], pts[0]);
1595 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001596 case kConic_Verb:
1597 this->conicTo(pts[1], pts[0], *--conicWeights);
1598 break;
reed@google.com63d73742012-01-10 15:33:12 +00001599 case kCubic_Verb:
1600 this->cubicTo(pts[2], pts[1], pts[0]);
1601 break;
1602 case kClose_Verb:
1603 needClose = true;
1604 break;
1605 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00001606 SkDEBUGFAIL("unexpected verb");
reed@google.com63d73742012-01-10 15:33:12 +00001607 }
1608 }
1609}
1610
reed@android.com8a1c16f2008-12-17 15:59:43 +00001611///////////////////////////////////////////////////////////////////////////////
1612
1613void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
1614 SkMatrix matrix;
1615
1616 matrix.setTranslate(dx, dy);
1617 this->transform(matrix, dst);
1618}
1619
reed@android.com8a1c16f2008-12-17 15:59:43 +00001620static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
1621 int level = 2) {
1622 if (--level >= 0) {
1623 SkPoint tmp[7];
1624
1625 SkChopCubicAtHalf(pts, tmp);
1626 subdivide_cubic_to(path, &tmp[0], level);
1627 subdivide_cubic_to(path, &tmp[3], level);
1628 } else {
1629 path->cubicTo(pts[1], pts[2], pts[3]);
1630 }
1631}
1632
1633void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
1634 SkDEBUGCODE(this->validate();)
1635 if (dst == NULL) {
1636 dst = (SkPath*)this;
1637 }
1638
tomhudson@google.com8d430182011-06-06 19:11:19 +00001639 if (matrix.hasPerspective()) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001640 SkPath tmp;
1641 tmp.fFillType = fFillType;
1642
1643 SkPath::Iter iter(*this, false);
1644 SkPoint pts[4];
1645 SkPath::Verb verb;
1646
reed@google.com4a3b7142012-05-16 17:16:46 +00001647 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001648 switch (verb) {
1649 case kMove_Verb:
1650 tmp.moveTo(pts[0]);
1651 break;
1652 case kLine_Verb:
1653 tmp.lineTo(pts[1]);
1654 break;
1655 case kQuad_Verb:
reed220f9262014-12-17 08:21:04 -08001656 // promote the quad to a conic
1657 tmp.conicTo(pts[1], pts[2],
1658 SkConic::TransformW(pts, SK_Scalar1, matrix));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001659 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001660 case kConic_Verb:
reed220f9262014-12-17 08:21:04 -08001661 tmp.conicTo(pts[1], pts[2],
1662 SkConic::TransformW(pts, iter.conicWeight(), matrix));
reed@google.com277c3f82013-05-31 15:17:50 +00001663 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001664 case kCubic_Verb:
1665 subdivide_cubic_to(&tmp, pts);
1666 break;
1667 case kClose_Verb:
1668 tmp.close();
1669 break;
1670 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001671 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001672 break;
1673 }
1674 }
1675
1676 dst->swap(tmp);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001677 SkPathRef::Editor ed(&dst->fPathRef);
1678 matrix.mapPoints(ed.points(), ed.pathRef()->countPoints());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001679 dst->fDirection = kUnknown_Direction;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001680 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001681 SkPathRef::CreateTransformedCopy(&dst->fPathRef, *fPathRef.get(), matrix);
1682
reed@android.com8a1c16f2008-12-17 15:59:43 +00001683 if (this != dst) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001684 dst->fFillType = fFillType;
reed@google.com2a6f8ab2011-10-25 18:41:23 +00001685 dst->fConvexity = fConvexity;
jvanverthb3eb6872014-10-24 07:12:51 -07001686 dst->fIsVolatile = fIsVolatile;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001687 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001688
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001689 if (kUnknown_Direction == fDirection) {
1690 dst->fDirection = kUnknown_Direction;
1691 } else {
1692 SkScalar det2x2 =
1693 SkScalarMul(matrix.get(SkMatrix::kMScaleX), matrix.get(SkMatrix::kMScaleY)) -
1694 SkScalarMul(matrix.get(SkMatrix::kMSkewX), matrix.get(SkMatrix::kMSkewY));
1695 if (det2x2 < 0) {
1696 dst->fDirection = SkPath::OppositeDirection(static_cast<Direction>(fDirection));
1697 } else if (det2x2 > 0) {
1698 dst->fDirection = fDirection;
1699 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001700 dst->fConvexity = kUnknown_Convexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001701 dst->fDirection = kUnknown_Direction;
1702 }
1703 }
1704
reed@android.com8a1c16f2008-12-17 15:59:43 +00001705 SkDEBUGCODE(dst->validate();)
1706 }
1707}
1708
reed@android.com8a1c16f2008-12-17 15:59:43 +00001709///////////////////////////////////////////////////////////////////////////////
1710///////////////////////////////////////////////////////////////////////////////
1711
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001712enum SegmentState {
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001713 kEmptyContour_SegmentState, // The current contour is empty. We may be
1714 // starting processing or we may have just
1715 // closed a contour.
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001716 kAfterMove_SegmentState, // We have seen a move, but nothing else.
1717 kAfterPrimitive_SegmentState // We have seen a primitive but not yet
1718 // closed the path. Also the initial state.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001719};
1720
1721SkPath::Iter::Iter() {
1722#ifdef SK_DEBUG
1723 fPts = NULL;
reed@google.com277c3f82013-05-31 15:17:50 +00001724 fConicWeights = NULL;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001725 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001726 fForceClose = fCloseLine = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001727 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001728#endif
1729 // need to init enough to make next() harmlessly return kDone_Verb
1730 fVerbs = NULL;
1731 fVerbStop = NULL;
1732 fNeedClose = false;
1733}
1734
1735SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
1736 this->setPath(path, forceClose);
1737}
1738
1739void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001740 fPts = path.fPathRef->points();
1741 fVerbs = path.fPathRef->verbs();
1742 fVerbStop = path.fPathRef->verbsMemBegin();
reed@google.com277c3f82013-05-31 15:17:50 +00001743 fConicWeights = path.fPathRef->conicWeights() - 1; // begin one behind
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001744 fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org72785c42011-12-29 21:03:28 +00001745 fMoveTo.fX = fMoveTo.fY = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001746 fForceClose = SkToU8(forceClose);
1747 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001748 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001749}
1750
1751bool SkPath::Iter::isClosedContour() const {
1752 if (fVerbs == NULL || fVerbs == fVerbStop) {
1753 return false;
1754 }
1755 if (fForceClose) {
1756 return true;
1757 }
1758
1759 const uint8_t* verbs = fVerbs;
1760 const uint8_t* stop = fVerbStop;
reed@google.comabf15c12011-01-18 20:35:51 +00001761
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001762 if (kMove_Verb == *(verbs - 1)) {
1763 verbs -= 1; // skip the initial moveto
reed@android.com8a1c16f2008-12-17 15:59:43 +00001764 }
1765
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001766 while (verbs > stop) {
1767 // verbs points one beyond the current verb, decrement first.
1768 unsigned v = *(--verbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001769 if (kMove_Verb == v) {
1770 break;
1771 }
1772 if (kClose_Verb == v) {
1773 return true;
1774 }
1775 }
1776 return false;
1777}
1778
1779SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001780 SkASSERT(pts);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001781 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001782 // A special case: if both points are NaN, SkPoint::operation== returns
1783 // false, but the iterator expects that they are treated as the same.
1784 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001785 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1786 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001787 return kClose_Verb;
1788 }
1789
reed@google.com9e25dbf2012-05-15 17:05:38 +00001790 pts[0] = fLastPt;
1791 pts[1] = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001792 fLastPt = fMoveTo;
1793 fCloseLine = true;
1794 return kLine_Verb;
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +00001795 } else {
1796 pts[0] = fMoveTo;
1797 return kClose_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001798 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001799}
1800
reed@google.com9e25dbf2012-05-15 17:05:38 +00001801const SkPoint& SkPath::Iter::cons_moveTo() {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001802 if (fSegmentState == kAfterMove_SegmentState) {
1803 // Set the first return pt to the move pt
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001804 fSegmentState = kAfterPrimitive_SegmentState;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001805 return fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001806 } else {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001807 SkASSERT(fSegmentState == kAfterPrimitive_SegmentState);
1808 // Set the first return pt to the last pt of the previous primitive.
reed@google.com9e25dbf2012-05-15 17:05:38 +00001809 return fPts[-1];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001810 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001811}
1812
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001813void SkPath::Iter::consumeDegenerateSegments() {
1814 // We need to step over anything that will not move the current draw point
1815 // forward before the next move is seen
1816 const uint8_t* lastMoveVerb = 0;
1817 const SkPoint* lastMovePt = 0;
1818 SkPoint lastPt = fLastPt;
1819 while (fVerbs != fVerbStop) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001820 unsigned verb = *(fVerbs - 1); // fVerbs is one beyond the current verb
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001821 switch (verb) {
1822 case kMove_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001823 // Keep a record of this most recent move
1824 lastMoveVerb = fVerbs;
1825 lastMovePt = fPts;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001826 lastPt = fPts[0];
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001827 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001828 fPts++;
1829 break;
1830
1831 case kClose_Verb:
schenney@chromium.org7e963602012-06-13 17:05:43 +00001832 // A close when we are in a segment is always valid except when it
1833 // follows a move which follows a segment.
1834 if (fSegmentState == kAfterPrimitive_SegmentState && !lastMoveVerb) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001835 return;
1836 }
1837 // A close at any other time must be ignored
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001838 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001839 break;
1840
1841 case kLine_Verb:
1842 if (!IsLineDegenerate(lastPt, fPts[0])) {
1843 if (lastMoveVerb) {
1844 fVerbs = lastMoveVerb;
1845 fPts = lastMovePt;
1846 return;
1847 }
1848 return;
1849 }
1850 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001851 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001852 fPts++;
1853 break;
1854
reed@google.com277c3f82013-05-31 15:17:50 +00001855 case kConic_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001856 case kQuad_Verb:
1857 if (!IsQuadDegenerate(lastPt, fPts[0], fPts[1])) {
1858 if (lastMoveVerb) {
1859 fVerbs = lastMoveVerb;
1860 fPts = lastMovePt;
1861 return;
1862 }
1863 return;
1864 }
1865 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001866 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001867 fPts += 2;
reed@google.com277c3f82013-05-31 15:17:50 +00001868 fConicWeights += (kConic_Verb == verb);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001869 break;
1870
1871 case kCubic_Verb:
1872 if (!IsCubicDegenerate(lastPt, fPts[0], fPts[1], fPts[2])) {
1873 if (lastMoveVerb) {
1874 fVerbs = lastMoveVerb;
1875 fPts = lastMovePt;
1876 return;
1877 }
1878 return;
1879 }
1880 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001881 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001882 fPts += 3;
1883 break;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001884
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001885 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001886 SkDEBUGFAIL("Should never see kDone_Verb");
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001887 }
1888 }
1889}
1890
reed@google.com4a3b7142012-05-16 17:16:46 +00001891SkPath::Verb SkPath::Iter::doNext(SkPoint ptsParam[4]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001892 SkASSERT(ptsParam);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001893
reed@android.com8a1c16f2008-12-17 15:59:43 +00001894 if (fVerbs == fVerbStop) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001895 // Close the curve if requested and if there is some curve to close
1896 if (fNeedClose && fSegmentState == kAfterPrimitive_SegmentState) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001897 if (kLine_Verb == this->autoClose(ptsParam)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001898 return kLine_Verb;
1899 }
1900 fNeedClose = false;
1901 return kClose_Verb;
1902 }
1903 return kDone_Verb;
1904 }
1905
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001906 // fVerbs is one beyond the current verb, decrement first
1907 unsigned verb = *(--fVerbs);
reed@google.com9e25dbf2012-05-15 17:05:38 +00001908 const SkPoint* SK_RESTRICT srcPts = fPts;
1909 SkPoint* SK_RESTRICT pts = ptsParam;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001910
1911 switch (verb) {
1912 case kMove_Verb:
1913 if (fNeedClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001914 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001915 verb = this->autoClose(pts);
1916 if (verb == kClose_Verb) {
1917 fNeedClose = false;
1918 }
1919 return (Verb)verb;
1920 }
1921 if (fVerbs == fVerbStop) { // might be a trailing moveto
1922 return kDone_Verb;
1923 }
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001924 fMoveTo = *srcPts;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001925 pts[0] = *srcPts;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001926 srcPts += 1;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001927 fSegmentState = kAfterMove_SegmentState;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001928 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001929 fNeedClose = fForceClose;
1930 break;
1931 case kLine_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001932 pts[0] = this->cons_moveTo();
1933 pts[1] = srcPts[0];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001934 fLastPt = srcPts[0];
1935 fCloseLine = false;
1936 srcPts += 1;
1937 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001938 case kConic_Verb:
1939 fConicWeights += 1;
1940 // fall-through
reed@android.com8a1c16f2008-12-17 15:59:43 +00001941 case kQuad_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001942 pts[0] = this->cons_moveTo();
1943 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001944 fLastPt = srcPts[1];
1945 srcPts += 2;
1946 break;
1947 case kCubic_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001948 pts[0] = this->cons_moveTo();
1949 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001950 fLastPt = srcPts[2];
1951 srcPts += 3;
1952 break;
1953 case kClose_Verb:
1954 verb = this->autoClose(pts);
1955 if (verb == kLine_Verb) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001956 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001957 } else {
1958 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001959 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001960 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001961 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001962 break;
1963 }
1964 fPts = srcPts;
1965 return (Verb)verb;
1966}
1967
1968///////////////////////////////////////////////////////////////////////////////
1969
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001970SkPath::RawIter::RawIter() {
1971#ifdef SK_DEBUG
1972 fPts = NULL;
reed@google.com277c3f82013-05-31 15:17:50 +00001973 fConicWeights = NULL;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001974 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
1975#endif
1976 // need to init enough to make next() harmlessly return kDone_Verb
1977 fVerbs = NULL;
1978 fVerbStop = NULL;
1979}
1980
1981SkPath::RawIter::RawIter(const SkPath& path) {
1982 this->setPath(path);
1983}
1984
1985void SkPath::RawIter::setPath(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001986 fPts = path.fPathRef->points();
1987 fVerbs = path.fPathRef->verbs();
1988 fVerbStop = path.fPathRef->verbsMemBegin();
reed@google.com277c3f82013-05-31 15:17:50 +00001989 fConicWeights = path.fPathRef->conicWeights() - 1; // begin one behind
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001990 fMoveTo.fX = fMoveTo.fY = 0;
1991 fLastPt.fX = fLastPt.fY = 0;
1992}
1993
1994SkPath::Verb SkPath::RawIter::next(SkPoint pts[4]) {
bsalomon49f085d2014-09-05 13:34:00 -07001995 SkASSERT(pts);
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001996 if (fVerbs == fVerbStop) {
1997 return kDone_Verb;
1998 }
1999
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002000 // fVerbs points one beyond next verb so decrement first.
2001 unsigned verb = *(--fVerbs);
2002 const SkPoint* srcPts = fPts;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002003
2004 switch (verb) {
2005 case kMove_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002006 pts[0] = *srcPts;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002007 fMoveTo = srcPts[0];
2008 fLastPt = fMoveTo;
2009 srcPts += 1;
2010 break;
2011 case kLine_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002012 pts[0] = fLastPt;
2013 pts[1] = srcPts[0];
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002014 fLastPt = srcPts[0];
2015 srcPts += 1;
2016 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002017 case kConic_Verb:
2018 fConicWeights += 1;
2019 // fall-through
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002020 case kQuad_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002021 pts[0] = fLastPt;
2022 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002023 fLastPt = srcPts[1];
2024 srcPts += 2;
2025 break;
2026 case kCubic_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002027 pts[0] = fLastPt;
2028 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002029 fLastPt = srcPts[2];
2030 srcPts += 3;
2031 break;
2032 case kClose_Verb:
2033 fLastPt = fMoveTo;
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002034 pts[0] = fMoveTo;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002035 break;
2036 }
2037 fPts = srcPts;
2038 return (Verb)verb;
2039}
2040
2041///////////////////////////////////////////////////////////////////////////////
2042
reed@android.com8a1c16f2008-12-17 15:59:43 +00002043/*
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002044 Format in compressed buffer: [ptCount, verbCount, pts[], verbs[]]
reed@android.com8a1c16f2008-12-17 15:59:43 +00002045*/
2046
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002047size_t SkPath::writeToMemory(void* storage) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002048 SkDEBUGCODE(this->validate();)
2049
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002050 if (NULL == storage) {
robertphillips@google.comca0c8382013-09-26 12:18:23 +00002051 const int byteCount = sizeof(int32_t) + fPathRef->writeSize();
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002052 return SkAlign4(byteCount);
2053 }
2054
2055 SkWBuffer buffer(storage);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002056
robertphillips@google.com466310d2013-12-03 16:43:54 +00002057 int32_t packed = (fConvexity << kConvexity_SerializationShift) |
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002058 (fFillType << kFillType_SerializationShift) |
jvanverthb3eb6872014-10-24 07:12:51 -07002059 (fDirection << kDirection_SerializationShift) |
2060 (fIsVolatile << kIsVolatile_SerializationShift);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002061
robertphillips@google.com2972bb52012-08-07 17:32:51 +00002062 buffer.write32(packed);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002063
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002064 fPathRef->writeToBuffer(&buffer);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002065
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002066 buffer.padToAlign4();
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002067 return buffer.pos();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002068}
2069
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002070size_t SkPath::readFromMemory(const void* storage, size_t length) {
2071 SkRBufferWithSizeCheck buffer(storage, length);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002072
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00002073 int32_t packed;
2074 if (!buffer.readS32(&packed)) {
2075 return 0;
2076 }
2077
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002078 fConvexity = (packed >> kConvexity_SerializationShift) & 0xFF;
2079 fFillType = (packed >> kFillType_SerializationShift) & 0xFF;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002080 fDirection = (packed >> kDirection_SerializationShift) & 0x3;
jvanverthb3eb6872014-10-24 07:12:51 -07002081 fIsVolatile = (packed >> kIsVolatile_SerializationShift) & 0x1;
commit-bot@chromium.orgfed2ab62014-01-23 15:16:05 +00002082 SkPathRef* pathRef = SkPathRef::CreateFromBuffer(&buffer);
reed@google.comabf15c12011-01-18 20:35:51 +00002083
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002084 size_t sizeRead = 0;
2085 if (buffer.isValid()) {
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00002086 fPathRef.reset(pathRef);
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002087 SkDEBUGCODE(this->validate();)
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00002088 buffer.skipToAlign4();
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002089 sizeRead = buffer.pos();
bsalomon49f085d2014-09-05 13:34:00 -07002090 } else if (pathRef) {
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00002091 // If the buffer is not valid, pathRef should be NULL
2092 sk_throw();
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002093 }
2094 return sizeRead;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002095}
2096
2097///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00002098
reede05fed02014-12-15 07:59:53 -08002099#include "SkStringUtils.h"
caryclark66a5d8b2014-06-24 08:30:15 -07002100#include "SkStream.h"
reed@google.com51bbe752013-01-17 22:07:50 +00002101
reed@google.com51bbe752013-01-17 22:07:50 +00002102static void append_params(SkString* str, const char label[], const SkPoint pts[],
reede05fed02014-12-15 07:59:53 -08002103 int count, SkScalarAsStringType strType, SkScalar conicWeight = -1) {
reed@google.com51bbe752013-01-17 22:07:50 +00002104 str->append(label);
2105 str->append("(");
skia.committer@gmail.com15dd3002013-01-18 07:07:28 +00002106
reed@google.com51bbe752013-01-17 22:07:50 +00002107 const SkScalar* values = &pts[0].fX;
2108 count *= 2;
2109
2110 for (int i = 0; i < count; ++i) {
reede05fed02014-12-15 07:59:53 -08002111 SkAppendScalar(str, values[i], strType);
reed@google.com51bbe752013-01-17 22:07:50 +00002112 if (i < count - 1) {
2113 str->append(", ");
2114 }
2115 }
reed@google.com277c3f82013-05-31 15:17:50 +00002116 if (conicWeight >= 0) {
2117 str->append(", ");
reede05fed02014-12-15 07:59:53 -08002118 SkAppendScalar(str, conicWeight, strType);
reed@google.com277c3f82013-05-31 15:17:50 +00002119 }
caryclark08fa28c2014-10-23 13:08:56 -07002120 str->append(");");
reede05fed02014-12-15 07:59:53 -08002121 if (kHex_SkScalarAsStringType == strType) {
caryclark08fa28c2014-10-23 13:08:56 -07002122 str->append(" // ");
2123 for (int i = 0; i < count; ++i) {
reede05fed02014-12-15 07:59:53 -08002124 SkAppendScalarDec(str, values[i]);
caryclark08fa28c2014-10-23 13:08:56 -07002125 if (i < count - 1) {
2126 str->append(", ");
2127 }
2128 }
2129 if (conicWeight >= 0) {
2130 str->append(", ");
reede05fed02014-12-15 07:59:53 -08002131 SkAppendScalarDec(str, conicWeight);
caryclark08fa28c2014-10-23 13:08:56 -07002132 }
2133 }
2134 str->append("\n");
reed@google.com51bbe752013-01-17 22:07:50 +00002135}
2136
caryclarke9562592014-09-15 09:26:09 -07002137void SkPath::dump(SkWStream* wStream, bool forceClose, bool dumpAsHex) const {
reede05fed02014-12-15 07:59:53 -08002138 SkScalarAsStringType asType = dumpAsHex ? kHex_SkScalarAsStringType : kDec_SkScalarAsStringType;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002139 Iter iter(*this, forceClose);
2140 SkPoint pts[4];
2141 Verb verb;
2142
caryclark66a5d8b2014-06-24 08:30:15 -07002143 if (!wStream) {
2144 SkDebugf("path: forceClose=%s\n", forceClose ? "true" : "false");
2145 }
reed@google.com51bbe752013-01-17 22:07:50 +00002146 SkString builder;
2147
reed@google.com4a3b7142012-05-16 17:16:46 +00002148 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002149 switch (verb) {
2150 case kMove_Verb:
reede05fed02014-12-15 07:59:53 -08002151 append_params(&builder, "path.moveTo", &pts[0], 1, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002152 break;
2153 case kLine_Verb:
reede05fed02014-12-15 07:59:53 -08002154 append_params(&builder, "path.lineTo", &pts[1], 1, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002155 break;
2156 case kQuad_Verb:
reede05fed02014-12-15 07:59:53 -08002157 append_params(&builder, "path.quadTo", &pts[1], 2, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002158 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002159 case kConic_Verb:
reede05fed02014-12-15 07:59:53 -08002160 append_params(&builder, "path.conicTo", &pts[1], 2, asType, iter.conicWeight());
reed@google.com277c3f82013-05-31 15:17:50 +00002161 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002162 case kCubic_Verb:
reede05fed02014-12-15 07:59:53 -08002163 append_params(&builder, "path.cubicTo", &pts[1], 3, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002164 break;
2165 case kClose_Verb:
caryclark66a5d8b2014-06-24 08:30:15 -07002166 builder.append("path.close();\n");
reed@android.com8a1c16f2008-12-17 15:59:43 +00002167 break;
2168 default:
2169 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
2170 verb = kDone_Verb; // stop the loop
2171 break;
2172 }
2173 }
caryclark66a5d8b2014-06-24 08:30:15 -07002174 if (wStream) {
2175 wStream->writeText(builder.c_str());
2176 } else {
2177 SkDebugf("%s", builder.c_str());
2178 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002179}
2180
reed@android.come522ca52009-11-23 20:10:41 +00002181void SkPath::dump() const {
caryclarke9562592014-09-15 09:26:09 -07002182 this->dump(NULL, false, false);
2183}
2184
2185void SkPath::dumpHex() const {
2186 this->dump(NULL, false, true);
reed@android.come522ca52009-11-23 20:10:41 +00002187}
2188
2189#ifdef SK_DEBUG
2190void SkPath::validate() const {
2191 SkASSERT(this != NULL);
2192 SkASSERT((fFillType & ~3) == 0);
reed@google.comabf15c12011-01-18 20:35:51 +00002193
djsollen@google.com077348c2012-10-22 20:23:32 +00002194#ifdef SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002195 if (!fBoundsIsDirty) {
2196 SkRect bounds;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002197
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002198 bool isFinite = compute_pt_bounds(&bounds, *fPathRef.get());
robertphillips@google.com5d8d1862012-08-15 14:36:41 +00002199 SkASSERT(SkToBool(fIsFinite) == isFinite);
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002200
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002201 if (fPathRef->countPoints() <= 1) {
reed@android.come522ca52009-11-23 20:10:41 +00002202 // if we're empty, fBounds may be empty but translated, so we can't
2203 // necessarily compare to bounds directly
2204 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
2205 // be [2, 2, 2, 2]
2206 SkASSERT(bounds.isEmpty());
2207 SkASSERT(fBounds.isEmpty());
2208 } else {
reed@google.comeac52bd2011-11-14 18:13:59 +00002209 if (bounds.isEmpty()) {
2210 SkASSERT(fBounds.isEmpty());
2211 } else {
reed@google.com3563c9e2011-11-14 19:34:57 +00002212 if (!fBounds.isEmpty()) {
2213 SkASSERT(fBounds.contains(bounds));
2214 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002215 }
reed@android.come522ca52009-11-23 20:10:41 +00002216 }
2217 }
djsollen@google.com077348c2012-10-22 20:23:32 +00002218#endif // SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002219}
djsollen@google.com077348c2012-10-22 20:23:32 +00002220#endif // SK_DEBUG
reed@android.come522ca52009-11-23 20:10:41 +00002221
reed@google.com04863fa2011-05-15 04:08:24 +00002222///////////////////////////////////////////////////////////////////////////////
2223
reed@google.com0b7b9822011-05-16 12:29:27 +00002224static int sign(SkScalar x) { return x < 0; }
2225#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00002226
robertphillipsc506e302014-09-16 09:43:31 -07002227enum DirChange {
2228 kLeft_DirChange,
2229 kRight_DirChange,
2230 kStraight_DirChange,
2231 kBackwards_DirChange,
2232
2233 kInvalid_DirChange
2234};
2235
2236
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002237static bool almost_equal(SkScalar compA, SkScalar compB) {
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002238 // The error epsilon was empirically derived; worse case round rects
2239 // with a mid point outset by 2x float epsilon in tests had an error
2240 // of 12.
2241 const int epsilon = 16;
2242 if (!SkScalarIsFinite(compA) || !SkScalarIsFinite(compB)) {
2243 return false;
2244 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002245 // no need to check for small numbers because SkPath::Iter has removed degenerate values
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002246 int aBits = SkFloatAs2sCompliment(compA);
2247 int bBits = SkFloatAs2sCompliment(compB);
2248 return aBits < bBits + epsilon && bBits < aBits + epsilon;
reed@google.com04863fa2011-05-15 04:08:24 +00002249}
2250
robertphillipsc506e302014-09-16 09:43:31 -07002251static DirChange direction_change(const SkPoint& lastPt, const SkVector& curPt,
2252 const SkVector& lastVec, const SkVector& curVec) {
2253 SkScalar cross = SkPoint::CrossProduct(lastVec, curVec);
2254
2255 SkScalar smallest = SkTMin(curPt.fX, SkTMin(curPt.fY, SkTMin(lastPt.fX, lastPt.fY)));
2256 SkScalar largest = SkTMax(curPt.fX, SkTMax(curPt.fY, SkTMax(lastPt.fX, lastPt.fY)));
2257 largest = SkTMax(largest, -smallest);
2258
2259 if (!almost_equal(largest, largest + cross)) {
2260 int sign = SkScalarSignAsInt(cross);
2261 if (sign) {
2262 return (1 == sign) ? kRight_DirChange : kLeft_DirChange;
2263 }
2264 }
2265
2266 if (!SkScalarNearlyZero(lastVec.lengthSqd(), SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
2267 !SkScalarNearlyZero(curVec.lengthSqd(), SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
2268 lastVec.dot(curVec) < 0.0f) {
2269 return kBackwards_DirChange;
2270 }
2271
2272 return kStraight_DirChange;
2273}
2274
reed@google.com04863fa2011-05-15 04:08:24 +00002275// only valid for a single contour
2276struct Convexicator {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002277 Convexicator()
2278 : fPtCount(0)
2279 , fConvexity(SkPath::kConvex_Convexity)
caryclarkd3d1a982014-12-08 04:57:38 -08002280 , fDirection(SkPath::kUnknown_Direction)
2281 , fIsFinite(true) {
robertphillipsc506e302014-09-16 09:43:31 -07002282 fExpectedDir = kInvalid_DirChange;
reed@google.com04863fa2011-05-15 04:08:24 +00002283 // warnings
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002284 fLastPt.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002285 fCurrPt.set(0, 0);
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002286 fLastVec.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002287 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00002288
2289 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00002290 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00002291 }
2292
2293 SkPath::Convexity getConvexity() const { return fConvexity; }
2294
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002295 /** The direction returned is only valid if the path is determined convex */
2296 SkPath::Direction getDirection() const { return fDirection; }
2297
reed@google.com04863fa2011-05-15 04:08:24 +00002298 void addPt(const SkPoint& pt) {
caryclarkd3d1a982014-12-08 04:57:38 -08002299 if (SkPath::kConcave_Convexity == fConvexity || !fIsFinite) {
reed@google.com04863fa2011-05-15 04:08:24 +00002300 return;
2301 }
2302
2303 if (0 == fPtCount) {
2304 fCurrPt = pt;
2305 ++fPtCount;
2306 } else {
2307 SkVector vec = pt - fCurrPt;
caryclarkd3d1a982014-12-08 04:57:38 -08002308 SkScalar lengthSqd = vec.lengthSqd();
2309 if (!SkScalarIsFinite(lengthSqd)) {
2310 fIsFinite = false;
2311 } else if (!SkScalarNearlyZero(lengthSqd, SK_ScalarNearlyZero*SK_ScalarNearlyZero)) {
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002312 fLastPt = fCurrPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002313 fCurrPt = pt;
2314 if (++fPtCount == 2) {
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002315 fFirstVec = fLastVec = vec;
reed@google.com04863fa2011-05-15 04:08:24 +00002316 } else {
2317 SkASSERT(fPtCount > 2);
2318 this->addVec(vec);
2319 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002320
reed@google.com85b6e392011-05-15 20:25:17 +00002321 int sx = sign(vec.fX);
2322 int sy = sign(vec.fY);
2323 fDx += (sx != fSx);
2324 fDy += (sy != fSy);
2325 fSx = sx;
2326 fSy = sy;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002327
reed@google.com85b6e392011-05-15 20:25:17 +00002328 if (fDx > 3 || fDy > 3) {
2329 fConvexity = SkPath::kConcave_Convexity;
2330 }
reed@google.com04863fa2011-05-15 04:08:24 +00002331 }
2332 }
2333 }
2334
2335 void close() {
2336 if (fPtCount > 2) {
2337 this->addVec(fFirstVec);
2338 }
2339 }
2340
caryclarkd3d1a982014-12-08 04:57:38 -08002341 bool isFinite() const {
2342 return fIsFinite;
2343 }
2344
reed@google.com04863fa2011-05-15 04:08:24 +00002345private:
2346 void addVec(const SkVector& vec) {
2347 SkASSERT(vec.fX || vec.fY);
robertphillipsc506e302014-09-16 09:43:31 -07002348 DirChange dir = direction_change(fLastPt, fCurrPt, fLastVec, vec);
2349 switch (dir) {
2350 case kLeft_DirChange: // fall through
2351 case kRight_DirChange:
2352 if (kInvalid_DirChange == fExpectedDir) {
2353 fExpectedDir = dir;
2354 fDirection = (kRight_DirChange == dir) ? SkPath::kCW_Direction
2355 : SkPath::kCCW_Direction;
2356 } else if (dir != fExpectedDir) {
2357 fConvexity = SkPath::kConcave_Convexity;
2358 fDirection = SkPath::kUnknown_Direction;
2359 }
2360 fLastVec = vec;
2361 break;
2362 case kStraight_DirChange:
2363 break;
2364 case kBackwards_DirChange:
2365 fLastVec = vec;
2366 break;
2367 case kInvalid_DirChange:
2368 SkFAIL("Use of invalid direction change flag");
2369 break;
reed@google.com04863fa2011-05-15 04:08:24 +00002370 }
2371 }
2372
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002373 SkPoint fLastPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002374 SkPoint fCurrPt;
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002375 // fLastVec does not necessarily start at fLastPt. We only advance it when the cross product
2376 // value with the current vec is deemed to be of a significant value.
2377 SkVector fLastVec, fFirstVec;
reed@google.com04863fa2011-05-15 04:08:24 +00002378 int fPtCount; // non-degenerate points
robertphillipsc506e302014-09-16 09:43:31 -07002379 DirChange fExpectedDir;
reed@google.com04863fa2011-05-15 04:08:24 +00002380 SkPath::Convexity fConvexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002381 SkPath::Direction fDirection;
reed@google.com0b7b9822011-05-16 12:29:27 +00002382 int fDx, fDy, fSx, fSy;
caryclarkd3d1a982014-12-08 04:57:38 -08002383 bool fIsFinite;
reed@google.com04863fa2011-05-15 04:08:24 +00002384};
2385
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002386SkPath::Convexity SkPath::internalGetConvexity() const {
2387 SkASSERT(kUnknown_Convexity == fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002388 SkPoint pts[4];
2389 SkPath::Verb verb;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002390 SkPath::Iter iter(*this, true);
reed@google.com04863fa2011-05-15 04:08:24 +00002391
2392 int contourCount = 0;
2393 int count;
2394 Convexicator state;
2395
caryclarkd3d1a982014-12-08 04:57:38 -08002396 if (!isFinite()) {
2397 return kUnknown_Convexity;
2398 }
reed@google.com04863fa2011-05-15 04:08:24 +00002399 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
2400 switch (verb) {
2401 case kMove_Verb:
2402 if (++contourCount > 1) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002403 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002404 return kConcave_Convexity;
2405 }
2406 pts[1] = pts[0];
2407 count = 1;
2408 break;
2409 case kLine_Verb: count = 1; break;
2410 case kQuad_Verb: count = 2; break;
reed@google.com277c3f82013-05-31 15:17:50 +00002411 case kConic_Verb: count = 2; break;
reed@google.com04863fa2011-05-15 04:08:24 +00002412 case kCubic_Verb: count = 3; break;
2413 case kClose_Verb:
2414 state.close();
2415 count = 0;
2416 break;
2417 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00002418 SkDEBUGFAIL("bad verb");
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002419 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002420 return kConcave_Convexity;
2421 }
2422
2423 for (int i = 1; i <= count; i++) {
2424 state.addPt(pts[i]);
2425 }
2426 // early exit
caryclarkd3d1a982014-12-08 04:57:38 -08002427 if (!state.isFinite()) {
2428 return kUnknown_Convexity;
2429 }
reed@google.com04863fa2011-05-15 04:08:24 +00002430 if (kConcave_Convexity == state.getConvexity()) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002431 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002432 return kConcave_Convexity;
2433 }
2434 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002435 fConvexity = state.getConvexity();
2436 if (kConvex_Convexity == fConvexity && kUnknown_Direction == fDirection) {
2437 fDirection = state.getDirection();
2438 }
2439 return static_cast<Convexity>(fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002440}
reed@google.com69a99432012-01-10 18:00:10 +00002441
2442///////////////////////////////////////////////////////////////////////////////
2443
2444class ContourIter {
2445public:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002446 ContourIter(const SkPathRef& pathRef);
reed@google.com69a99432012-01-10 18:00:10 +00002447
2448 bool done() const { return fDone; }
2449 // if !done() then these may be called
2450 int count() const { return fCurrPtCount; }
2451 const SkPoint* pts() const { return fCurrPt; }
2452 void next();
2453
2454private:
2455 int fCurrPtCount;
2456 const SkPoint* fCurrPt;
2457 const uint8_t* fCurrVerb;
2458 const uint8_t* fStopVerbs;
reed@google.com277c3f82013-05-31 15:17:50 +00002459 const SkScalar* fCurrConicWeight;
reed@google.com69a99432012-01-10 18:00:10 +00002460 bool fDone;
reed@google.comd1ab9322012-01-10 18:40:03 +00002461 SkDEBUGCODE(int fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002462};
2463
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002464ContourIter::ContourIter(const SkPathRef& pathRef) {
2465 fStopVerbs = pathRef.verbsMemBegin();
reed@google.com69a99432012-01-10 18:00:10 +00002466 fDone = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002467 fCurrPt = pathRef.points();
2468 fCurrVerb = pathRef.verbs();
reed@google.com277c3f82013-05-31 15:17:50 +00002469 fCurrConicWeight = pathRef.conicWeights();
reed@google.com69a99432012-01-10 18:00:10 +00002470 fCurrPtCount = 0;
reed@google.comd1ab9322012-01-10 18:40:03 +00002471 SkDEBUGCODE(fContourCounter = 0;)
reed@google.com69a99432012-01-10 18:00:10 +00002472 this->next();
2473}
2474
2475void ContourIter::next() {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002476 if (fCurrVerb <= fStopVerbs) {
reed@google.com69a99432012-01-10 18:00:10 +00002477 fDone = true;
2478 }
2479 if (fDone) {
2480 return;
2481 }
2482
2483 // skip pts of prev contour
2484 fCurrPt += fCurrPtCount;
2485
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002486 SkASSERT(SkPath::kMove_Verb == fCurrVerb[~0]);
reed@google.com69a99432012-01-10 18:00:10 +00002487 int ptCount = 1; // moveTo
2488 const uint8_t* verbs = fCurrVerb;
2489
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002490 for (--verbs; verbs > fStopVerbs; --verbs) {
2491 switch (verbs[~0]) {
reed@google.com69a99432012-01-10 18:00:10 +00002492 case SkPath::kMove_Verb:
reed@google.com69a99432012-01-10 18:00:10 +00002493 goto CONTOUR_END;
2494 case SkPath::kLine_Verb:
2495 ptCount += 1;
2496 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002497 case SkPath::kConic_Verb:
2498 fCurrConicWeight += 1;
2499 // fall-through
reed@google.com69a99432012-01-10 18:00:10 +00002500 case SkPath::kQuad_Verb:
2501 ptCount += 2;
2502 break;
2503 case SkPath::kCubic_Verb:
2504 ptCount += 3;
2505 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002506 case SkPath::kClose_Verb:
2507 break;
2508 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00002509 SkDEBUGFAIL("unexpected verb");
reed@google.com69a99432012-01-10 18:00:10 +00002510 break;
2511 }
2512 }
2513CONTOUR_END:
2514 fCurrPtCount = ptCount;
2515 fCurrVerb = verbs;
reed@google.comd1ab9322012-01-10 18:40:03 +00002516 SkDEBUGCODE(++fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002517}
2518
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002519// returns cross product of (p1 - p0) and (p2 - p0)
reed@google.com69a99432012-01-10 18:00:10 +00002520static SkScalar cross_prod(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002521 SkScalar cross = SkPoint::CrossProduct(p1 - p0, p2 - p0);
2522 // We may get 0 when the above subtracts underflow. We expect this to be
2523 // very rare and lazily promote to double.
2524 if (0 == cross) {
2525 double p0x = SkScalarToDouble(p0.fX);
2526 double p0y = SkScalarToDouble(p0.fY);
2527
2528 double p1x = SkScalarToDouble(p1.fX);
2529 double p1y = SkScalarToDouble(p1.fY);
2530
2531 double p2x = SkScalarToDouble(p2.fX);
2532 double p2y = SkScalarToDouble(p2.fY);
2533
2534 cross = SkDoubleToScalar((p1x - p0x) * (p2y - p0y) -
2535 (p1y - p0y) * (p2x - p0x));
2536
2537 }
2538 return cross;
reed@google.com69a99432012-01-10 18:00:10 +00002539}
2540
reed@google.comc1ea60a2012-01-31 15:15:36 +00002541// Returns the first pt with the maximum Y coordinate
reed@google.com69a99432012-01-10 18:00:10 +00002542static int find_max_y(const SkPoint pts[], int count) {
2543 SkASSERT(count > 0);
2544 SkScalar max = pts[0].fY;
reed@google.comc1ea60a2012-01-31 15:15:36 +00002545 int firstIndex = 0;
reed@google.com69a99432012-01-10 18:00:10 +00002546 for (int i = 1; i < count; ++i) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002547 SkScalar y = pts[i].fY;
2548 if (y > max) {
2549 max = y;
2550 firstIndex = i;
reed@google.com69a99432012-01-10 18:00:10 +00002551 }
2552 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002553 return firstIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002554}
2555
reed@google.comcabaf1d2012-01-11 21:03:05 +00002556static int find_diff_pt(const SkPoint pts[], int index, int n, int inc) {
2557 int i = index;
2558 for (;;) {
2559 i = (i + inc) % n;
2560 if (i == index) { // we wrapped around, so abort
2561 break;
2562 }
2563 if (pts[index] != pts[i]) { // found a different point, success!
2564 break;
2565 }
2566 }
2567 return i;
2568}
2569
reed@google.comc1ea60a2012-01-31 15:15:36 +00002570/**
2571 * Starting at index, and moving forward (incrementing), find the xmin and
2572 * xmax of the contiguous points that have the same Y.
2573 */
2574static int find_min_max_x_at_y(const SkPoint pts[], int index, int n,
2575 int* maxIndexPtr) {
2576 const SkScalar y = pts[index].fY;
2577 SkScalar min = pts[index].fX;
2578 SkScalar max = min;
2579 int minIndex = index;
2580 int maxIndex = index;
2581 for (int i = index + 1; i < n; ++i) {
2582 if (pts[i].fY != y) {
2583 break;
2584 }
2585 SkScalar x = pts[i].fX;
2586 if (x < min) {
2587 min = x;
2588 minIndex = i;
2589 } else if (x > max) {
2590 max = x;
2591 maxIndex = i;
2592 }
2593 }
2594 *maxIndexPtr = maxIndex;
2595 return minIndex;
2596}
2597
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002598static void crossToDir(SkScalar cross, SkPath::Direction* dir) {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002599 *dir = cross > 0 ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002600}
2601
reed@google.comac8543f2012-01-30 20:51:25 +00002602/*
2603 * We loop through all contours, and keep the computed cross-product of the
2604 * contour that contained the global y-max. If we just look at the first
2605 * contour, we may find one that is wound the opposite way (correctly) since
2606 * it is the interior of a hole (e.g. 'o'). Thus we must find the contour
2607 * that is outer most (or at least has the global y-max) before we can consider
2608 * its cross product.
2609 */
reed@google.com69a99432012-01-10 18:00:10 +00002610bool SkPath::cheapComputeDirection(Direction* dir) const {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002611 if (kUnknown_Direction != fDirection) {
2612 *dir = static_cast<Direction>(fDirection);
2613 return true;
2614 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002615
2616 // don't want to pay the cost for computing this if it
2617 // is unknown, so we don't call isConvex()
2618 if (kConvex_Convexity == this->getConvexityOrUnknown()) {
2619 SkASSERT(kUnknown_Direction == fDirection);
2620 *dir = static_cast<Direction>(fDirection);
2621 return false;
2622 }
reed@google.com69a99432012-01-10 18:00:10 +00002623
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002624 ContourIter iter(*fPathRef.get());
reed@google.com69a99432012-01-10 18:00:10 +00002625
reed@google.comac8543f2012-01-30 20:51:25 +00002626 // initialize with our logical y-min
2627 SkScalar ymax = this->getBounds().fTop;
2628 SkScalar ymaxCross = 0;
2629
reed@google.com69a99432012-01-10 18:00:10 +00002630 for (; !iter.done(); iter.next()) {
2631 int n = iter.count();
reed@google.comcabaf1d2012-01-11 21:03:05 +00002632 if (n < 3) {
2633 continue;
2634 }
djsollen@google.come63793a2012-03-21 15:39:03 +00002635
reed@google.comcabaf1d2012-01-11 21:03:05 +00002636 const SkPoint* pts = iter.pts();
reed@google.com69a99432012-01-10 18:00:10 +00002637 SkScalar cross = 0;
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002638 int index = find_max_y(pts, n);
2639 if (pts[index].fY < ymax) {
2640 continue;
2641 }
2642
2643 // If there is more than 1 distinct point at the y-max, we take the
2644 // x-min and x-max of them and just subtract to compute the dir.
2645 if (pts[(index + 1) % n].fY == pts[index].fY) {
2646 int maxIndex;
2647 int minIndex = find_min_max_x_at_y(pts, index, n, &maxIndex);
2648 if (minIndex == maxIndex) {
2649 goto TRY_CROSSPROD;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002650 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002651 SkASSERT(pts[minIndex].fY == pts[index].fY);
2652 SkASSERT(pts[maxIndex].fY == pts[index].fY);
2653 SkASSERT(pts[minIndex].fX <= pts[maxIndex].fX);
2654 // we just subtract the indices, and let that auto-convert to
2655 // SkScalar, since we just want - or + to signal the direction.
2656 cross = minIndex - maxIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002657 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002658 TRY_CROSSPROD:
2659 // Find a next and prev index to use for the cross-product test,
2660 // but we try to find pts that form non-zero vectors from pts[index]
2661 //
2662 // Its possible that we can't find two non-degenerate vectors, so
2663 // we have to guard our search (e.g. all the pts could be in the
2664 // same place).
2665
2666 // we pass n - 1 instead of -1 so we don't foul up % operator by
2667 // passing it a negative LH argument.
2668 int prev = find_diff_pt(pts, index, n, n - 1);
2669 if (prev == index) {
2670 // completely degenerate, skip to next contour
reed@google.comac8543f2012-01-30 20:51:25 +00002671 continue;
2672 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002673 int next = find_diff_pt(pts, index, n, 1);
2674 SkASSERT(next != index);
2675 cross = cross_prod(pts[prev], pts[index], pts[next]);
2676 // if we get a zero and the points are horizontal, then we look at the spread in
2677 // x-direction. We really should continue to walk away from the degeneracy until
2678 // there is a divergence.
2679 if (0 == cross && pts[prev].fY == pts[index].fY && pts[next].fY == pts[index].fY) {
2680 // construct the subtract so we get the correct Direction below
2681 cross = pts[index].fX - pts[next].fX;
reed@google.com188bfcf2012-01-17 18:26:38 +00002682 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002683 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002684
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002685 if (cross) {
2686 // record our best guess so far
2687 ymax = pts[index].fY;
2688 ymaxCross = cross;
reed@google.com69a99432012-01-10 18:00:10 +00002689 }
2690 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002691 if (ymaxCross) {
2692 crossToDir(ymaxCross, dir);
2693 fDirection = *dir;
2694 return true;
2695 } else {
2696 return false;
2697 }
reed@google.comac8543f2012-01-30 20:51:25 +00002698}
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002699
2700///////////////////////////////////////////////////////////////////////////////
2701
2702static SkScalar eval_cubic_coeff(SkScalar A, SkScalar B, SkScalar C,
2703 SkScalar D, SkScalar t) {
2704 return SkScalarMulAdd(SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C), t, D);
2705}
2706
2707static SkScalar eval_cubic_pts(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2708 SkScalar t) {
2709 SkScalar A = c3 + 3*(c1 - c2) - c0;
2710 SkScalar B = 3*(c2 - c1 - c1 + c0);
2711 SkScalar C = 3*(c1 - c0);
2712 SkScalar D = c0;
2713 return eval_cubic_coeff(A, B, C, D, t);
2714}
2715
2716/* Given 4 cubic points (either Xs or Ys), and a target X or Y, compute the
2717 t value such that cubic(t) = target
2718 */
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002719static void chopMonoCubicAt(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002720 SkScalar target, SkScalar* t) {
2721 // SkASSERT(c0 <= c1 && c1 <= c2 && c2 <= c3);
2722 SkASSERT(c0 < target && target < c3);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002723
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002724 SkScalar D = c0 - target;
2725 SkScalar A = c3 + 3*(c1 - c2) - c0;
2726 SkScalar B = 3*(c2 - c1 - c1 + c0);
2727 SkScalar C = 3*(c1 - c0);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002728
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002729 const SkScalar TOLERANCE = SK_Scalar1 / 4096;
2730 SkScalar minT = 0;
2731 SkScalar maxT = SK_Scalar1;
2732 SkScalar mid;
2733 int i;
2734 for (i = 0; i < 16; i++) {
2735 mid = SkScalarAve(minT, maxT);
2736 SkScalar delta = eval_cubic_coeff(A, B, C, D, mid);
2737 if (delta < 0) {
2738 minT = mid;
2739 delta = -delta;
2740 } else {
2741 maxT = mid;
2742 }
2743 if (delta < TOLERANCE) {
2744 break;
2745 }
2746 }
2747 *t = mid;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002748}
2749
2750template <size_t N> static void find_minmax(const SkPoint pts[],
2751 SkScalar* minPtr, SkScalar* maxPtr) {
2752 SkScalar min, max;
2753 min = max = pts[0].fX;
2754 for (size_t i = 1; i < N; ++i) {
2755 min = SkMinScalar(min, pts[i].fX);
2756 max = SkMaxScalar(max, pts[i].fX);
2757 }
2758 *minPtr = min;
2759 *maxPtr = max;
2760}
2761
2762static int winding_mono_cubic(const SkPoint pts[], SkScalar x, SkScalar y) {
2763 SkPoint storage[4];
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002764
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002765 int dir = 1;
2766 if (pts[0].fY > pts[3].fY) {
2767 storage[0] = pts[3];
2768 storage[1] = pts[2];
2769 storage[2] = pts[1];
2770 storage[3] = pts[0];
2771 pts = storage;
2772 dir = -1;
2773 }
2774 if (y < pts[0].fY || y >= pts[3].fY) {
2775 return 0;
2776 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002777
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002778 // quickreject or quickaccept
2779 SkScalar min, max;
2780 find_minmax<4>(pts, &min, &max);
2781 if (x < min) {
2782 return 0;
2783 }
2784 if (x > max) {
2785 return dir;
2786 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002787
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002788 // compute the actual x(t) value
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002789 SkScalar t;
2790 chopMonoCubicAt(pts[0].fY, pts[1].fY, pts[2].fY, pts[3].fY, y, &t);
2791 SkScalar xt = eval_cubic_pts(pts[0].fX, pts[1].fX, pts[2].fX, pts[3].fX, t);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002792 return xt < x ? dir : 0;
2793}
2794
2795static int winding_cubic(const SkPoint pts[], SkScalar x, SkScalar y) {
2796 SkPoint dst[10];
2797 int n = SkChopCubicAtYExtrema(pts, dst);
2798 int w = 0;
2799 for (int i = 0; i <= n; ++i) {
2800 w += winding_mono_cubic(&dst[i * 3], x, y);
2801 }
2802 return w;
2803}
2804
2805static int winding_mono_quad(const SkPoint pts[], SkScalar x, SkScalar y) {
2806 SkScalar y0 = pts[0].fY;
2807 SkScalar y2 = pts[2].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002808
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002809 int dir = 1;
2810 if (y0 > y2) {
2811 SkTSwap(y0, y2);
2812 dir = -1;
2813 }
2814 if (y < y0 || y >= y2) {
2815 return 0;
2816 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002817
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002818 // bounds check on X (not required. is it faster?)
2819#if 0
2820 if (pts[0].fX > x && pts[1].fX > x && pts[2].fX > x) {
2821 return 0;
2822 }
2823#endif
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002824
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002825 SkScalar roots[2];
2826 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
2827 2 * (pts[1].fY - pts[0].fY),
2828 pts[0].fY - y,
2829 roots);
2830 SkASSERT(n <= 1);
2831 SkScalar xt;
2832 if (0 == n) {
2833 SkScalar mid = SkScalarAve(y0, y2);
2834 // Need [0] and [2] if dir == 1
2835 // and [2] and [0] if dir == -1
2836 xt = y < mid ? pts[1 - dir].fX : pts[dir - 1].fX;
2837 } else {
2838 SkScalar t = roots[0];
2839 SkScalar C = pts[0].fX;
2840 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
2841 SkScalar B = 2 * (pts[1].fX - C);
2842 xt = SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C);
2843 }
2844 return xt < x ? dir : 0;
2845}
2846
2847static bool is_mono_quad(SkScalar y0, SkScalar y1, SkScalar y2) {
2848 // return SkScalarSignAsInt(y0 - y1) + SkScalarSignAsInt(y1 - y2) != 0;
2849 if (y0 == y1) {
2850 return true;
2851 }
2852 if (y0 < y1) {
2853 return y1 <= y2;
2854 } else {
2855 return y1 >= y2;
2856 }
2857}
2858
2859static int winding_quad(const SkPoint pts[], SkScalar x, SkScalar y) {
2860 SkPoint dst[5];
2861 int n = 0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002862
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002863 if (!is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY)) {
2864 n = SkChopQuadAtYExtrema(pts, dst);
2865 pts = dst;
2866 }
2867 int w = winding_mono_quad(pts, x, y);
2868 if (n > 0) {
2869 w += winding_mono_quad(&pts[2], x, y);
2870 }
2871 return w;
2872}
2873
2874static int winding_line(const SkPoint pts[], SkScalar x, SkScalar y) {
2875 SkScalar x0 = pts[0].fX;
2876 SkScalar y0 = pts[0].fY;
2877 SkScalar x1 = pts[1].fX;
2878 SkScalar y1 = pts[1].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002879
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002880 SkScalar dy = y1 - y0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002881
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002882 int dir = 1;
2883 if (y0 > y1) {
2884 SkTSwap(y0, y1);
2885 dir = -1;
2886 }
2887 if (y < y0 || y >= y1) {
2888 return 0;
2889 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002890
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002891 SkScalar cross = SkScalarMul(x1 - x0, y - pts[0].fY) -
2892 SkScalarMul(dy, x - pts[0].fX);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002893
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002894 if (SkScalarSignAsInt(cross) == dir) {
2895 dir = 0;
2896 }
2897 return dir;
2898}
2899
reed@google.com4db592c2013-10-30 17:39:43 +00002900static bool contains_inclusive(const SkRect& r, SkScalar x, SkScalar y) {
2901 return r.fLeft <= x && x <= r.fRight && r.fTop <= y && y <= r.fBottom;
2902}
2903
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002904bool SkPath::contains(SkScalar x, SkScalar y) const {
2905 bool isInverse = this->isInverseFillType();
2906 if (this->isEmpty()) {
2907 return isInverse;
2908 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002909
reed@google.com4db592c2013-10-30 17:39:43 +00002910 if (!contains_inclusive(this->getBounds(), x, y)) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002911 return isInverse;
2912 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002913
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002914 SkPath::Iter iter(*this, true);
2915 bool done = false;
2916 int w = 0;
2917 do {
2918 SkPoint pts[4];
2919 switch (iter.next(pts, false)) {
2920 case SkPath::kMove_Verb:
2921 case SkPath::kClose_Verb:
2922 break;
2923 case SkPath::kLine_Verb:
2924 w += winding_line(pts, x, y);
2925 break;
2926 case SkPath::kQuad_Verb:
2927 w += winding_quad(pts, x, y);
2928 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002929 case SkPath::kConic_Verb:
2930 SkASSERT(0);
2931 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002932 case SkPath::kCubic_Verb:
2933 w += winding_cubic(pts, x, y);
2934 break;
2935 case SkPath::kDone_Verb:
2936 done = true;
2937 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002938 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002939 } while (!done);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002940
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002941 switch (this->getFillType()) {
2942 case SkPath::kEvenOdd_FillType:
2943 case SkPath::kInverseEvenOdd_FillType:
2944 w &= 1;
2945 break;
reed@google.come9bb6232012-07-11 18:56:10 +00002946 default:
2947 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002948 }
2949 return SkToBool(w);
2950}