blob: c793396b560db50b02c75ad3e76dd577cc9e16bf [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()
djsollen523cda32015-02-17 08:06:32 -0800128 : fPathRef(SkPathRef::CreateEmpty()) {
bungeman@google.coma5809a32013-06-21 15:13:34 +0000129 this->resetFields();
jvanverthb3eb6872014-10-24 07:12:51 -0700130 fIsVolatile = false;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000131}
132
133void SkPath::resetFields() {
134 //fPathRef is assumed to have been emptied by the caller.
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000135 fLastMoveToIndex = INITIAL_LASTMOVETOINDEX_VALUE;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000136 fFillType = kWinding_FillType;
reed@google.com04863fa2011-05-15 04:08:24 +0000137 fConvexity = kUnknown_Convexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000138 fDirection = kUnknown_Direction;
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000139
140 // We don't touch Android's fSourcePath. It's used to track texture garbage collection, so we
141 // don't want to muck with it if it's been set to something non-NULL.
reed@android.com6b82d1a2009-06-03 02:35:01 +0000142}
reed@android.com8a1c16f2008-12-17 15:59:43 +0000143
bungeman@google.coma5809a32013-06-21 15:13:34 +0000144SkPath::SkPath(const SkPath& that)
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000145 : fPathRef(SkRef(that.fPathRef.get())) {
bungeman@google.coma5809a32013-06-21 15:13:34 +0000146 this->copyFields(that);
147 SkDEBUGCODE(that.validate();)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000148}
149
150SkPath::~SkPath() {
151 SkDEBUGCODE(this->validate();)
152}
153
bungeman@google.coma5809a32013-06-21 15:13:34 +0000154SkPath& SkPath::operator=(const SkPath& that) {
155 SkDEBUGCODE(that.validate();)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000156
bungeman@google.coma5809a32013-06-21 15:13:34 +0000157 if (this != &that) {
158 fPathRef.reset(SkRef(that.fPathRef.get()));
159 this->copyFields(that);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000160 }
161 SkDEBUGCODE(this->validate();)
162 return *this;
163}
164
bungeman@google.coma5809a32013-06-21 15:13:34 +0000165void SkPath::copyFields(const SkPath& that) {
166 //fPathRef is assumed to have been set by the caller.
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000167 fLastMoveToIndex = that.fLastMoveToIndex;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000168 fFillType = that.fFillType;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000169 fConvexity = that.fConvexity;
170 fDirection = that.fDirection;
jvanverthb3eb6872014-10-24 07:12:51 -0700171 fIsVolatile = that.fIsVolatile;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000172}
173
djsollen@google.com9c1a9672013-08-09 13:49:13 +0000174bool operator==(const SkPath& a, const SkPath& b) {
reed@android.com6b82d1a2009-06-03 02:35:01 +0000175 // note: don't need to look at isConvex or bounds, since just comparing the
176 // raw data is sufficient.
reed@android.com3abec1d2009-03-02 05:36:20 +0000177 return &a == &b ||
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000178 (a.fFillType == b.fFillType && *a.fPathRef.get() == *b.fPathRef.get());
reed@android.com3abec1d2009-03-02 05:36:20 +0000179}
180
bungeman@google.coma5809a32013-06-21 15:13:34 +0000181void SkPath::swap(SkPath& that) {
182 SkASSERT(&that != NULL);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000183
bungeman@google.coma5809a32013-06-21 15:13:34 +0000184 if (this != &that) {
185 fPathRef.swap(&that.fPathRef);
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000186 SkTSwap<int>(fLastMoveToIndex, that.fLastMoveToIndex);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000187 SkTSwap<uint8_t>(fFillType, that.fFillType);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000188 SkTSwap<uint8_t>(fConvexity, that.fConvexity);
189 SkTSwap<uint8_t>(fDirection, that.fDirection);
jvanverthb3eb6872014-10-24 07:12:51 -0700190 SkTSwap<SkBool8>(fIsVolatile, that.fIsVolatile);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000191 }
192}
193
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000194static inline bool check_edge_against_rect(const SkPoint& p0,
195 const SkPoint& p1,
196 const SkRect& rect,
197 SkPath::Direction dir) {
198 const SkPoint* edgeBegin;
199 SkVector v;
200 if (SkPath::kCW_Direction == dir) {
201 v = p1 - p0;
202 edgeBegin = &p0;
203 } else {
204 v = p0 - p1;
205 edgeBegin = &p1;
206 }
207 if (v.fX || v.fY) {
208 // check the cross product of v with the vec from edgeBegin to each rect corner
209 SkScalar yL = SkScalarMul(v.fY, rect.fLeft - edgeBegin->fX);
210 SkScalar xT = SkScalarMul(v.fX, rect.fTop - edgeBegin->fY);
211 SkScalar yR = SkScalarMul(v.fY, rect.fRight - edgeBegin->fX);
212 SkScalar xB = SkScalarMul(v.fX, rect.fBottom - edgeBegin->fY);
213 if ((xT < yL) || (xT < yR) || (xB < yL) || (xB < yR)) {
214 return false;
215 }
216 }
217 return true;
218}
219
220bool SkPath::conservativelyContainsRect(const SkRect& rect) const {
221 // This only handles non-degenerate convex paths currently.
222 if (kConvex_Convexity != this->getConvexity()) {
223 return false;
224 }
skia.committer@gmail.comcec8de62012-11-14 02:01:22 +0000225
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000226 Direction direction;
227 if (!this->cheapComputeDirection(&direction)) {
228 return false;
229 }
230
231 SkPoint firstPt;
232 SkPoint prevPt;
233 RawIter iter(*this);
234 SkPath::Verb verb;
235 SkPoint pts[4];
236 SkDEBUGCODE(int moveCnt = 0;)
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000237 SkDEBUGCODE(int segmentCount = 0;)
238 SkDEBUGCODE(int closeCount = 0;)
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000239
240 while ((verb = iter.next(pts)) != kDone_Verb) {
241 int nextPt = -1;
242 switch (verb) {
243 case kMove_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000244 SkASSERT(!segmentCount && !closeCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000245 SkDEBUGCODE(++moveCnt);
246 firstPt = prevPt = pts[0];
247 break;
248 case kLine_Verb:
249 nextPt = 1;
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000250 SkASSERT(moveCnt && !closeCount);
251 SkDEBUGCODE(++segmentCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000252 break;
253 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000254 case kConic_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000255 SkASSERT(moveCnt && !closeCount);
256 SkDEBUGCODE(++segmentCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000257 nextPt = 2;
258 break;
259 case kCubic_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000260 SkASSERT(moveCnt && !closeCount);
261 SkDEBUGCODE(++segmentCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000262 nextPt = 3;
263 break;
264 case kClose_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000265 SkDEBUGCODE(++closeCount;)
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000266 break;
267 default:
268 SkDEBUGFAIL("unknown verb");
269 }
270 if (-1 != nextPt) {
reed220f9262014-12-17 08:21:04 -0800271 if (SkPath::kConic_Verb == verb) {
272 SkConic orig;
273 orig.set(pts, iter.conicWeight());
274 SkPoint quadPts[5];
275 int count = orig.chopIntoQuadsPOW2(quadPts, 1);
276 SK_ALWAYSBREAK(2 == count);
277
278 if (!check_edge_against_rect(quadPts[0], quadPts[2], rect, direction)) {
279 return false;
280 }
281 if (!check_edge_against_rect(quadPts[2], quadPts[4], rect, direction)) {
282 return false;
283 }
284 } else {
285 if (!check_edge_against_rect(prevPt, pts[nextPt], rect, direction)) {
286 return false;
287 }
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000288 }
289 prevPt = pts[nextPt];
290 }
291 }
292
293 return check_edge_against_rect(prevPt, firstPt, rect, direction);
294}
295
robertphillips@google.com7101abe2013-10-29 22:45:37 +0000296uint32_t SkPath::getGenerationID() const {
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000297 uint32_t genID = fPathRef->genID();
djsollen523cda32015-02-17 08:06:32 -0800298#ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000299 SkASSERT((unsigned)fFillType < (1 << (32 - kPathRefGenIDBitCnt)));
300 genID |= static_cast<uint32_t>(fFillType) << kPathRefGenIDBitCnt;
301#endif
302 return genID;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000303}
djsollen@google.come63793a2012-03-21 15:39:03 +0000304
reed@android.com8a1c16f2008-12-17 15:59:43 +0000305void SkPath::reset() {
306 SkDEBUGCODE(this->validate();)
307
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000308 fPathRef.reset(SkPathRef::CreateEmpty());
bungeman@google.coma5809a32013-06-21 15:13:34 +0000309 this->resetFields();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000310}
311
312void SkPath::rewind() {
313 SkDEBUGCODE(this->validate();)
314
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000315 SkPathRef::Rewind(&fPathRef);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000316 this->resetFields();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000317}
318
reed@google.com7e6c4d12012-05-10 14:05:43 +0000319bool SkPath::isLine(SkPoint line[2]) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000320 int verbCount = fPathRef->countVerbs();
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000321
commit-bot@chromium.orga62efcc2013-08-05 13:23:13 +0000322 if (2 == verbCount) {
323 SkASSERT(kMove_Verb == fPathRef->atVerb(0));
324 if (kLine_Verb == fPathRef->atVerb(1)) {
325 SkASSERT(2 == fPathRef->countPoints());
reed@google.com7e6c4d12012-05-10 14:05:43 +0000326 if (line) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000327 const SkPoint* pts = fPathRef->points();
reed@google.com7e6c4d12012-05-10 14:05:43 +0000328 line[0] = pts[0];
329 line[1] = pts[1];
330 }
331 return true;
332 }
333 }
334 return false;
335}
336
caryclark@google.comf1316942011-07-26 19:54:45 +0000337/*
338 Determines if path is a rect by keeping track of changes in direction
339 and looking for a loop either clockwise or counterclockwise.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000340
caryclark@google.comf1316942011-07-26 19:54:45 +0000341 The direction is computed such that:
342 0: vertical up
caryclark@google.comf68154a2012-11-21 15:18:06 +0000343 1: horizontal left
caryclark@google.comf1316942011-07-26 19:54:45 +0000344 2: vertical down
caryclark@google.comf68154a2012-11-21 15:18:06 +0000345 3: horizontal right
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000346
caryclark@google.comf1316942011-07-26 19:54:45 +0000347A rectangle cycles up/right/down/left or up/left/down/right.
348
349The test fails if:
350 The path is closed, and followed by a line.
351 A second move creates a new endpoint.
352 A diagonal line is parsed.
353 There's more than four changes of direction.
354 There's a discontinuity on the line (e.g., a move in the middle)
355 The line reverses direction.
caryclark@google.comf1316942011-07-26 19:54:45 +0000356 The path contains a quadratic or cubic.
357 The path contains fewer than four points.
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000358 *The rectangle doesn't complete a cycle.
359 *The final point isn't equal to the first point.
360
361 *These last two conditions we relax if we have a 3-edge path that would
362 form a rectangle if it were closed (as we do when we fill a path)
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000363
caryclark@google.comf1316942011-07-26 19:54:45 +0000364It's OK if the path has:
365 Several colinear line segments composing a rectangle side.
366 Single points on the rectangle side.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000367
caryclark@google.comf1316942011-07-26 19:54:45 +0000368The direction takes advantage of the corners found since opposite sides
369must travel in opposite directions.
370
371FIXME: Allow colinear quads and cubics to be treated like lines.
372FIXME: If the API passes fill-only, return true if the filled stroke
373 is a rectangle, though the caller failed to close the path.
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000374
375 first,last,next direction state-machine:
376 0x1 is set if the segment is horizontal
377 0x2 is set if the segment is moving to the right or down
378 thus:
379 two directions are opposites iff (dirA ^ dirB) == 0x2
380 two directions are perpendicular iff (dirA ^ dirB) == 0x1
skia.committer@gmail.comf5e67c12014-01-16 07:01:48 +0000381
caryclark@google.comf1316942011-07-26 19:54:45 +0000382 */
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000383static int rect_make_dir(SkScalar dx, SkScalar dy) {
384 return ((0 != dx) << 0) | ((dx > 0 || dy > 0) << 1);
385}
caryclark@google.comf68154a2012-11-21 15:18:06 +0000386bool SkPath::isRectContour(bool allowPartial, int* currVerb, const SkPoint** ptsPtr,
387 bool* isClosed, Direction* direction) const {
caryclark@google.comf1316942011-07-26 19:54:45 +0000388 int corners = 0;
389 SkPoint first, last;
caryclark@google.com56f233a2012-11-19 13:06:06 +0000390 const SkPoint* pts = *ptsPtr;
caryclark@google.combfe90372012-11-21 13:56:20 +0000391 const SkPoint* savePts = NULL;
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000392 first.set(0, 0);
393 last.set(0, 0);
394 int firstDirection = 0;
395 int lastDirection = 0;
396 int nextDirection = 0;
397 bool closedOrMoved = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000398 bool autoClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000399 int verbCnt = fPathRef->countVerbs();
caryclark@google.com56f233a2012-11-19 13:06:06 +0000400 while (*currVerb < verbCnt && (!allowPartial || !autoClose)) {
401 switch (fPathRef->atVerb(*currVerb)) {
caryclark@google.comf1316942011-07-26 19:54:45 +0000402 case kClose_Verb:
caryclark@google.com56f233a2012-11-19 13:06:06 +0000403 savePts = pts;
404 pts = *ptsPtr;
caryclark@google.comf1316942011-07-26 19:54:45 +0000405 autoClose = true;
406 case kLine_Verb: {
407 SkScalar left = last.fX;
408 SkScalar top = last.fY;
409 SkScalar right = pts->fX;
410 SkScalar bottom = pts->fY;
411 ++pts;
412 if (left != right && top != bottom) {
413 return false; // diagonal
414 }
415 if (left == right && top == bottom) {
416 break; // single point on side OK
417 }
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000418 nextDirection = rect_make_dir(right - left, bottom - top);
caryclark@google.comf1316942011-07-26 19:54:45 +0000419 if (0 == corners) {
420 firstDirection = nextDirection;
421 first = last;
422 last = pts[-1];
423 corners = 1;
424 closedOrMoved = false;
425 break;
426 }
427 if (closedOrMoved) {
428 return false; // closed followed by a line
429 }
caryclark@google.combfe90372012-11-21 13:56:20 +0000430 if (autoClose && nextDirection == firstDirection) {
431 break; // colinear with first
432 }
caryclark@google.comf1316942011-07-26 19:54:45 +0000433 closedOrMoved = autoClose;
434 if (lastDirection != nextDirection) {
435 if (++corners > 4) {
436 return false; // too many direction changes
437 }
438 }
439 last = pts[-1];
440 if (lastDirection == nextDirection) {
441 break; // colinear segment
442 }
443 // Possible values for corners are 2, 3, and 4.
444 // When corners == 3, nextDirection opposes firstDirection.
445 // Otherwise, nextDirection at corner 2 opposes corner 4.
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000446 int turn = firstDirection ^ (corners - 1);
caryclark@google.comf1316942011-07-26 19:54:45 +0000447 int directionCycle = 3 == corners ? 0 : nextDirection ^ turn;
448 if ((directionCycle ^ turn) != nextDirection) {
449 return false; // direction didn't follow cycle
450 }
451 break;
452 }
453 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000454 case kConic_Verb:
caryclark@google.comf1316942011-07-26 19:54:45 +0000455 case kCubic_Verb:
456 return false; // quadratic, cubic not allowed
457 case kMove_Verb:
458 last = *pts++;
459 closedOrMoved = true;
460 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000461 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000462 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000463 break;
caryclark@google.comf1316942011-07-26 19:54:45 +0000464 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000465 *currVerb += 1;
caryclark@google.comf1316942011-07-26 19:54:45 +0000466 lastDirection = nextDirection;
467 }
468 // Success if 4 corners and first point equals last
caryclark@google.combfe90372012-11-21 13:56:20 +0000469 bool result = 4 == corners && (first == last || autoClose);
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000470 if (!result) {
471 // check if we are just an incomplete rectangle, in which case we can
472 // return true, but not claim to be closed.
473 // e.g.
474 // 3 sided rectangle
475 // 4 sided but the last edge is not long enough to reach the start
476 //
477 SkScalar closeX = first.x() - last.x();
478 SkScalar closeY = first.y() - last.y();
479 if (closeX && closeY) {
480 return false; // we're diagonal, abort (can we ever reach this?)
481 }
482 int closeDirection = rect_make_dir(closeX, closeY);
483 // make sure the close-segment doesn't double-back on itself
484 if (3 == corners || (4 == corners && closeDirection == lastDirection)) {
485 result = true;
486 autoClose = false; // we are not closed
487 }
488 }
caryclark@google.combfe90372012-11-21 13:56:20 +0000489 if (savePts) {
490 *ptsPtr = savePts;
491 }
caryclark@google.comf68154a2012-11-21 15:18:06 +0000492 if (result && isClosed) {
493 *isClosed = autoClose;
494 }
495 if (result && direction) {
sugoi@google.com12b4e272012-12-06 20:13:11 +0000496 *direction = firstDirection == ((lastDirection + 1) & 3) ? kCCW_Direction : kCW_Direction;
caryclark@google.comf68154a2012-11-21 15:18:06 +0000497 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000498 return result;
499}
500
robertphillips4f662e62014-12-29 14:06:51 -0800501bool SkPath::isRect(SkRect* rect, bool* isClosed, Direction* direction) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000502 SkDEBUGCODE(this->validate();)
503 int currVerb = 0;
504 const SkPoint* pts = fPathRef->points();
robertphillipsfe7c4272014-12-29 11:36:39 -0800505 const SkPoint* first = pts;
robertphillips4f662e62014-12-29 14:06:51 -0800506 if (!this->isRectContour(false, &currVerb, &pts, isClosed, direction)) {
robertphillipsfe7c4272014-12-29 11:36:39 -0800507 return false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000508 }
robertphillipsfe7c4272014-12-29 11:36:39 -0800509 if (rect) {
robertphillips4f662e62014-12-29 14:06:51 -0800510 int32_t num = SkToS32(pts - first);
511 if (num) {
512 rect->set(first, num);
robertphillipsfe7c4272014-12-29 11:36:39 -0800513 } else {
514 // 'pts' isn't updated for open rects
515 *rect = this->getBounds();
516 }
517 }
518 return true;
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000519}
skia.committer@gmail.com020b25b2013-06-22 07:00:58 +0000520
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000521bool SkPath::isNestedRects(SkRect rects[2], Direction dirs[2]) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000522 SkDEBUGCODE(this->validate();)
523 int currVerb = 0;
524 const SkPoint* pts = fPathRef->points();
525 const SkPoint* first = pts;
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000526 Direction testDirs[2];
527 if (!isRectContour(true, &currVerb, &pts, NULL, &testDirs[0])) {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000528 return false;
529 }
530 const SkPoint* last = pts;
531 SkRect testRects[2];
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000532 if (isRectContour(false, &currVerb, &pts, NULL, &testDirs[1])) {
scroggo@google.com614f9e32013-05-09 18:05:32 +0000533 testRects[0].set(first, SkToS32(last - first));
534 testRects[1].set(last, SkToS32(pts - last));
caryclark@google.com56f233a2012-11-19 13:06:06 +0000535 if (testRects[0].contains(testRects[1])) {
536 if (rects) {
537 rects[0] = testRects[0];
538 rects[1] = testRects[1];
539 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000540 if (dirs) {
541 dirs[0] = testDirs[0];
542 dirs[1] = testDirs[1];
543 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000544 return true;
545 }
546 if (testRects[1].contains(testRects[0])) {
547 if (rects) {
548 rects[0] = testRects[1];
549 rects[1] = testRects[0];
550 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000551 if (dirs) {
552 dirs[0] = testDirs[1];
553 dirs[1] = testDirs[0];
554 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000555 return true;
556 }
557 }
558 return false;
559}
560
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000561int SkPath::countPoints() const {
562 return fPathRef->countPoints();
563}
564
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000565int SkPath::getPoints(SkPoint dst[], int max) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000566 SkDEBUGCODE(this->validate();)
567
568 SkASSERT(max >= 0);
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000569 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000570 int count = SkMin32(max, fPathRef->countPoints());
571 memcpy(dst, fPathRef->points(), count * sizeof(SkPoint));
572 return fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000573}
574
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000575SkPoint SkPath::getPoint(int index) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000576 if ((unsigned)index < (unsigned)fPathRef->countPoints()) {
577 return fPathRef->atPoint(index);
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000578 }
579 return SkPoint::Make(0, 0);
580}
581
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000582int SkPath::countVerbs() const {
583 return fPathRef->countVerbs();
584}
585
586static inline void copy_verbs_reverse(uint8_t* inorderDst,
587 const uint8_t* reversedSrc,
588 int count) {
589 for (int i = 0; i < count; ++i) {
590 inorderDst[i] = reversedSrc[~i];
591 }
592}
593
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000594int SkPath::getVerbs(uint8_t dst[], int max) const {
595 SkDEBUGCODE(this->validate();)
596
597 SkASSERT(max >= 0);
598 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000599 int count = SkMin32(max, fPathRef->countVerbs());
600 copy_verbs_reverse(dst, fPathRef->verbs(), count);
601 return fPathRef->countVerbs();
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000602}
603
reed@google.com294dd7b2011-10-11 11:58:32 +0000604bool SkPath::getLastPt(SkPoint* lastPt) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000605 SkDEBUGCODE(this->validate();)
606
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000607 int count = fPathRef->countPoints();
reed@google.com294dd7b2011-10-11 11:58:32 +0000608 if (count > 0) {
609 if (lastPt) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000610 *lastPt = fPathRef->atPoint(count - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000611 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000612 return true;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000613 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000614 if (lastPt) {
615 lastPt->set(0, 0);
616 }
617 return false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000618}
619
620void SkPath::setLastPt(SkScalar x, SkScalar y) {
621 SkDEBUGCODE(this->validate();)
622
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000623 int count = fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000624 if (count == 0) {
625 this->moveTo(x, y);
626 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000627 SkPathRef::Editor ed(&fPathRef);
628 ed.atPoint(count-1)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000629 }
630}
631
reed@google.com04863fa2011-05-15 04:08:24 +0000632void SkPath::setConvexity(Convexity c) {
633 if (fConvexity != c) {
634 fConvexity = c;
reed@google.com04863fa2011-05-15 04:08:24 +0000635 }
636}
637
reed@android.com8a1c16f2008-12-17 15:59:43 +0000638//////////////////////////////////////////////////////////////////////////////
639// Construction methods
640
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000641#define DIRTY_AFTER_EDIT \
642 do { \
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000643 fConvexity = kUnknown_Convexity; \
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000644 fDirection = kUnknown_Direction; \
reed@google.comb54455e2011-05-16 14:16:04 +0000645 } while (0)
646
reed@android.com8a1c16f2008-12-17 15:59:43 +0000647void SkPath::incReserve(U16CPU inc) {
648 SkDEBUGCODE(this->validate();)
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000649 SkPathRef::Editor(&fPathRef, inc, inc);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000650 SkDEBUGCODE(this->validate();)
651}
652
653void SkPath::moveTo(SkScalar x, SkScalar y) {
654 SkDEBUGCODE(this->validate();)
655
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000656 SkPathRef::Editor ed(&fPathRef);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000657
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000658 // remember our index
659 fLastMoveToIndex = fPathRef->countPoints();
660
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000661 ed.growForVerb(kMove_Verb)->set(x, y);
bsalomonb17c1292014-08-28 14:04:55 -0700662
663 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000664}
665
666void SkPath::rMoveTo(SkScalar x, SkScalar y) {
667 SkPoint pt;
668 this->getLastPt(&pt);
669 this->moveTo(pt.fX + x, pt.fY + y);
670}
671
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000672void SkPath::injectMoveToIfNeeded() {
673 if (fLastMoveToIndex < 0) {
674 SkScalar x, y;
675 if (fPathRef->countVerbs() == 0) {
676 x = y = 0;
677 } else {
678 const SkPoint& pt = fPathRef->atPoint(~fLastMoveToIndex);
679 x = pt.fX;
680 y = pt.fY;
681 }
682 this->moveTo(x, y);
683 }
684}
685
reed@android.com8a1c16f2008-12-17 15:59:43 +0000686void SkPath::lineTo(SkScalar x, SkScalar y) {
687 SkDEBUGCODE(this->validate();)
688
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000689 this->injectMoveToIfNeeded();
690
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000691 SkPathRef::Editor ed(&fPathRef);
692 ed.growForVerb(kLine_Verb)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000693
reed@google.comb54455e2011-05-16 14:16:04 +0000694 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000695}
696
697void SkPath::rLineTo(SkScalar x, SkScalar y) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000698 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000699 SkPoint pt;
700 this->getLastPt(&pt);
701 this->lineTo(pt.fX + x, pt.fY + y);
702}
703
704void SkPath::quadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
705 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000706
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000707 this->injectMoveToIfNeeded();
708
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000709 SkPathRef::Editor ed(&fPathRef);
710 SkPoint* pts = ed.growForVerb(kQuad_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000711 pts[0].set(x1, y1);
712 pts[1].set(x2, y2);
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000713
reed@google.comb54455e2011-05-16 14:16:04 +0000714 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000715}
716
717void SkPath::rQuadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000718 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000719 SkPoint pt;
720 this->getLastPt(&pt);
721 this->quadTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2);
722}
723
reed@google.com277c3f82013-05-31 15:17:50 +0000724void SkPath::conicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
725 SkScalar w) {
726 // check for <= 0 or NaN with this test
727 if (!(w > 0)) {
728 this->lineTo(x2, y2);
729 } else if (!SkScalarIsFinite(w)) {
730 this->lineTo(x1, y1);
731 this->lineTo(x2, y2);
732 } else if (SK_Scalar1 == w) {
733 this->quadTo(x1, y1, x2, y2);
734 } else {
735 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000736
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000737 this->injectMoveToIfNeeded();
738
reed@google.com277c3f82013-05-31 15:17:50 +0000739 SkPathRef::Editor ed(&fPathRef);
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000740 SkPoint* pts = ed.growForVerb(kConic_Verb, w);
reed@google.com277c3f82013-05-31 15:17:50 +0000741 pts[0].set(x1, y1);
742 pts[1].set(x2, y2);
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000743
reed@google.com277c3f82013-05-31 15:17:50 +0000744 DIRTY_AFTER_EDIT;
745 }
746}
747
748void SkPath::rConicTo(SkScalar dx1, SkScalar dy1, SkScalar dx2, SkScalar dy2,
749 SkScalar w) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000750 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@google.com277c3f82013-05-31 15:17:50 +0000751 SkPoint pt;
752 this->getLastPt(&pt);
753 this->conicTo(pt.fX + dx1, pt.fY + dy1, pt.fX + dx2, pt.fY + dy2, w);
754}
755
reed@android.com8a1c16f2008-12-17 15:59:43 +0000756void SkPath::cubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
757 SkScalar x3, SkScalar y3) {
758 SkDEBUGCODE(this->validate();)
759
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000760 this->injectMoveToIfNeeded();
761
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000762 SkPathRef::Editor ed(&fPathRef);
763 SkPoint* pts = ed.growForVerb(kCubic_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000764 pts[0].set(x1, y1);
765 pts[1].set(x2, y2);
766 pts[2].set(x3, y3);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000767
reed@google.comb54455e2011-05-16 14:16:04 +0000768 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000769}
770
771void SkPath::rCubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
772 SkScalar x3, SkScalar y3) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000773 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000774 SkPoint pt;
775 this->getLastPt(&pt);
776 this->cubicTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2,
777 pt.fX + x3, pt.fY + y3);
778}
779
780void SkPath::close() {
781 SkDEBUGCODE(this->validate();)
782
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000783 int count = fPathRef->countVerbs();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000784 if (count > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000785 switch (fPathRef->atVerb(count - 1)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000786 case kLine_Verb:
787 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000788 case kConic_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +0000789 case kCubic_Verb:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000790 case kMove_Verb: {
791 SkPathRef::Editor ed(&fPathRef);
792 ed.growForVerb(kClose_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000793 break;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000794 }
reed@google.com277c3f82013-05-31 15:17:50 +0000795 case kClose_Verb:
reed@google.comfa2f2a42013-05-30 15:29:48 +0000796 // don't add a close if it's the first verb or a repeat
reed@google.com7950a9e2013-05-30 14:57:55 +0000797 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000798 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000799 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000800 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000801 }
802 }
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000803
804 // signal that we need a moveTo to follow us (unless we're done)
805#if 0
806 if (fLastMoveToIndex >= 0) {
807 fLastMoveToIndex = ~fLastMoveToIndex;
808 }
809#else
810 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
811#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000812}
813
814///////////////////////////////////////////////////////////////////////////////
reed@google.comabf15c12011-01-18 20:35:51 +0000815
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000816static void assert_known_direction(int dir) {
817 SkASSERT(SkPath::kCW_Direction == dir || SkPath::kCCW_Direction == dir);
818}
819
reed@android.com8a1c16f2008-12-17 15:59:43 +0000820void SkPath::addRect(const SkRect& rect, Direction dir) {
821 this->addRect(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, dir);
822}
823
824void SkPath::addRect(SkScalar left, SkScalar top, SkScalar right,
825 SkScalar bottom, Direction dir) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000826 assert_known_direction(dir);
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000827 fDirection = this->hasOnlyMoveTos() ? dir : kUnknown_Direction;
828 SkAutoDisableDirectionCheck addc(this);
829
reed@android.com8a1c16f2008-12-17 15:59:43 +0000830 SkAutoPathBoundsUpdate apbu(this, left, top, right, bottom);
831
832 this->incReserve(5);
833
834 this->moveTo(left, top);
835 if (dir == kCCW_Direction) {
836 this->lineTo(left, bottom);
837 this->lineTo(right, bottom);
838 this->lineTo(right, top);
839 } else {
840 this->lineTo(right, top);
841 this->lineTo(right, bottom);
842 this->lineTo(left, bottom);
843 }
844 this->close();
845}
846
reed@google.com744faba2012-05-29 19:54:52 +0000847void SkPath::addPoly(const SkPoint pts[], int count, bool close) {
848 SkDEBUGCODE(this->validate();)
849 if (count <= 0) {
850 return;
851 }
852
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000853 fLastMoveToIndex = fPathRef->countPoints();
854
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000855 // +close makes room for the extra kClose_Verb
856 SkPathRef::Editor ed(&fPathRef, count+close, count);
857
858 ed.growForVerb(kMove_Verb)->set(pts[0].fX, pts[0].fY);
reed@google.com744faba2012-05-29 19:54:52 +0000859 if (count > 1) {
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000860 SkPoint* p = ed.growForRepeatedVerb(kLine_Verb, count - 1);
861 memcpy(p, &pts[1], (count-1) * sizeof(SkPoint));
reed@google.com744faba2012-05-29 19:54:52 +0000862 }
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000863
reed@google.com744faba2012-05-29 19:54:52 +0000864 if (close) {
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000865 ed.growForVerb(kClose_Verb);
reed@google.com744faba2012-05-29 19:54:52 +0000866 }
867
reed@google.com744faba2012-05-29 19:54:52 +0000868 DIRTY_AFTER_EDIT;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000869 SkDEBUGCODE(this->validate();)
reed@google.com744faba2012-05-29 19:54:52 +0000870}
871
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000872#include "SkGeometry.h"
873
reedf90ea012015-01-29 12:03:58 -0800874static bool arc_is_lone_point(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
875 SkPoint* pt) {
876 if (0 == sweepAngle && (0 == startAngle || SkIntToScalar(360) == startAngle)) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000877 // Chrome uses this path to move into and out of ovals. If not
878 // treated as a special case the moves can distort the oval's
879 // bounding box (and break the circle special case).
reedf90ea012015-01-29 12:03:58 -0800880 pt->set(oval.fRight, oval.centerY());
881 return true;
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000882 } else if (0 == oval.width() && 0 == oval.height()) {
883 // Chrome will sometimes create 0 radius round rects. Having degenerate
884 // quad segments in the path prevents the path from being recognized as
885 // a rect.
886 // TODO: optimizing the case where only one of width or height is zero
887 // should also be considered. This case, however, doesn't seem to be
888 // as common as the single point case.
reedf90ea012015-01-29 12:03:58 -0800889 pt->set(oval.fRight, oval.fTop);
890 return true;
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000891 }
reedf90ea012015-01-29 12:03:58 -0800892 return false;
893}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000894
reedd5d27d92015-02-09 13:54:43 -0800895// Return the unit vectors pointing at the start/stop points for the given start/sweep angles
896//
897static void angles_to_unit_vectors(SkScalar startAngle, SkScalar sweepAngle,
898 SkVector* startV, SkVector* stopV, SkRotationDirection* dir) {
899 startV->fY = SkScalarSinCos(SkDegreesToRadians(startAngle), &startV->fX);
900 stopV->fY = SkScalarSinCos(SkDegreesToRadians(startAngle + sweepAngle), &stopV->fX);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000901
902 /* If the sweep angle is nearly (but less than) 360, then due to precision
reedd5d27d92015-02-09 13:54:43 -0800903 loss in radians-conversion and/or sin/cos, we may end up with coincident
904 vectors, which will fool SkBuildQuadArc into doing nothing (bad) instead
905 of drawing a nearly complete circle (good).
906 e.g. canvas.drawArc(0, 359.99, ...)
907 -vs- canvas.drawArc(0, 359.9, ...)
908 We try to detect this edge case, and tweak the stop vector
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000909 */
reedd5d27d92015-02-09 13:54:43 -0800910 if (*startV == *stopV) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000911 SkScalar sw = SkScalarAbs(sweepAngle);
912 if (sw < SkIntToScalar(360) && sw > SkIntToScalar(359)) {
913 SkScalar stopRad = SkDegreesToRadians(startAngle + sweepAngle);
914 // make a guess at a tiny angle (in radians) to tweak by
915 SkScalar deltaRad = SkScalarCopySign(SK_Scalar1/512, sweepAngle);
916 // not sure how much will be enough, so we use a loop
917 do {
918 stopRad -= deltaRad;
reedd5d27d92015-02-09 13:54:43 -0800919 stopV->fY = SkScalarSinCos(stopRad, &stopV->fX);
920 } while (*startV == *stopV);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000921 }
922 }
reedd5d27d92015-02-09 13:54:43 -0800923 *dir = sweepAngle > 0 ? kCW_SkRotationDirection : kCCW_SkRotationDirection;
924}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000925
reedd5d27d92015-02-09 13:54:43 -0800926#ifdef SK_SUPPORT_LEGACY_ARCTO_QUADS
927static int build_arc_points(const SkRect& oval, const SkVector& start, const SkVector& stop,
928 SkRotationDirection dir, SkPoint pts[kSkBuildQuadArcStorage]) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000929 SkMatrix matrix;
930
931 matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height()));
932 matrix.postTranslate(oval.centerX(), oval.centerY());
933
reedd5d27d92015-02-09 13:54:43 -0800934 return SkBuildQuadArc(start, stop, dir, &matrix, pts);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000935}
reedd5d27d92015-02-09 13:54:43 -0800936#else
937static int build_arc_conics(const SkRect& oval, const SkVector& start, const SkVector& stop,
938 SkRotationDirection dir, SkConic conics[SkConic::kMaxConicsForArc]) {
939 SkMatrix matrix;
940
941 matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height()));
942 matrix.postTranslate(oval.centerX(), oval.centerY());
943
944 return SkConic::BuildUnitArc(start, stop, dir, &matrix, conics);
945}
946#endif
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000947
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +0000948void SkPath::addRoundRect(const SkRect& rect, const SkScalar radii[],
reed@android.com8a1c16f2008-12-17 15:59:43 +0000949 Direction dir) {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +0000950 SkRRect rrect;
951 rrect.setRectRadii(rect, (const SkVector*) radii);
952 this->addRRect(rrect, dir);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000953}
954
reed1b28a3a2014-12-17 14:42:09 -0800955#ifdef SK_SUPPORT_LEGACY_ADDRRECT
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +0000956/* The inline clockwise and counterclockwise round rect quad approximations
957 make it easier to see the symmetry patterns used by add corner quads.
958Clockwise corner value
959 path->lineTo(rect.fLeft, rect.fTop + ry); 0 upper left
960 path->quadTo(rect.fLeft, rect.fTop + offPtY,
961 rect.fLeft + midPtX, rect.fTop + midPtY);
962 path->quadTo(rect.fLeft + offPtX, rect.fTop,
963 rect.fLeft + rx, rect.fTop);
964
965 path->lineTo(rect.fRight - rx, rect.fTop); 1 upper right
966 path->quadTo(rect.fRight - offPtX, rect.fTop,
967 rect.fRight - midPtX, rect.fTop + midPtY);
968 path->quadTo(rect.fRight, rect.fTop + offPtY,
969 rect.fRight, rect.fTop + ry);
970
971 path->lineTo(rect.fRight, rect.fBottom - ry); 2 lower right
972 path->quadTo(rect.fRight, rect.fBottom - offPtY,
973 rect.fRight - midPtX, rect.fBottom - midPtY);
974 path->quadTo(rect.fRight - offPtX, rect.fBottom,
975 rect.fRight - rx, rect.fBottom);
976
977 path->lineTo(rect.fLeft + rx, rect.fBottom); 3 lower left
978 path->quadTo(rect.fLeft + offPtX, rect.fBottom,
979 rect.fLeft + midPtX, rect.fBottom - midPtY);
980 path->quadTo(rect.fLeft, rect.fBottom - offPtY,
981 rect.fLeft, rect.fBottom - ry);
982
983Counterclockwise
984 path->lineTo(rect.fLeft, rect.fBottom - ry); 3 lower left
985 path->quadTo(rect.fLeft, rect.fBottom - offPtY,
986 rect.fLeft + midPtX, rect.fBottom - midPtY);
987 path->quadTo(rect.fLeft + offPtX, rect.fBottom,
988 rect.fLeft + rx, rect.fBottom);
989
990 path->lineTo(rect.fRight - rx, rect.fBottom); 2 lower right
991 path->quadTo(rect.fRight - offPtX, rect.fBottom,
992 rect.fRight - midPtX, rect.fBottom - midPtY);
993 path->quadTo(rect.fRight, rect.fBottom - offPtY,
994 rect.fRight, rect.fBottom - ry);
995
996 path->lineTo(rect.fRight, rect.fTop + ry); 1 upper right
997 path->quadTo(rect.fRight, rect.fTop + offPtY,
998 rect.fRight - midPtX, rect.fTop + midPtY);
999 path->quadTo(rect.fRight - offPtX, rect.fTop,
1000 rect.fRight - rx, rect.fTop);
1001
1002 path->lineTo(rect.fLeft + rx, rect.fTop); 0 upper left
1003 path->quadTo(rect.fLeft + offPtX, rect.fTop,
1004 rect.fLeft + midPtX, rect.fTop + midPtY);
1005 path->quadTo(rect.fLeft, rect.fTop + offPtY,
1006 rect.fLeft, rect.fTop + ry);
1007*/
1008static void add_corner_quads(SkPath* path, const SkRRect& rrect,
1009 SkRRect::Corner corner, SkPath::Direction dir) {
1010 const SkRect& rect = rrect.rect();
1011 const SkVector& radii = rrect.radii(corner);
1012 SkScalar rx = radii.fX;
1013 SkScalar ry = radii.fY;
1014 // The mid point of the quadratic arc approximation is half way between the two
1015 // control points.
caryclark@google.com2e1b99e2013-11-08 18:05:02 +00001016 const SkScalar mid = 1 - (SK_Scalar1 + SK_ScalarTanPIOver8) / 2;
1017 SkScalar midPtX = rx * mid;
1018 SkScalar midPtY = ry * mid;
1019 const SkScalar control = 1 - SK_ScalarTanPIOver8;
1020 SkScalar offPtX = rx * control;
1021 SkScalar offPtY = ry * control;
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001022 static const int kCornerPts = 5;
1023 SkScalar xOff[kCornerPts];
1024 SkScalar yOff[kCornerPts];
1025
1026 if ((corner & 1) == (dir == SkPath::kCCW_Direction)) { // corners always alternate direction
1027 SkASSERT(dir == SkPath::kCCW_Direction
1028 ? corner == SkRRect::kLowerLeft_Corner || corner == SkRRect::kUpperRight_Corner
1029 : corner == SkRRect::kUpperLeft_Corner || corner == SkRRect::kLowerRight_Corner);
1030 xOff[0] = xOff[1] = 0;
1031 xOff[2] = midPtX;
1032 xOff[3] = offPtX;
1033 xOff[4] = rx;
1034 yOff[0] = ry;
1035 yOff[1] = offPtY;
1036 yOff[2] = midPtY;
1037 yOff[3] = yOff[4] = 0;
1038 } else {
1039 xOff[0] = rx;
1040 xOff[1] = offPtX;
1041 xOff[2] = midPtX;
1042 xOff[3] = xOff[4] = 0;
1043 yOff[0] = yOff[1] = 0;
1044 yOff[2] = midPtY;
1045 yOff[3] = offPtY;
1046 yOff[4] = ry;
1047 }
1048 if ((corner - 1) & 2) {
1049 SkASSERT(corner == SkRRect::kLowerLeft_Corner || corner == SkRRect::kUpperLeft_Corner);
1050 for (int i = 0; i < kCornerPts; ++i) {
1051 xOff[i] = rect.fLeft + xOff[i];
1052 }
1053 } else {
1054 SkASSERT(corner == SkRRect::kLowerRight_Corner || corner == SkRRect::kUpperRight_Corner);
1055 for (int i = 0; i < kCornerPts; ++i) {
1056 xOff[i] = rect.fRight - xOff[i];
1057 }
1058 }
1059 if (corner < SkRRect::kLowerRight_Corner) {
1060 for (int i = 0; i < kCornerPts; ++i) {
1061 yOff[i] = rect.fTop + yOff[i];
1062 }
1063 } else {
1064 for (int i = 0; i < kCornerPts; ++i) {
1065 yOff[i] = rect.fBottom - yOff[i];
1066 }
1067 }
1068
1069 SkPoint lastPt;
1070 SkAssertResult(path->getLastPt(&lastPt));
1071 if (lastPt.fX != xOff[0] || lastPt.fY != yOff[0]) {
1072 path->lineTo(xOff[0], yOff[0]);
1073 }
1074 if (rx || ry) {
1075 path->quadTo(xOff[1], yOff[1], xOff[2], yOff[2]);
1076 path->quadTo(xOff[3], yOff[3], xOff[4], yOff[4]);
1077 } else {
1078 path->lineTo(xOff[2], yOff[2]);
1079 path->lineTo(xOff[4], yOff[4]);
1080 }
1081}
reed1b28a3a2014-12-17 14:42:09 -08001082#endif
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001083
reed@google.com4ed0fb72012-12-12 20:48:18 +00001084void SkPath::addRRect(const SkRRect& rrect, Direction dir) {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001085 assert_known_direction(dir);
1086
1087 if (rrect.isEmpty()) {
1088 return;
1089 }
1090
reed@google.com4ed0fb72012-12-12 20:48:18 +00001091 const SkRect& bounds = rrect.getBounds();
1092
1093 if (rrect.isRect()) {
1094 this->addRect(bounds, dir);
1095 } else if (rrect.isOval()) {
1096 this->addOval(bounds, dir);
reed@google.com4ed0fb72012-12-12 20:48:18 +00001097 } else {
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001098 fDirection = this->hasOnlyMoveTos() ? dir : kUnknown_Direction;
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001099
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001100 SkAutoPathBoundsUpdate apbu(this, bounds);
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001101 SkAutoDisableDirectionCheck addc(this);
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001102
reed1b28a3a2014-12-17 14:42:09 -08001103#ifdef SK_SUPPORT_LEGACY_ADDRRECT
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001104 this->incReserve(21);
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001105 if (kCW_Direction == dir) {
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001106 this->moveTo(bounds.fLeft,
1107 bounds.fBottom - rrect.fRadii[SkRRect::kLowerLeft_Corner].fY);
1108 add_corner_quads(this, rrect, SkRRect::kUpperLeft_Corner, dir);
1109 add_corner_quads(this, rrect, SkRRect::kUpperRight_Corner, dir);
1110 add_corner_quads(this, rrect, SkRRect::kLowerRight_Corner, dir);
1111 add_corner_quads(this, rrect, SkRRect::kLowerLeft_Corner, dir);
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001112 } else {
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001113 this->moveTo(bounds.fLeft,
1114 bounds.fTop + rrect.fRadii[SkRRect::kUpperLeft_Corner].fY);
1115 add_corner_quads(this, rrect, SkRRect::kLowerLeft_Corner, dir);
1116 add_corner_quads(this, rrect, SkRRect::kLowerRight_Corner, dir);
1117 add_corner_quads(this, rrect, SkRRect::kUpperRight_Corner, dir);
1118 add_corner_quads(this, rrect, SkRRect::kUpperLeft_Corner, dir);
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001119 }
reed1b28a3a2014-12-17 14:42:09 -08001120#else
1121 const SkScalar L = bounds.fLeft;
1122 const SkScalar T = bounds.fTop;
1123 const SkScalar R = bounds.fRight;
1124 const SkScalar B = bounds.fBottom;
1125 const SkScalar W = SK_ScalarRoot2Over2;
1126
1127 this->incReserve(13);
1128 if (kCW_Direction == dir) {
1129 this->moveTo(L, B - rrect.fRadii[SkRRect::kLowerLeft_Corner].fY);
1130
1131 this->lineTo(L, T + rrect.fRadii[SkRRect::kUpperLeft_Corner].fY);
1132 this->conicTo(L, T, L + rrect.fRadii[SkRRect::kUpperLeft_Corner].fX, T, W);
1133
1134 this->lineTo(R - rrect.fRadii[SkRRect::kUpperRight_Corner].fX, T);
1135 this->conicTo(R, T, R, T + rrect.fRadii[SkRRect::kUpperRight_Corner].fY, W);
1136
1137 this->lineTo(R, B - rrect.fRadii[SkRRect::kLowerRight_Corner].fY);
1138 this->conicTo(R, B, R - rrect.fRadii[SkRRect::kLowerRight_Corner].fX, B, W);
1139
1140 this->lineTo(L + rrect.fRadii[SkRRect::kLowerLeft_Corner].fX, B);
1141 this->conicTo(L, B, L, B - rrect.fRadii[SkRRect::kLowerLeft_Corner].fY, W);
1142 } else {
1143 this->moveTo(L, T + rrect.fRadii[SkRRect::kUpperLeft_Corner].fY);
1144
1145 this->lineTo(L, B - rrect.fRadii[SkRRect::kLowerLeft_Corner].fY);
1146 this->conicTo(L, B, L + rrect.fRadii[SkRRect::kLowerLeft_Corner].fX, B, W);
1147
1148 this->lineTo(R - rrect.fRadii[SkRRect::kLowerRight_Corner].fX, B);
1149 this->conicTo(R, B, R, B - rrect.fRadii[SkRRect::kLowerRight_Corner].fY, W);
1150
1151 this->lineTo(R, T + rrect.fRadii[SkRRect::kUpperRight_Corner].fY);
1152 this->conicTo(R, T, R - rrect.fRadii[SkRRect::kUpperRight_Corner].fX, T, W);
1153
1154 this->lineTo(L + rrect.fRadii[SkRRect::kUpperLeft_Corner].fX, T);
1155 this->conicTo(L, T, L, T + rrect.fRadii[SkRRect::kUpperLeft_Corner].fY, W);
1156 }
1157#endif
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001158 this->close();
reed@google.com4ed0fb72012-12-12 20:48:18 +00001159 }
reed5bcbe912014-12-15 12:28:33 -08001160 SkDEBUGCODE(fPathRef->validate();)
reed@google.com4ed0fb72012-12-12 20:48:18 +00001161}
1162
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001163bool SkPath::hasOnlyMoveTos() const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001164 int count = fPathRef->countVerbs();
1165 const uint8_t* verbs = const_cast<const SkPathRef*>(fPathRef.get())->verbsMemBegin();
1166 for (int i = 0; i < count; ++i) {
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001167 if (*verbs == kLine_Verb ||
1168 *verbs == kQuad_Verb ||
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001169 *verbs == kConic_Verb ||
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001170 *verbs == kCubic_Verb) {
1171 return false;
1172 }
1173 ++verbs;
1174 }
1175 return true;
1176}
1177
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001178void SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
1179 Direction dir) {
1180 assert_known_direction(dir);
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001181
humper@google.com75e3ca12013-04-08 21:44:11 +00001182 if (rx < 0 || ry < 0) {
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001183 SkErrorInternals::SetError( kInvalidArgument_SkError,
humper@google.com75e3ca12013-04-08 21:44:11 +00001184 "I got %f and %f as radii to SkPath::AddRoundRect, "
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001185 "but negative radii are not allowed.",
humper@google.com75e3ca12013-04-08 21:44:11 +00001186 SkScalarToDouble(rx), SkScalarToDouble(ry) );
1187 return;
1188 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001189
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001190 SkRRect rrect;
1191 rrect.setRectXY(rect, rx, ry);
1192 this->addRRect(rrect, dir);
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001193}
1194
reed@android.com8a1c16f2008-12-17 15:59:43 +00001195void SkPath::addOval(const SkRect& oval, Direction dir) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +00001196 assert_known_direction(dir);
1197
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001198 /* If addOval() is called after previous moveTo(),
1199 this path is still marked as an oval. This is used to
1200 fit into WebKit's calling sequences.
1201 We can't simply check isEmpty() in this case, as additional
1202 moveTo() would mark the path non empty.
1203 */
robertphillips@google.com466310d2013-12-03 16:43:54 +00001204 bool isOval = hasOnlyMoveTos();
1205 if (isOval) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001206 fDirection = dir;
1207 } else {
1208 fDirection = kUnknown_Direction;
1209 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001210
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001211 SkAutoDisableDirectionCheck addc(this);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001212
reed@android.com8a1c16f2008-12-17 15:59:43 +00001213 SkAutoPathBoundsUpdate apbu(this, oval);
1214
reed220f9262014-12-17 08:21:04 -08001215#ifdef SK_SUPPORT_LEGACY_ADDOVAL
reed@android.com8a1c16f2008-12-17 15:59:43 +00001216 SkScalar cx = oval.centerX();
1217 SkScalar cy = oval.centerY();
1218 SkScalar rx = SkScalarHalf(oval.width());
1219 SkScalar ry = SkScalarHalf(oval.height());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001220
reed@android.com8a1c16f2008-12-17 15:59:43 +00001221 SkScalar sx = SkScalarMul(rx, SK_ScalarTanPIOver8);
1222 SkScalar sy = SkScalarMul(ry, SK_ScalarTanPIOver8);
1223 SkScalar mx = SkScalarMul(rx, SK_ScalarRoot2Over2);
1224 SkScalar my = SkScalarMul(ry, SK_ScalarRoot2Over2);
1225
1226 /*
reed220f9262014-12-17 08:21:04 -08001227 To handle imprecision in computing the center and radii, we revert to
1228 the provided bounds when we can (i.e. use oval.fLeft instead of cx-rx)
1229 to ensure that we don't exceed the oval's bounds *ever*, since we want
1230 to use oval for our fast-bounds, rather than have to recompute it.
1231 */
reed@android.com8a1c16f2008-12-17 15:59:43 +00001232 const SkScalar L = oval.fLeft; // cx - rx
1233 const SkScalar T = oval.fTop; // cy - ry
1234 const SkScalar R = oval.fRight; // cx + rx
1235 const SkScalar B = oval.fBottom; // cy + ry
1236
1237 this->incReserve(17); // 8 quads + close
1238 this->moveTo(R, cy);
1239 if (dir == kCCW_Direction) {
1240 this->quadTo( R, cy - sy, cx + mx, cy - my);
1241 this->quadTo(cx + sx, T, cx , T);
1242 this->quadTo(cx - sx, T, cx - mx, cy - my);
1243 this->quadTo( L, cy - sy, L, cy );
1244 this->quadTo( L, cy + sy, cx - mx, cy + my);
1245 this->quadTo(cx - sx, B, cx , B);
1246 this->quadTo(cx + sx, B, cx + mx, cy + my);
1247 this->quadTo( R, cy + sy, R, cy );
1248 } else {
1249 this->quadTo( R, cy + sy, cx + mx, cy + my);
1250 this->quadTo(cx + sx, B, cx , B);
1251 this->quadTo(cx - sx, B, cx - mx, cy + my);
1252 this->quadTo( L, cy + sy, L, cy );
1253 this->quadTo( L, cy - sy, cx - mx, cy - my);
1254 this->quadTo(cx - sx, T, cx , T);
1255 this->quadTo(cx + sx, T, cx + mx, cy - my);
1256 this->quadTo( R, cy - sy, R, cy );
1257 }
reed220f9262014-12-17 08:21:04 -08001258#else
1259 const SkScalar L = oval.fLeft;
1260 const SkScalar T = oval.fTop;
1261 const SkScalar R = oval.fRight;
1262 const SkScalar B = oval.fBottom;
1263 const SkScalar cx = oval.centerX();
1264 const SkScalar cy = oval.centerY();
1265 const SkScalar weight = SK_ScalarRoot2Over2;
1266
1267 this->incReserve(9); // move + 4 conics
1268 this->moveTo(R, cy);
1269 if (dir == kCCW_Direction) {
1270 this->conicTo(R, T, cx, T, weight);
1271 this->conicTo(L, T, L, cy, weight);
1272 this->conicTo(L, B, cx, B, weight);
1273 this->conicTo(R, B, R, cy, weight);
1274 } else {
1275 this->conicTo(R, B, cx, B, weight);
1276 this->conicTo(L, B, L, cy, weight);
1277 this->conicTo(L, T, cx, T, weight);
1278 this->conicTo(R, T, R, cy, weight);
1279 }
1280#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +00001281 this->close();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001282
robertphillips@google.com466310d2013-12-03 16:43:54 +00001283 SkPathRef::Editor ed(&fPathRef);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001284
robertphillips@google.com466310d2013-12-03 16:43:54 +00001285 ed.setIsOval(isOval);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001286}
1287
reed@android.com8a1c16f2008-12-17 15:59:43 +00001288void SkPath::addCircle(SkScalar x, SkScalar y, SkScalar r, Direction dir) {
1289 if (r > 0) {
1290 SkRect rect;
1291 rect.set(x - r, y - r, x + r, y + r);
1292 this->addOval(rect, dir);
1293 }
1294}
1295
reed@android.com8a1c16f2008-12-17 15:59:43 +00001296void SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
1297 bool forceMoveTo) {
1298 if (oval.width() < 0 || oval.height() < 0) {
1299 return;
1300 }
1301
reedf90ea012015-01-29 12:03:58 -08001302 if (fPathRef->countVerbs() == 0) {
1303 forceMoveTo = true;
1304 }
1305
1306 SkPoint lonePt;
1307 if (arc_is_lone_point(oval, startAngle, sweepAngle, &lonePt)) {
1308 forceMoveTo ? this->moveTo(lonePt) : this->lineTo(lonePt);
1309 return;
1310 }
1311
reedd5d27d92015-02-09 13:54:43 -08001312 SkVector startV, stopV;
1313 SkRotationDirection dir;
1314 angles_to_unit_vectors(startAngle, sweepAngle, &startV, &stopV, &dir);
1315
1316#ifdef SK_SUPPORT_LEGACY_ARCTO_QUADS
reed@android.com8a1c16f2008-12-17 15:59:43 +00001317 SkPoint pts[kSkBuildQuadArcStorage];
reedd5d27d92015-02-09 13:54:43 -08001318 int count = build_arc_points(oval, startV, stopV, dir, pts);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001319 SkASSERT((count & 1) == 1);
1320
reed@android.com8a1c16f2008-12-17 15:59:43 +00001321 this->incReserve(count);
1322 forceMoveTo ? this->moveTo(pts[0]) : this->lineTo(pts[0]);
1323 for (int i = 1; i < count; i += 2) {
1324 this->quadTo(pts[i], pts[i+1]);
1325 }
reedd5d27d92015-02-09 13:54:43 -08001326#else
1327 SkConic conics[SkConic::kMaxConicsForArc];
1328 int count = build_arc_conics(oval, startV, stopV, dir, conics);
1329 if (count) {
1330 this->incReserve(count * 2 + 1);
1331 const SkPoint& pt = conics[0].fPts[0];
1332 forceMoveTo ? this->moveTo(pt) : this->lineTo(pt);
1333 for (int i = 0; i < count; ++i) {
1334 this->conicTo(conics[i].fPts[1], conics[i].fPts[2], conics[i].fW);
1335 }
1336 }
1337#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +00001338}
1339
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001340void SkPath::addArc(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001341 if (oval.isEmpty() || 0 == sweepAngle) {
1342 return;
1343 }
1344
1345 const SkScalar kFullCircleAngle = SkIntToScalar(360);
1346
1347 if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
1348 this->addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction);
reedc7789042015-01-29 12:59:11 -08001349 } else {
1350 this->arcTo(oval, startAngle, sweepAngle, true);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001351 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001352}
1353
1354/*
1355 Need to handle the case when the angle is sharp, and our computed end-points
1356 for the arc go behind pt1 and/or p2...
1357*/
reedc7789042015-01-29 12:59:11 -08001358void SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2, SkScalar radius) {
reeda8b326c2014-12-09 11:50:32 -08001359 if (radius == 0) {
1360 this->lineTo(x1, y1);
1361 return;
1362 }
1363
1364 SkVector before, after;
reed@google.comabf15c12011-01-18 20:35:51 +00001365
reed@android.com8a1c16f2008-12-17 15:59:43 +00001366 // need to know our prev pt so we can construct tangent vectors
1367 {
1368 SkPoint start;
1369 this->getLastPt(&start);
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001370 // Handle degenerate cases by adding a line to the first point and
1371 // bailing out.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001372 before.setNormalize(x1 - start.fX, y1 - start.fY);
1373 after.setNormalize(x2 - x1, y2 - y1);
1374 }
reed@google.comabf15c12011-01-18 20:35:51 +00001375
reed@android.com8a1c16f2008-12-17 15:59:43 +00001376 SkScalar cosh = SkPoint::DotProduct(before, after);
1377 SkScalar sinh = SkPoint::CrossProduct(before, after);
1378
1379 if (SkScalarNearlyZero(sinh)) { // angle is too tight
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001380 this->lineTo(x1, y1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001381 return;
1382 }
reed@google.comabf15c12011-01-18 20:35:51 +00001383
reed@android.com8a1c16f2008-12-17 15:59:43 +00001384 SkScalar dist = SkScalarMulDiv(radius, SK_Scalar1 - cosh, sinh);
1385 if (dist < 0) {
1386 dist = -dist;
1387 }
1388
1389 SkScalar xx = x1 - SkScalarMul(dist, before.fX);
1390 SkScalar yy = y1 - SkScalarMul(dist, before.fY);
1391 SkRotationDirection arcDir;
1392
1393 // now turn before/after into normals
1394 if (sinh > 0) {
1395 before.rotateCCW();
1396 after.rotateCCW();
1397 arcDir = kCW_SkRotationDirection;
1398 } else {
1399 before.rotateCW();
1400 after.rotateCW();
1401 arcDir = kCCW_SkRotationDirection;
1402 }
1403
1404 SkMatrix matrix;
1405 SkPoint pts[kSkBuildQuadArcStorage];
reed@google.comabf15c12011-01-18 20:35:51 +00001406
reed@android.com8a1c16f2008-12-17 15:59:43 +00001407 matrix.setScale(radius, radius);
1408 matrix.postTranslate(xx - SkScalarMul(radius, before.fX),
1409 yy - SkScalarMul(radius, before.fY));
reed@google.comabf15c12011-01-18 20:35:51 +00001410
reed@android.com8a1c16f2008-12-17 15:59:43 +00001411 int count = SkBuildQuadArc(before, after, arcDir, &matrix, pts);
reed@google.comabf15c12011-01-18 20:35:51 +00001412
reed@android.com8a1c16f2008-12-17 15:59:43 +00001413 this->incReserve(count);
1414 // [xx,yy] == pts[0]
1415 this->lineTo(xx, yy);
1416 for (int i = 1; i < count; i += 2) {
1417 this->quadTo(pts[i], pts[i+1]);
1418 }
1419}
1420
1421///////////////////////////////////////////////////////////////////////////////
1422
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001423void SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy, AddPathMode mode) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001424 SkMatrix matrix;
1425
1426 matrix.setTranslate(dx, dy);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001427 this->addPath(path, matrix, mode);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001428}
1429
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001430void SkPath::addPath(const SkPath& path, const SkMatrix& matrix, AddPathMode mode) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001431 SkPathRef::Editor(&fPathRef, path.countVerbs(), path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001432
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001433 RawIter iter(path);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001434 SkPoint pts[4];
1435 Verb verb;
1436
1437 SkMatrix::MapPtsProc proc = matrix.getMapPtsProc();
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001438 bool firstVerb = true;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001439 while ((verb = iter.next(pts)) != kDone_Verb) {
1440 switch (verb) {
1441 case kMove_Verb:
1442 proc(matrix, &pts[0], &pts[0], 1);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001443 if (firstVerb && mode == kExtend_AddPathMode && !isEmpty()) {
1444 injectMoveToIfNeeded(); // In case last contour is closed
1445 this->lineTo(pts[0]);
1446 } else {
1447 this->moveTo(pts[0]);
1448 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001449 break;
1450 case kLine_Verb:
1451 proc(matrix, &pts[1], &pts[1], 1);
1452 this->lineTo(pts[1]);
1453 break;
1454 case kQuad_Verb:
1455 proc(matrix, &pts[1], &pts[1], 2);
1456 this->quadTo(pts[1], pts[2]);
1457 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001458 case kConic_Verb:
1459 proc(matrix, &pts[1], &pts[1], 2);
1460 this->conicTo(pts[1], pts[2], iter.conicWeight());
1461 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001462 case kCubic_Verb:
1463 proc(matrix, &pts[1], &pts[1], 3);
1464 this->cubicTo(pts[1], pts[2], pts[3]);
1465 break;
1466 case kClose_Verb:
1467 this->close();
1468 break;
1469 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001470 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001471 }
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001472 firstVerb = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001473 }
1474}
1475
1476///////////////////////////////////////////////////////////////////////////////
1477
reed@google.com277c3f82013-05-31 15:17:50 +00001478static int pts_in_verb(unsigned verb) {
1479 static const uint8_t gPtsInVerb[] = {
1480 1, // kMove
1481 1, // kLine
1482 2, // kQuad
1483 2, // kConic
1484 3, // kCubic
1485 0, // kClose
1486 0 // kDone
1487 };
1488
1489 SkASSERT(verb < SK_ARRAY_COUNT(gPtsInVerb));
1490 return gPtsInVerb[verb];
1491}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001492
reed@android.com8a1c16f2008-12-17 15:59:43 +00001493// ignore the last point of the 1st contour
1494void SkPath::reversePathTo(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001495 int i, vcount = path.fPathRef->countVerbs();
1496 // exit early if the path is empty, or just has a moveTo.
1497 if (vcount < 2) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001498 return;
1499 }
1500
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001501 SkPathRef::Editor(&fPathRef, vcount, path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001502
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001503 const uint8_t* verbs = path.fPathRef->verbs();
1504 const SkPoint* pts = path.fPathRef->points();
reed@google.com277c3f82013-05-31 15:17:50 +00001505 const SkScalar* conicWeights = path.fPathRef->conicWeights();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001506
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001507 SkASSERT(verbs[~0] == kMove_Verb);
1508 for (i = 1; i < vcount; ++i) {
reed@google.com277c3f82013-05-31 15:17:50 +00001509 unsigned v = verbs[~i];
1510 int n = pts_in_verb(v);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001511 if (n == 0) {
1512 break;
1513 }
1514 pts += n;
reed@google.com277c3f82013-05-31 15:17:50 +00001515 conicWeights += (SkPath::kConic_Verb == v);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001516 }
1517
1518 while (--i > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001519 switch (verbs[~i]) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001520 case kLine_Verb:
1521 this->lineTo(pts[-1].fX, pts[-1].fY);
1522 break;
1523 case kQuad_Verb:
1524 this->quadTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY);
1525 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001526 case kConic_Verb:
1527 this->conicTo(pts[-1], pts[-2], *--conicWeights);
1528 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001529 case kCubic_Verb:
1530 this->cubicTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY,
1531 pts[-3].fX, pts[-3].fY);
1532 break;
1533 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001534 SkDEBUGFAIL("bad verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001535 break;
1536 }
reed@google.com277c3f82013-05-31 15:17:50 +00001537 pts -= pts_in_verb(verbs[~i]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001538 }
1539}
1540
reed@google.com63d73742012-01-10 15:33:12 +00001541void SkPath::reverseAddPath(const SkPath& src) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001542 SkPathRef::Editor ed(&fPathRef, src.fPathRef->countPoints(), src.fPathRef->countVerbs());
reed@google.com63d73742012-01-10 15:33:12 +00001543
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001544 const SkPoint* pts = src.fPathRef->pointsEnd();
1545 // we will iterator through src's verbs backwards
1546 const uint8_t* verbs = src.fPathRef->verbsMemBegin(); // points at the last verb
1547 const uint8_t* verbsEnd = src.fPathRef->verbs(); // points just past the first verb
reed@google.com277c3f82013-05-31 15:17:50 +00001548 const SkScalar* conicWeights = src.fPathRef->conicWeightsEnd();
reed@google.com63d73742012-01-10 15:33:12 +00001549
1550 bool needMove = true;
1551 bool needClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001552 while (verbs < verbsEnd) {
1553 uint8_t v = *(verbs++);
reed@google.com277c3f82013-05-31 15:17:50 +00001554 int n = pts_in_verb(v);
reed@google.com63d73742012-01-10 15:33:12 +00001555
1556 if (needMove) {
1557 --pts;
1558 this->moveTo(pts->fX, pts->fY);
1559 needMove = false;
1560 }
1561 pts -= n;
1562 switch (v) {
1563 case kMove_Verb:
1564 if (needClose) {
1565 this->close();
1566 needClose = false;
1567 }
1568 needMove = true;
1569 pts += 1; // so we see the point in "if (needMove)" above
1570 break;
1571 case kLine_Verb:
1572 this->lineTo(pts[0]);
1573 break;
1574 case kQuad_Verb:
1575 this->quadTo(pts[1], pts[0]);
1576 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001577 case kConic_Verb:
1578 this->conicTo(pts[1], pts[0], *--conicWeights);
1579 break;
reed@google.com63d73742012-01-10 15:33:12 +00001580 case kCubic_Verb:
1581 this->cubicTo(pts[2], pts[1], pts[0]);
1582 break;
1583 case kClose_Verb:
1584 needClose = true;
1585 break;
1586 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00001587 SkDEBUGFAIL("unexpected verb");
reed@google.com63d73742012-01-10 15:33:12 +00001588 }
1589 }
1590}
1591
reed@android.com8a1c16f2008-12-17 15:59:43 +00001592///////////////////////////////////////////////////////////////////////////////
1593
1594void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
1595 SkMatrix matrix;
1596
1597 matrix.setTranslate(dx, dy);
1598 this->transform(matrix, dst);
1599}
1600
reed@android.com8a1c16f2008-12-17 15:59:43 +00001601static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
1602 int level = 2) {
1603 if (--level >= 0) {
1604 SkPoint tmp[7];
1605
1606 SkChopCubicAtHalf(pts, tmp);
1607 subdivide_cubic_to(path, &tmp[0], level);
1608 subdivide_cubic_to(path, &tmp[3], level);
1609 } else {
1610 path->cubicTo(pts[1], pts[2], pts[3]);
1611 }
1612}
1613
1614void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
1615 SkDEBUGCODE(this->validate();)
1616 if (dst == NULL) {
1617 dst = (SkPath*)this;
1618 }
1619
tomhudson@google.com8d430182011-06-06 19:11:19 +00001620 if (matrix.hasPerspective()) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001621 SkPath tmp;
1622 tmp.fFillType = fFillType;
1623
1624 SkPath::Iter iter(*this, false);
1625 SkPoint pts[4];
1626 SkPath::Verb verb;
1627
reed@google.com4a3b7142012-05-16 17:16:46 +00001628 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001629 switch (verb) {
1630 case kMove_Verb:
1631 tmp.moveTo(pts[0]);
1632 break;
1633 case kLine_Verb:
1634 tmp.lineTo(pts[1]);
1635 break;
1636 case kQuad_Verb:
reed220f9262014-12-17 08:21:04 -08001637 // promote the quad to a conic
1638 tmp.conicTo(pts[1], pts[2],
1639 SkConic::TransformW(pts, SK_Scalar1, matrix));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001640 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001641 case kConic_Verb:
reed220f9262014-12-17 08:21:04 -08001642 tmp.conicTo(pts[1], pts[2],
1643 SkConic::TransformW(pts, iter.conicWeight(), matrix));
reed@google.com277c3f82013-05-31 15:17:50 +00001644 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001645 case kCubic_Verb:
1646 subdivide_cubic_to(&tmp, pts);
1647 break;
1648 case kClose_Verb:
1649 tmp.close();
1650 break;
1651 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001652 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001653 break;
1654 }
1655 }
1656
1657 dst->swap(tmp);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001658 SkPathRef::Editor ed(&dst->fPathRef);
1659 matrix.mapPoints(ed.points(), ed.pathRef()->countPoints());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001660 dst->fDirection = kUnknown_Direction;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001661 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001662 SkPathRef::CreateTransformedCopy(&dst->fPathRef, *fPathRef.get(), matrix);
1663
reed@android.com8a1c16f2008-12-17 15:59:43 +00001664 if (this != dst) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001665 dst->fFillType = fFillType;
reed@google.com2a6f8ab2011-10-25 18:41:23 +00001666 dst->fConvexity = fConvexity;
jvanverthb3eb6872014-10-24 07:12:51 -07001667 dst->fIsVolatile = fIsVolatile;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001668 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001669
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001670 if (kUnknown_Direction == fDirection) {
1671 dst->fDirection = kUnknown_Direction;
1672 } else {
1673 SkScalar det2x2 =
1674 SkScalarMul(matrix.get(SkMatrix::kMScaleX), matrix.get(SkMatrix::kMScaleY)) -
1675 SkScalarMul(matrix.get(SkMatrix::kMSkewX), matrix.get(SkMatrix::kMSkewY));
1676 if (det2x2 < 0) {
1677 dst->fDirection = SkPath::OppositeDirection(static_cast<Direction>(fDirection));
1678 } else if (det2x2 > 0) {
1679 dst->fDirection = fDirection;
1680 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001681 dst->fConvexity = kUnknown_Convexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001682 dst->fDirection = kUnknown_Direction;
1683 }
1684 }
1685
reed@android.com8a1c16f2008-12-17 15:59:43 +00001686 SkDEBUGCODE(dst->validate();)
1687 }
1688}
1689
reed@android.com8a1c16f2008-12-17 15:59:43 +00001690///////////////////////////////////////////////////////////////////////////////
1691///////////////////////////////////////////////////////////////////////////////
1692
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001693enum SegmentState {
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001694 kEmptyContour_SegmentState, // The current contour is empty. We may be
1695 // starting processing or we may have just
1696 // closed a contour.
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001697 kAfterMove_SegmentState, // We have seen a move, but nothing else.
1698 kAfterPrimitive_SegmentState // We have seen a primitive but not yet
1699 // closed the path. Also the initial state.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001700};
1701
1702SkPath::Iter::Iter() {
1703#ifdef SK_DEBUG
1704 fPts = NULL;
reed@google.com277c3f82013-05-31 15:17:50 +00001705 fConicWeights = NULL;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001706 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001707 fForceClose = fCloseLine = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001708 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001709#endif
1710 // need to init enough to make next() harmlessly return kDone_Verb
1711 fVerbs = NULL;
1712 fVerbStop = NULL;
1713 fNeedClose = false;
1714}
1715
1716SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
1717 this->setPath(path, forceClose);
1718}
1719
1720void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001721 fPts = path.fPathRef->points();
1722 fVerbs = path.fPathRef->verbs();
1723 fVerbStop = path.fPathRef->verbsMemBegin();
reed@google.com277c3f82013-05-31 15:17:50 +00001724 fConicWeights = path.fPathRef->conicWeights() - 1; // begin one behind
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001725 fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org72785c42011-12-29 21:03:28 +00001726 fMoveTo.fX = fMoveTo.fY = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001727 fForceClose = SkToU8(forceClose);
1728 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001729 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001730}
1731
1732bool SkPath::Iter::isClosedContour() const {
1733 if (fVerbs == NULL || fVerbs == fVerbStop) {
1734 return false;
1735 }
1736 if (fForceClose) {
1737 return true;
1738 }
1739
1740 const uint8_t* verbs = fVerbs;
1741 const uint8_t* stop = fVerbStop;
reed@google.comabf15c12011-01-18 20:35:51 +00001742
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001743 if (kMove_Verb == *(verbs - 1)) {
1744 verbs -= 1; // skip the initial moveto
reed@android.com8a1c16f2008-12-17 15:59:43 +00001745 }
1746
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001747 while (verbs > stop) {
1748 // verbs points one beyond the current verb, decrement first.
1749 unsigned v = *(--verbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001750 if (kMove_Verb == v) {
1751 break;
1752 }
1753 if (kClose_Verb == v) {
1754 return true;
1755 }
1756 }
1757 return false;
1758}
1759
1760SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001761 SkASSERT(pts);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001762 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001763 // A special case: if both points are NaN, SkPoint::operation== returns
1764 // false, but the iterator expects that they are treated as the same.
1765 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001766 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1767 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001768 return kClose_Verb;
1769 }
1770
reed@google.com9e25dbf2012-05-15 17:05:38 +00001771 pts[0] = fLastPt;
1772 pts[1] = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001773 fLastPt = fMoveTo;
1774 fCloseLine = true;
1775 return kLine_Verb;
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +00001776 } else {
1777 pts[0] = fMoveTo;
1778 return kClose_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001779 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001780}
1781
reed@google.com9e25dbf2012-05-15 17:05:38 +00001782const SkPoint& SkPath::Iter::cons_moveTo() {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001783 if (fSegmentState == kAfterMove_SegmentState) {
1784 // Set the first return pt to the move pt
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001785 fSegmentState = kAfterPrimitive_SegmentState;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001786 return fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001787 } else {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001788 SkASSERT(fSegmentState == kAfterPrimitive_SegmentState);
1789 // Set the first return pt to the last pt of the previous primitive.
reed@google.com9e25dbf2012-05-15 17:05:38 +00001790 return fPts[-1];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001791 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001792}
1793
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001794void SkPath::Iter::consumeDegenerateSegments() {
1795 // We need to step over anything that will not move the current draw point
1796 // forward before the next move is seen
1797 const uint8_t* lastMoveVerb = 0;
1798 const SkPoint* lastMovePt = 0;
1799 SkPoint lastPt = fLastPt;
1800 while (fVerbs != fVerbStop) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001801 unsigned verb = *(fVerbs - 1); // fVerbs is one beyond the current verb
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001802 switch (verb) {
1803 case kMove_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001804 // Keep a record of this most recent move
1805 lastMoveVerb = fVerbs;
1806 lastMovePt = fPts;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001807 lastPt = fPts[0];
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001808 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001809 fPts++;
1810 break;
1811
1812 case kClose_Verb:
schenney@chromium.org7e963602012-06-13 17:05:43 +00001813 // A close when we are in a segment is always valid except when it
1814 // follows a move which follows a segment.
1815 if (fSegmentState == kAfterPrimitive_SegmentState && !lastMoveVerb) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001816 return;
1817 }
1818 // A close at any other time must be ignored
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001819 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001820 break;
1821
1822 case kLine_Verb:
1823 if (!IsLineDegenerate(lastPt, fPts[0])) {
1824 if (lastMoveVerb) {
1825 fVerbs = lastMoveVerb;
1826 fPts = lastMovePt;
1827 return;
1828 }
1829 return;
1830 }
1831 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001832 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001833 fPts++;
1834 break;
1835
reed@google.com277c3f82013-05-31 15:17:50 +00001836 case kConic_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001837 case kQuad_Verb:
1838 if (!IsQuadDegenerate(lastPt, fPts[0], fPts[1])) {
1839 if (lastMoveVerb) {
1840 fVerbs = lastMoveVerb;
1841 fPts = lastMovePt;
1842 return;
1843 }
1844 return;
1845 }
1846 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001847 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001848 fPts += 2;
reed@google.com277c3f82013-05-31 15:17:50 +00001849 fConicWeights += (kConic_Verb == verb);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001850 break;
1851
1852 case kCubic_Verb:
1853 if (!IsCubicDegenerate(lastPt, fPts[0], fPts[1], fPts[2])) {
1854 if (lastMoveVerb) {
1855 fVerbs = lastMoveVerb;
1856 fPts = lastMovePt;
1857 return;
1858 }
1859 return;
1860 }
1861 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001862 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001863 fPts += 3;
1864 break;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001865
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001866 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001867 SkDEBUGFAIL("Should never see kDone_Verb");
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001868 }
1869 }
1870}
1871
reed@google.com4a3b7142012-05-16 17:16:46 +00001872SkPath::Verb SkPath::Iter::doNext(SkPoint ptsParam[4]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001873 SkASSERT(ptsParam);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001874
reed@android.com8a1c16f2008-12-17 15:59:43 +00001875 if (fVerbs == fVerbStop) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001876 // Close the curve if requested and if there is some curve to close
1877 if (fNeedClose && fSegmentState == kAfterPrimitive_SegmentState) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001878 if (kLine_Verb == this->autoClose(ptsParam)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001879 return kLine_Verb;
1880 }
1881 fNeedClose = false;
1882 return kClose_Verb;
1883 }
1884 return kDone_Verb;
1885 }
1886
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001887 // fVerbs is one beyond the current verb, decrement first
1888 unsigned verb = *(--fVerbs);
reed@google.com9e25dbf2012-05-15 17:05:38 +00001889 const SkPoint* SK_RESTRICT srcPts = fPts;
1890 SkPoint* SK_RESTRICT pts = ptsParam;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001891
1892 switch (verb) {
1893 case kMove_Verb:
1894 if (fNeedClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001895 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001896 verb = this->autoClose(pts);
1897 if (verb == kClose_Verb) {
1898 fNeedClose = false;
1899 }
1900 return (Verb)verb;
1901 }
1902 if (fVerbs == fVerbStop) { // might be a trailing moveto
1903 return kDone_Verb;
1904 }
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001905 fMoveTo = *srcPts;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001906 pts[0] = *srcPts;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001907 srcPts += 1;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001908 fSegmentState = kAfterMove_SegmentState;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001909 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001910 fNeedClose = fForceClose;
1911 break;
1912 case kLine_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001913 pts[0] = this->cons_moveTo();
1914 pts[1] = srcPts[0];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001915 fLastPt = srcPts[0];
1916 fCloseLine = false;
1917 srcPts += 1;
1918 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001919 case kConic_Verb:
1920 fConicWeights += 1;
1921 // fall-through
reed@android.com8a1c16f2008-12-17 15:59:43 +00001922 case kQuad_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001923 pts[0] = this->cons_moveTo();
1924 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001925 fLastPt = srcPts[1];
1926 srcPts += 2;
1927 break;
1928 case kCubic_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001929 pts[0] = this->cons_moveTo();
1930 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001931 fLastPt = srcPts[2];
1932 srcPts += 3;
1933 break;
1934 case kClose_Verb:
1935 verb = this->autoClose(pts);
1936 if (verb == kLine_Verb) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001937 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001938 } else {
1939 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001940 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001941 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001942 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001943 break;
1944 }
1945 fPts = srcPts;
1946 return (Verb)verb;
1947}
1948
1949///////////////////////////////////////////////////////////////////////////////
1950
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001951SkPath::RawIter::RawIter() {
1952#ifdef SK_DEBUG
1953 fPts = NULL;
reed@google.com277c3f82013-05-31 15:17:50 +00001954 fConicWeights = NULL;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001955 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
1956#endif
1957 // need to init enough to make next() harmlessly return kDone_Verb
1958 fVerbs = NULL;
1959 fVerbStop = NULL;
1960}
1961
1962SkPath::RawIter::RawIter(const SkPath& path) {
1963 this->setPath(path);
1964}
1965
1966void SkPath::RawIter::setPath(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001967 fPts = path.fPathRef->points();
1968 fVerbs = path.fPathRef->verbs();
1969 fVerbStop = path.fPathRef->verbsMemBegin();
reed@google.com277c3f82013-05-31 15:17:50 +00001970 fConicWeights = path.fPathRef->conicWeights() - 1; // begin one behind
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001971 fMoveTo.fX = fMoveTo.fY = 0;
1972 fLastPt.fX = fLastPt.fY = 0;
1973}
1974
1975SkPath::Verb SkPath::RawIter::next(SkPoint pts[4]) {
bsalomon49f085d2014-09-05 13:34:00 -07001976 SkASSERT(pts);
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001977 if (fVerbs == fVerbStop) {
1978 return kDone_Verb;
1979 }
1980
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001981 // fVerbs points one beyond next verb so decrement first.
1982 unsigned verb = *(--fVerbs);
1983 const SkPoint* srcPts = fPts;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001984
1985 switch (verb) {
1986 case kMove_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001987 pts[0] = *srcPts;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001988 fMoveTo = srcPts[0];
1989 fLastPt = fMoveTo;
1990 srcPts += 1;
1991 break;
1992 case kLine_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001993 pts[0] = fLastPt;
1994 pts[1] = srcPts[0];
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001995 fLastPt = srcPts[0];
1996 srcPts += 1;
1997 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001998 case kConic_Verb:
1999 fConicWeights += 1;
2000 // fall-through
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002001 case kQuad_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002002 pts[0] = fLastPt;
2003 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002004 fLastPt = srcPts[1];
2005 srcPts += 2;
2006 break;
2007 case kCubic_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002008 pts[0] = fLastPt;
2009 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002010 fLastPt = srcPts[2];
2011 srcPts += 3;
2012 break;
2013 case kClose_Verb:
2014 fLastPt = fMoveTo;
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002015 pts[0] = fMoveTo;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002016 break;
2017 }
2018 fPts = srcPts;
2019 return (Verb)verb;
2020}
2021
2022///////////////////////////////////////////////////////////////////////////////
2023
reed@android.com8a1c16f2008-12-17 15:59:43 +00002024/*
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002025 Format in compressed buffer: [ptCount, verbCount, pts[], verbs[]]
reed@android.com8a1c16f2008-12-17 15:59:43 +00002026*/
2027
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002028size_t SkPath::writeToMemory(void* storage) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002029 SkDEBUGCODE(this->validate();)
2030
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002031 if (NULL == storage) {
robertphillips@google.comca0c8382013-09-26 12:18:23 +00002032 const int byteCount = sizeof(int32_t) + fPathRef->writeSize();
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002033 return SkAlign4(byteCount);
2034 }
2035
2036 SkWBuffer buffer(storage);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002037
robertphillips@google.com466310d2013-12-03 16:43:54 +00002038 int32_t packed = (fConvexity << kConvexity_SerializationShift) |
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002039 (fFillType << kFillType_SerializationShift) |
jvanverthb3eb6872014-10-24 07:12:51 -07002040 (fDirection << kDirection_SerializationShift) |
2041 (fIsVolatile << kIsVolatile_SerializationShift);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002042
robertphillips@google.com2972bb52012-08-07 17:32:51 +00002043 buffer.write32(packed);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002044
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002045 fPathRef->writeToBuffer(&buffer);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002046
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002047 buffer.padToAlign4();
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002048 return buffer.pos();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002049}
2050
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002051size_t SkPath::readFromMemory(const void* storage, size_t length) {
2052 SkRBufferWithSizeCheck buffer(storage, length);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002053
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00002054 int32_t packed;
2055 if (!buffer.readS32(&packed)) {
2056 return 0;
2057 }
2058
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002059 fConvexity = (packed >> kConvexity_SerializationShift) & 0xFF;
2060 fFillType = (packed >> kFillType_SerializationShift) & 0xFF;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002061 fDirection = (packed >> kDirection_SerializationShift) & 0x3;
jvanverthb3eb6872014-10-24 07:12:51 -07002062 fIsVolatile = (packed >> kIsVolatile_SerializationShift) & 0x1;
commit-bot@chromium.orgfed2ab62014-01-23 15:16:05 +00002063 SkPathRef* pathRef = SkPathRef::CreateFromBuffer(&buffer);
reed@google.comabf15c12011-01-18 20:35:51 +00002064
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002065 size_t sizeRead = 0;
2066 if (buffer.isValid()) {
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00002067 fPathRef.reset(pathRef);
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002068 SkDEBUGCODE(this->validate();)
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00002069 buffer.skipToAlign4();
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002070 sizeRead = buffer.pos();
bsalomon49f085d2014-09-05 13:34:00 -07002071 } else if (pathRef) {
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00002072 // If the buffer is not valid, pathRef should be NULL
2073 sk_throw();
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002074 }
2075 return sizeRead;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002076}
2077
2078///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00002079
reede05fed02014-12-15 07:59:53 -08002080#include "SkStringUtils.h"
caryclark66a5d8b2014-06-24 08:30:15 -07002081#include "SkStream.h"
reed@google.com51bbe752013-01-17 22:07:50 +00002082
reed@google.com51bbe752013-01-17 22:07:50 +00002083static void append_params(SkString* str, const char label[], const SkPoint pts[],
reede05fed02014-12-15 07:59:53 -08002084 int count, SkScalarAsStringType strType, SkScalar conicWeight = -1) {
reed@google.com51bbe752013-01-17 22:07:50 +00002085 str->append(label);
2086 str->append("(");
skia.committer@gmail.com15dd3002013-01-18 07:07:28 +00002087
reed@google.com51bbe752013-01-17 22:07:50 +00002088 const SkScalar* values = &pts[0].fX;
2089 count *= 2;
2090
2091 for (int i = 0; i < count; ++i) {
reede05fed02014-12-15 07:59:53 -08002092 SkAppendScalar(str, values[i], strType);
reed@google.com51bbe752013-01-17 22:07:50 +00002093 if (i < count - 1) {
2094 str->append(", ");
2095 }
2096 }
reed@google.com277c3f82013-05-31 15:17:50 +00002097 if (conicWeight >= 0) {
2098 str->append(", ");
reede05fed02014-12-15 07:59:53 -08002099 SkAppendScalar(str, conicWeight, strType);
reed@google.com277c3f82013-05-31 15:17:50 +00002100 }
caryclark08fa28c2014-10-23 13:08:56 -07002101 str->append(");");
reede05fed02014-12-15 07:59:53 -08002102 if (kHex_SkScalarAsStringType == strType) {
caryclark08fa28c2014-10-23 13:08:56 -07002103 str->append(" // ");
2104 for (int i = 0; i < count; ++i) {
reede05fed02014-12-15 07:59:53 -08002105 SkAppendScalarDec(str, values[i]);
caryclark08fa28c2014-10-23 13:08:56 -07002106 if (i < count - 1) {
2107 str->append(", ");
2108 }
2109 }
2110 if (conicWeight >= 0) {
2111 str->append(", ");
reede05fed02014-12-15 07:59:53 -08002112 SkAppendScalarDec(str, conicWeight);
caryclark08fa28c2014-10-23 13:08:56 -07002113 }
2114 }
2115 str->append("\n");
reed@google.com51bbe752013-01-17 22:07:50 +00002116}
2117
caryclarke9562592014-09-15 09:26:09 -07002118void SkPath::dump(SkWStream* wStream, bool forceClose, bool dumpAsHex) const {
reede05fed02014-12-15 07:59:53 -08002119 SkScalarAsStringType asType = dumpAsHex ? kHex_SkScalarAsStringType : kDec_SkScalarAsStringType;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002120 Iter iter(*this, forceClose);
2121 SkPoint pts[4];
2122 Verb verb;
2123
caryclark66a5d8b2014-06-24 08:30:15 -07002124 if (!wStream) {
2125 SkDebugf("path: forceClose=%s\n", forceClose ? "true" : "false");
2126 }
reed@google.com51bbe752013-01-17 22:07:50 +00002127 SkString builder;
2128
reed@google.com4a3b7142012-05-16 17:16:46 +00002129 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002130 switch (verb) {
2131 case kMove_Verb:
reede05fed02014-12-15 07:59:53 -08002132 append_params(&builder, "path.moveTo", &pts[0], 1, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002133 break;
2134 case kLine_Verb:
reede05fed02014-12-15 07:59:53 -08002135 append_params(&builder, "path.lineTo", &pts[1], 1, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002136 break;
2137 case kQuad_Verb:
reede05fed02014-12-15 07:59:53 -08002138 append_params(&builder, "path.quadTo", &pts[1], 2, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002139 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002140 case kConic_Verb:
reede05fed02014-12-15 07:59:53 -08002141 append_params(&builder, "path.conicTo", &pts[1], 2, asType, iter.conicWeight());
reed@google.com277c3f82013-05-31 15:17:50 +00002142 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002143 case kCubic_Verb:
reede05fed02014-12-15 07:59:53 -08002144 append_params(&builder, "path.cubicTo", &pts[1], 3, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002145 break;
2146 case kClose_Verb:
caryclark66a5d8b2014-06-24 08:30:15 -07002147 builder.append("path.close();\n");
reed@android.com8a1c16f2008-12-17 15:59:43 +00002148 break;
2149 default:
2150 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
2151 verb = kDone_Verb; // stop the loop
2152 break;
2153 }
2154 }
caryclark66a5d8b2014-06-24 08:30:15 -07002155 if (wStream) {
2156 wStream->writeText(builder.c_str());
2157 } else {
2158 SkDebugf("%s", builder.c_str());
2159 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002160}
2161
reed@android.come522ca52009-11-23 20:10:41 +00002162void SkPath::dump() const {
caryclarke9562592014-09-15 09:26:09 -07002163 this->dump(NULL, false, false);
2164}
2165
2166void SkPath::dumpHex() const {
2167 this->dump(NULL, false, true);
reed@android.come522ca52009-11-23 20:10:41 +00002168}
2169
2170#ifdef SK_DEBUG
2171void SkPath::validate() const {
2172 SkASSERT(this != NULL);
2173 SkASSERT((fFillType & ~3) == 0);
reed@google.comabf15c12011-01-18 20:35:51 +00002174
djsollen@google.com077348c2012-10-22 20:23:32 +00002175#ifdef SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002176 if (!fBoundsIsDirty) {
2177 SkRect bounds;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002178
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002179 bool isFinite = compute_pt_bounds(&bounds, *fPathRef.get());
robertphillips@google.com5d8d1862012-08-15 14:36:41 +00002180 SkASSERT(SkToBool(fIsFinite) == isFinite);
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002181
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002182 if (fPathRef->countPoints() <= 1) {
reed@android.come522ca52009-11-23 20:10:41 +00002183 // if we're empty, fBounds may be empty but translated, so we can't
2184 // necessarily compare to bounds directly
2185 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
2186 // be [2, 2, 2, 2]
2187 SkASSERT(bounds.isEmpty());
2188 SkASSERT(fBounds.isEmpty());
2189 } else {
reed@google.comeac52bd2011-11-14 18:13:59 +00002190 if (bounds.isEmpty()) {
2191 SkASSERT(fBounds.isEmpty());
2192 } else {
reed@google.com3563c9e2011-11-14 19:34:57 +00002193 if (!fBounds.isEmpty()) {
2194 SkASSERT(fBounds.contains(bounds));
2195 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002196 }
reed@android.come522ca52009-11-23 20:10:41 +00002197 }
2198 }
djsollen@google.com077348c2012-10-22 20:23:32 +00002199#endif // SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002200}
djsollen@google.com077348c2012-10-22 20:23:32 +00002201#endif // SK_DEBUG
reed@android.come522ca52009-11-23 20:10:41 +00002202
reed@google.com04863fa2011-05-15 04:08:24 +00002203///////////////////////////////////////////////////////////////////////////////
2204
reed@google.com0b7b9822011-05-16 12:29:27 +00002205static int sign(SkScalar x) { return x < 0; }
2206#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00002207
robertphillipsc506e302014-09-16 09:43:31 -07002208enum DirChange {
2209 kLeft_DirChange,
2210 kRight_DirChange,
2211 kStraight_DirChange,
2212 kBackwards_DirChange,
2213
2214 kInvalid_DirChange
2215};
2216
2217
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002218static bool almost_equal(SkScalar compA, SkScalar compB) {
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002219 // The error epsilon was empirically derived; worse case round rects
2220 // with a mid point outset by 2x float epsilon in tests had an error
2221 // of 12.
2222 const int epsilon = 16;
2223 if (!SkScalarIsFinite(compA) || !SkScalarIsFinite(compB)) {
2224 return false;
2225 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002226 // no need to check for small numbers because SkPath::Iter has removed degenerate values
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002227 int aBits = SkFloatAs2sCompliment(compA);
2228 int bBits = SkFloatAs2sCompliment(compB);
2229 return aBits < bBits + epsilon && bBits < aBits + epsilon;
reed@google.com04863fa2011-05-15 04:08:24 +00002230}
2231
robertphillipsc506e302014-09-16 09:43:31 -07002232static DirChange direction_change(const SkPoint& lastPt, const SkVector& curPt,
2233 const SkVector& lastVec, const SkVector& curVec) {
2234 SkScalar cross = SkPoint::CrossProduct(lastVec, curVec);
2235
2236 SkScalar smallest = SkTMin(curPt.fX, SkTMin(curPt.fY, SkTMin(lastPt.fX, lastPt.fY)));
2237 SkScalar largest = SkTMax(curPt.fX, SkTMax(curPt.fY, SkTMax(lastPt.fX, lastPt.fY)));
2238 largest = SkTMax(largest, -smallest);
2239
2240 if (!almost_equal(largest, largest + cross)) {
2241 int sign = SkScalarSignAsInt(cross);
2242 if (sign) {
2243 return (1 == sign) ? kRight_DirChange : kLeft_DirChange;
2244 }
2245 }
2246
2247 if (!SkScalarNearlyZero(lastVec.lengthSqd(), SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
2248 !SkScalarNearlyZero(curVec.lengthSqd(), SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
2249 lastVec.dot(curVec) < 0.0f) {
2250 return kBackwards_DirChange;
2251 }
2252
2253 return kStraight_DirChange;
2254}
2255
reed@google.com04863fa2011-05-15 04:08:24 +00002256// only valid for a single contour
2257struct Convexicator {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002258 Convexicator()
2259 : fPtCount(0)
2260 , fConvexity(SkPath::kConvex_Convexity)
caryclarkd3d1a982014-12-08 04:57:38 -08002261 , fDirection(SkPath::kUnknown_Direction)
2262 , fIsFinite(true) {
robertphillipsc506e302014-09-16 09:43:31 -07002263 fExpectedDir = kInvalid_DirChange;
reed@google.com04863fa2011-05-15 04:08:24 +00002264 // warnings
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002265 fLastPt.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002266 fCurrPt.set(0, 0);
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002267 fLastVec.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002268 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00002269
2270 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00002271 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00002272 }
2273
2274 SkPath::Convexity getConvexity() const { return fConvexity; }
2275
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002276 /** The direction returned is only valid if the path is determined convex */
2277 SkPath::Direction getDirection() const { return fDirection; }
2278
reed@google.com04863fa2011-05-15 04:08:24 +00002279 void addPt(const SkPoint& pt) {
caryclarkd3d1a982014-12-08 04:57:38 -08002280 if (SkPath::kConcave_Convexity == fConvexity || !fIsFinite) {
reed@google.com04863fa2011-05-15 04:08:24 +00002281 return;
2282 }
2283
2284 if (0 == fPtCount) {
2285 fCurrPt = pt;
2286 ++fPtCount;
2287 } else {
2288 SkVector vec = pt - fCurrPt;
caryclarkd3d1a982014-12-08 04:57:38 -08002289 SkScalar lengthSqd = vec.lengthSqd();
2290 if (!SkScalarIsFinite(lengthSqd)) {
2291 fIsFinite = false;
2292 } else if (!SkScalarNearlyZero(lengthSqd, SK_ScalarNearlyZero*SK_ScalarNearlyZero)) {
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002293 fLastPt = fCurrPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002294 fCurrPt = pt;
2295 if (++fPtCount == 2) {
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002296 fFirstVec = fLastVec = vec;
reed@google.com04863fa2011-05-15 04:08:24 +00002297 } else {
2298 SkASSERT(fPtCount > 2);
2299 this->addVec(vec);
2300 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002301
reed@google.com85b6e392011-05-15 20:25:17 +00002302 int sx = sign(vec.fX);
2303 int sy = sign(vec.fY);
2304 fDx += (sx != fSx);
2305 fDy += (sy != fSy);
2306 fSx = sx;
2307 fSy = sy;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002308
reed@google.com85b6e392011-05-15 20:25:17 +00002309 if (fDx > 3 || fDy > 3) {
2310 fConvexity = SkPath::kConcave_Convexity;
2311 }
reed@google.com04863fa2011-05-15 04:08:24 +00002312 }
2313 }
2314 }
2315
2316 void close() {
2317 if (fPtCount > 2) {
2318 this->addVec(fFirstVec);
2319 }
2320 }
2321
caryclarkd3d1a982014-12-08 04:57:38 -08002322 bool isFinite() const {
2323 return fIsFinite;
2324 }
2325
reed@google.com04863fa2011-05-15 04:08:24 +00002326private:
2327 void addVec(const SkVector& vec) {
2328 SkASSERT(vec.fX || vec.fY);
robertphillipsc506e302014-09-16 09:43:31 -07002329 DirChange dir = direction_change(fLastPt, fCurrPt, fLastVec, vec);
2330 switch (dir) {
2331 case kLeft_DirChange: // fall through
2332 case kRight_DirChange:
2333 if (kInvalid_DirChange == fExpectedDir) {
2334 fExpectedDir = dir;
2335 fDirection = (kRight_DirChange == dir) ? SkPath::kCW_Direction
2336 : SkPath::kCCW_Direction;
2337 } else if (dir != fExpectedDir) {
2338 fConvexity = SkPath::kConcave_Convexity;
2339 fDirection = SkPath::kUnknown_Direction;
2340 }
2341 fLastVec = vec;
2342 break;
2343 case kStraight_DirChange:
2344 break;
2345 case kBackwards_DirChange:
2346 fLastVec = vec;
2347 break;
2348 case kInvalid_DirChange:
2349 SkFAIL("Use of invalid direction change flag");
2350 break;
reed@google.com04863fa2011-05-15 04:08:24 +00002351 }
2352 }
2353
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002354 SkPoint fLastPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002355 SkPoint fCurrPt;
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002356 // fLastVec does not necessarily start at fLastPt. We only advance it when the cross product
2357 // value with the current vec is deemed to be of a significant value.
2358 SkVector fLastVec, fFirstVec;
reed@google.com04863fa2011-05-15 04:08:24 +00002359 int fPtCount; // non-degenerate points
robertphillipsc506e302014-09-16 09:43:31 -07002360 DirChange fExpectedDir;
reed@google.com04863fa2011-05-15 04:08:24 +00002361 SkPath::Convexity fConvexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002362 SkPath::Direction fDirection;
reed@google.com0b7b9822011-05-16 12:29:27 +00002363 int fDx, fDy, fSx, fSy;
caryclarkd3d1a982014-12-08 04:57:38 -08002364 bool fIsFinite;
reed@google.com04863fa2011-05-15 04:08:24 +00002365};
2366
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002367SkPath::Convexity SkPath::internalGetConvexity() const {
2368 SkASSERT(kUnknown_Convexity == fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002369 SkPoint pts[4];
2370 SkPath::Verb verb;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002371 SkPath::Iter iter(*this, true);
reed@google.com04863fa2011-05-15 04:08:24 +00002372
2373 int contourCount = 0;
2374 int count;
2375 Convexicator state;
2376
caryclarkd3d1a982014-12-08 04:57:38 -08002377 if (!isFinite()) {
2378 return kUnknown_Convexity;
2379 }
reed@google.com04863fa2011-05-15 04:08:24 +00002380 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
2381 switch (verb) {
2382 case kMove_Verb:
2383 if (++contourCount > 1) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002384 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002385 return kConcave_Convexity;
2386 }
2387 pts[1] = pts[0];
2388 count = 1;
2389 break;
2390 case kLine_Verb: count = 1; break;
2391 case kQuad_Verb: count = 2; break;
reed@google.com277c3f82013-05-31 15:17:50 +00002392 case kConic_Verb: count = 2; break;
reed@google.com04863fa2011-05-15 04:08:24 +00002393 case kCubic_Verb: count = 3; break;
2394 case kClose_Verb:
2395 state.close();
2396 count = 0;
2397 break;
2398 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00002399 SkDEBUGFAIL("bad verb");
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002400 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002401 return kConcave_Convexity;
2402 }
2403
2404 for (int i = 1; i <= count; i++) {
2405 state.addPt(pts[i]);
2406 }
2407 // early exit
caryclarkd3d1a982014-12-08 04:57:38 -08002408 if (!state.isFinite()) {
2409 return kUnknown_Convexity;
2410 }
reed@google.com04863fa2011-05-15 04:08:24 +00002411 if (kConcave_Convexity == state.getConvexity()) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002412 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002413 return kConcave_Convexity;
2414 }
2415 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002416 fConvexity = state.getConvexity();
2417 if (kConvex_Convexity == fConvexity && kUnknown_Direction == fDirection) {
2418 fDirection = state.getDirection();
2419 }
2420 return static_cast<Convexity>(fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002421}
reed@google.com69a99432012-01-10 18:00:10 +00002422
2423///////////////////////////////////////////////////////////////////////////////
2424
2425class ContourIter {
2426public:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002427 ContourIter(const SkPathRef& pathRef);
reed@google.com69a99432012-01-10 18:00:10 +00002428
2429 bool done() const { return fDone; }
2430 // if !done() then these may be called
2431 int count() const { return fCurrPtCount; }
2432 const SkPoint* pts() const { return fCurrPt; }
2433 void next();
2434
2435private:
2436 int fCurrPtCount;
2437 const SkPoint* fCurrPt;
2438 const uint8_t* fCurrVerb;
2439 const uint8_t* fStopVerbs;
reed@google.com277c3f82013-05-31 15:17:50 +00002440 const SkScalar* fCurrConicWeight;
reed@google.com69a99432012-01-10 18:00:10 +00002441 bool fDone;
reed@google.comd1ab9322012-01-10 18:40:03 +00002442 SkDEBUGCODE(int fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002443};
2444
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002445ContourIter::ContourIter(const SkPathRef& pathRef) {
2446 fStopVerbs = pathRef.verbsMemBegin();
reed@google.com69a99432012-01-10 18:00:10 +00002447 fDone = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002448 fCurrPt = pathRef.points();
2449 fCurrVerb = pathRef.verbs();
reed@google.com277c3f82013-05-31 15:17:50 +00002450 fCurrConicWeight = pathRef.conicWeights();
reed@google.com69a99432012-01-10 18:00:10 +00002451 fCurrPtCount = 0;
reed@google.comd1ab9322012-01-10 18:40:03 +00002452 SkDEBUGCODE(fContourCounter = 0;)
reed@google.com69a99432012-01-10 18:00:10 +00002453 this->next();
2454}
2455
2456void ContourIter::next() {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002457 if (fCurrVerb <= fStopVerbs) {
reed@google.com69a99432012-01-10 18:00:10 +00002458 fDone = true;
2459 }
2460 if (fDone) {
2461 return;
2462 }
2463
2464 // skip pts of prev contour
2465 fCurrPt += fCurrPtCount;
2466
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002467 SkASSERT(SkPath::kMove_Verb == fCurrVerb[~0]);
reed@google.com69a99432012-01-10 18:00:10 +00002468 int ptCount = 1; // moveTo
2469 const uint8_t* verbs = fCurrVerb;
2470
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002471 for (--verbs; verbs > fStopVerbs; --verbs) {
2472 switch (verbs[~0]) {
reed@google.com69a99432012-01-10 18:00:10 +00002473 case SkPath::kMove_Verb:
reed@google.com69a99432012-01-10 18:00:10 +00002474 goto CONTOUR_END;
2475 case SkPath::kLine_Verb:
2476 ptCount += 1;
2477 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002478 case SkPath::kConic_Verb:
2479 fCurrConicWeight += 1;
2480 // fall-through
reed@google.com69a99432012-01-10 18:00:10 +00002481 case SkPath::kQuad_Verb:
2482 ptCount += 2;
2483 break;
2484 case SkPath::kCubic_Verb:
2485 ptCount += 3;
2486 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002487 case SkPath::kClose_Verb:
2488 break;
2489 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00002490 SkDEBUGFAIL("unexpected verb");
reed@google.com69a99432012-01-10 18:00:10 +00002491 break;
2492 }
2493 }
2494CONTOUR_END:
2495 fCurrPtCount = ptCount;
2496 fCurrVerb = verbs;
reed@google.comd1ab9322012-01-10 18:40:03 +00002497 SkDEBUGCODE(++fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002498}
2499
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002500// returns cross product of (p1 - p0) and (p2 - p0)
reed@google.com69a99432012-01-10 18:00:10 +00002501static SkScalar cross_prod(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002502 SkScalar cross = SkPoint::CrossProduct(p1 - p0, p2 - p0);
2503 // We may get 0 when the above subtracts underflow. We expect this to be
2504 // very rare and lazily promote to double.
2505 if (0 == cross) {
2506 double p0x = SkScalarToDouble(p0.fX);
2507 double p0y = SkScalarToDouble(p0.fY);
2508
2509 double p1x = SkScalarToDouble(p1.fX);
2510 double p1y = SkScalarToDouble(p1.fY);
2511
2512 double p2x = SkScalarToDouble(p2.fX);
2513 double p2y = SkScalarToDouble(p2.fY);
2514
2515 cross = SkDoubleToScalar((p1x - p0x) * (p2y - p0y) -
2516 (p1y - p0y) * (p2x - p0x));
2517
2518 }
2519 return cross;
reed@google.com69a99432012-01-10 18:00:10 +00002520}
2521
reed@google.comc1ea60a2012-01-31 15:15:36 +00002522// Returns the first pt with the maximum Y coordinate
reed@google.com69a99432012-01-10 18:00:10 +00002523static int find_max_y(const SkPoint pts[], int count) {
2524 SkASSERT(count > 0);
2525 SkScalar max = pts[0].fY;
reed@google.comc1ea60a2012-01-31 15:15:36 +00002526 int firstIndex = 0;
reed@google.com69a99432012-01-10 18:00:10 +00002527 for (int i = 1; i < count; ++i) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002528 SkScalar y = pts[i].fY;
2529 if (y > max) {
2530 max = y;
2531 firstIndex = i;
reed@google.com69a99432012-01-10 18:00:10 +00002532 }
2533 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002534 return firstIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002535}
2536
reed@google.comcabaf1d2012-01-11 21:03:05 +00002537static int find_diff_pt(const SkPoint pts[], int index, int n, int inc) {
2538 int i = index;
2539 for (;;) {
2540 i = (i + inc) % n;
2541 if (i == index) { // we wrapped around, so abort
2542 break;
2543 }
2544 if (pts[index] != pts[i]) { // found a different point, success!
2545 break;
2546 }
2547 }
2548 return i;
2549}
2550
reed@google.comc1ea60a2012-01-31 15:15:36 +00002551/**
2552 * Starting at index, and moving forward (incrementing), find the xmin and
2553 * xmax of the contiguous points that have the same Y.
2554 */
2555static int find_min_max_x_at_y(const SkPoint pts[], int index, int n,
2556 int* maxIndexPtr) {
2557 const SkScalar y = pts[index].fY;
2558 SkScalar min = pts[index].fX;
2559 SkScalar max = min;
2560 int minIndex = index;
2561 int maxIndex = index;
2562 for (int i = index + 1; i < n; ++i) {
2563 if (pts[i].fY != y) {
2564 break;
2565 }
2566 SkScalar x = pts[i].fX;
2567 if (x < min) {
2568 min = x;
2569 minIndex = i;
2570 } else if (x > max) {
2571 max = x;
2572 maxIndex = i;
2573 }
2574 }
2575 *maxIndexPtr = maxIndex;
2576 return minIndex;
2577}
2578
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002579static void crossToDir(SkScalar cross, SkPath::Direction* dir) {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002580 *dir = cross > 0 ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002581}
2582
reed@google.comac8543f2012-01-30 20:51:25 +00002583/*
2584 * We loop through all contours, and keep the computed cross-product of the
2585 * contour that contained the global y-max. If we just look at the first
2586 * contour, we may find one that is wound the opposite way (correctly) since
2587 * it is the interior of a hole (e.g. 'o'). Thus we must find the contour
2588 * that is outer most (or at least has the global y-max) before we can consider
2589 * its cross product.
2590 */
reed@google.com69a99432012-01-10 18:00:10 +00002591bool SkPath::cheapComputeDirection(Direction* dir) const {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002592 if (kUnknown_Direction != fDirection) {
2593 *dir = static_cast<Direction>(fDirection);
2594 return true;
2595 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002596
2597 // don't want to pay the cost for computing this if it
2598 // is unknown, so we don't call isConvex()
2599 if (kConvex_Convexity == this->getConvexityOrUnknown()) {
2600 SkASSERT(kUnknown_Direction == fDirection);
2601 *dir = static_cast<Direction>(fDirection);
2602 return false;
2603 }
reed@google.com69a99432012-01-10 18:00:10 +00002604
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002605 ContourIter iter(*fPathRef.get());
reed@google.com69a99432012-01-10 18:00:10 +00002606
reed@google.comac8543f2012-01-30 20:51:25 +00002607 // initialize with our logical y-min
2608 SkScalar ymax = this->getBounds().fTop;
2609 SkScalar ymaxCross = 0;
2610
reed@google.com69a99432012-01-10 18:00:10 +00002611 for (; !iter.done(); iter.next()) {
2612 int n = iter.count();
reed@google.comcabaf1d2012-01-11 21:03:05 +00002613 if (n < 3) {
2614 continue;
2615 }
djsollen@google.come63793a2012-03-21 15:39:03 +00002616
reed@google.comcabaf1d2012-01-11 21:03:05 +00002617 const SkPoint* pts = iter.pts();
reed@google.com69a99432012-01-10 18:00:10 +00002618 SkScalar cross = 0;
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002619 int index = find_max_y(pts, n);
2620 if (pts[index].fY < ymax) {
2621 continue;
2622 }
2623
2624 // If there is more than 1 distinct point at the y-max, we take the
2625 // x-min and x-max of them and just subtract to compute the dir.
2626 if (pts[(index + 1) % n].fY == pts[index].fY) {
2627 int maxIndex;
2628 int minIndex = find_min_max_x_at_y(pts, index, n, &maxIndex);
2629 if (minIndex == maxIndex) {
2630 goto TRY_CROSSPROD;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002631 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002632 SkASSERT(pts[minIndex].fY == pts[index].fY);
2633 SkASSERT(pts[maxIndex].fY == pts[index].fY);
2634 SkASSERT(pts[minIndex].fX <= pts[maxIndex].fX);
2635 // we just subtract the indices, and let that auto-convert to
2636 // SkScalar, since we just want - or + to signal the direction.
2637 cross = minIndex - maxIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002638 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002639 TRY_CROSSPROD:
2640 // Find a next and prev index to use for the cross-product test,
2641 // but we try to find pts that form non-zero vectors from pts[index]
2642 //
2643 // Its possible that we can't find two non-degenerate vectors, so
2644 // we have to guard our search (e.g. all the pts could be in the
2645 // same place).
2646
2647 // we pass n - 1 instead of -1 so we don't foul up % operator by
2648 // passing it a negative LH argument.
2649 int prev = find_diff_pt(pts, index, n, n - 1);
2650 if (prev == index) {
2651 // completely degenerate, skip to next contour
reed@google.comac8543f2012-01-30 20:51:25 +00002652 continue;
2653 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002654 int next = find_diff_pt(pts, index, n, 1);
2655 SkASSERT(next != index);
2656 cross = cross_prod(pts[prev], pts[index], pts[next]);
2657 // if we get a zero and the points are horizontal, then we look at the spread in
2658 // x-direction. We really should continue to walk away from the degeneracy until
2659 // there is a divergence.
2660 if (0 == cross && pts[prev].fY == pts[index].fY && pts[next].fY == pts[index].fY) {
2661 // construct the subtract so we get the correct Direction below
2662 cross = pts[index].fX - pts[next].fX;
reed@google.com188bfcf2012-01-17 18:26:38 +00002663 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002664 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002665
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002666 if (cross) {
2667 // record our best guess so far
2668 ymax = pts[index].fY;
2669 ymaxCross = cross;
reed@google.com69a99432012-01-10 18:00:10 +00002670 }
2671 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002672 if (ymaxCross) {
2673 crossToDir(ymaxCross, dir);
2674 fDirection = *dir;
2675 return true;
2676 } else {
2677 return false;
2678 }
reed@google.comac8543f2012-01-30 20:51:25 +00002679}
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002680
2681///////////////////////////////////////////////////////////////////////////////
2682
2683static SkScalar eval_cubic_coeff(SkScalar A, SkScalar B, SkScalar C,
2684 SkScalar D, SkScalar t) {
2685 return SkScalarMulAdd(SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C), t, D);
2686}
2687
2688static SkScalar eval_cubic_pts(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2689 SkScalar t) {
2690 SkScalar A = c3 + 3*(c1 - c2) - c0;
2691 SkScalar B = 3*(c2 - c1 - c1 + c0);
2692 SkScalar C = 3*(c1 - c0);
2693 SkScalar D = c0;
2694 return eval_cubic_coeff(A, B, C, D, t);
2695}
2696
2697/* Given 4 cubic points (either Xs or Ys), and a target X or Y, compute the
2698 t value such that cubic(t) = target
2699 */
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002700static void chopMonoCubicAt(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002701 SkScalar target, SkScalar* t) {
2702 // SkASSERT(c0 <= c1 && c1 <= c2 && c2 <= c3);
2703 SkASSERT(c0 < target && target < c3);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002704
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002705 SkScalar D = c0 - target;
2706 SkScalar A = c3 + 3*(c1 - c2) - c0;
2707 SkScalar B = 3*(c2 - c1 - c1 + c0);
2708 SkScalar C = 3*(c1 - c0);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002709
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002710 const SkScalar TOLERANCE = SK_Scalar1 / 4096;
2711 SkScalar minT = 0;
2712 SkScalar maxT = SK_Scalar1;
2713 SkScalar mid;
2714 int i;
2715 for (i = 0; i < 16; i++) {
2716 mid = SkScalarAve(minT, maxT);
2717 SkScalar delta = eval_cubic_coeff(A, B, C, D, mid);
2718 if (delta < 0) {
2719 minT = mid;
2720 delta = -delta;
2721 } else {
2722 maxT = mid;
2723 }
2724 if (delta < TOLERANCE) {
2725 break;
2726 }
2727 }
2728 *t = mid;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002729}
2730
2731template <size_t N> static void find_minmax(const SkPoint pts[],
2732 SkScalar* minPtr, SkScalar* maxPtr) {
2733 SkScalar min, max;
2734 min = max = pts[0].fX;
2735 for (size_t i = 1; i < N; ++i) {
2736 min = SkMinScalar(min, pts[i].fX);
2737 max = SkMaxScalar(max, pts[i].fX);
2738 }
2739 *minPtr = min;
2740 *maxPtr = max;
2741}
2742
2743static int winding_mono_cubic(const SkPoint pts[], SkScalar x, SkScalar y) {
2744 SkPoint storage[4];
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002745
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002746 int dir = 1;
2747 if (pts[0].fY > pts[3].fY) {
2748 storage[0] = pts[3];
2749 storage[1] = pts[2];
2750 storage[2] = pts[1];
2751 storage[3] = pts[0];
2752 pts = storage;
2753 dir = -1;
2754 }
2755 if (y < pts[0].fY || y >= pts[3].fY) {
2756 return 0;
2757 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002758
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002759 // quickreject or quickaccept
2760 SkScalar min, max;
2761 find_minmax<4>(pts, &min, &max);
2762 if (x < min) {
2763 return 0;
2764 }
2765 if (x > max) {
2766 return dir;
2767 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002768
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002769 // compute the actual x(t) value
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002770 SkScalar t;
2771 chopMonoCubicAt(pts[0].fY, pts[1].fY, pts[2].fY, pts[3].fY, y, &t);
2772 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 +00002773 return xt < x ? dir : 0;
2774}
2775
2776static int winding_cubic(const SkPoint pts[], SkScalar x, SkScalar y) {
2777 SkPoint dst[10];
2778 int n = SkChopCubicAtYExtrema(pts, dst);
2779 int w = 0;
2780 for (int i = 0; i <= n; ++i) {
2781 w += winding_mono_cubic(&dst[i * 3], x, y);
2782 }
2783 return w;
2784}
2785
2786static int winding_mono_quad(const SkPoint pts[], SkScalar x, SkScalar y) {
2787 SkScalar y0 = pts[0].fY;
2788 SkScalar y2 = pts[2].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002789
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002790 int dir = 1;
2791 if (y0 > y2) {
2792 SkTSwap(y0, y2);
2793 dir = -1;
2794 }
2795 if (y < y0 || y >= y2) {
2796 return 0;
2797 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002798
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002799 // bounds check on X (not required. is it faster?)
2800#if 0
2801 if (pts[0].fX > x && pts[1].fX > x && pts[2].fX > x) {
2802 return 0;
2803 }
2804#endif
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002805
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002806 SkScalar roots[2];
2807 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
2808 2 * (pts[1].fY - pts[0].fY),
2809 pts[0].fY - y,
2810 roots);
2811 SkASSERT(n <= 1);
2812 SkScalar xt;
2813 if (0 == n) {
2814 SkScalar mid = SkScalarAve(y0, y2);
2815 // Need [0] and [2] if dir == 1
2816 // and [2] and [0] if dir == -1
2817 xt = y < mid ? pts[1 - dir].fX : pts[dir - 1].fX;
2818 } else {
2819 SkScalar t = roots[0];
2820 SkScalar C = pts[0].fX;
2821 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
2822 SkScalar B = 2 * (pts[1].fX - C);
2823 xt = SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C);
2824 }
2825 return xt < x ? dir : 0;
2826}
2827
2828static bool is_mono_quad(SkScalar y0, SkScalar y1, SkScalar y2) {
2829 // return SkScalarSignAsInt(y0 - y1) + SkScalarSignAsInt(y1 - y2) != 0;
2830 if (y0 == y1) {
2831 return true;
2832 }
2833 if (y0 < y1) {
2834 return y1 <= y2;
2835 } else {
2836 return y1 >= y2;
2837 }
2838}
2839
2840static int winding_quad(const SkPoint pts[], SkScalar x, SkScalar y) {
2841 SkPoint dst[5];
2842 int n = 0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002843
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002844 if (!is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY)) {
2845 n = SkChopQuadAtYExtrema(pts, dst);
2846 pts = dst;
2847 }
2848 int w = winding_mono_quad(pts, x, y);
2849 if (n > 0) {
2850 w += winding_mono_quad(&pts[2], x, y);
2851 }
2852 return w;
2853}
2854
2855static int winding_line(const SkPoint pts[], SkScalar x, SkScalar y) {
2856 SkScalar x0 = pts[0].fX;
2857 SkScalar y0 = pts[0].fY;
2858 SkScalar x1 = pts[1].fX;
2859 SkScalar y1 = pts[1].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002860
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002861 SkScalar dy = y1 - y0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002862
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002863 int dir = 1;
2864 if (y0 > y1) {
2865 SkTSwap(y0, y1);
2866 dir = -1;
2867 }
2868 if (y < y0 || y >= y1) {
2869 return 0;
2870 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002871
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002872 SkScalar cross = SkScalarMul(x1 - x0, y - pts[0].fY) -
2873 SkScalarMul(dy, x - pts[0].fX);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002874
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002875 if (SkScalarSignAsInt(cross) == dir) {
2876 dir = 0;
2877 }
2878 return dir;
2879}
2880
reed@google.com4db592c2013-10-30 17:39:43 +00002881static bool contains_inclusive(const SkRect& r, SkScalar x, SkScalar y) {
2882 return r.fLeft <= x && x <= r.fRight && r.fTop <= y && y <= r.fBottom;
2883}
2884
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002885bool SkPath::contains(SkScalar x, SkScalar y) const {
2886 bool isInverse = this->isInverseFillType();
2887 if (this->isEmpty()) {
2888 return isInverse;
2889 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002890
reed@google.com4db592c2013-10-30 17:39:43 +00002891 if (!contains_inclusive(this->getBounds(), x, y)) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002892 return isInverse;
2893 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002894
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002895 SkPath::Iter iter(*this, true);
2896 bool done = false;
2897 int w = 0;
2898 do {
2899 SkPoint pts[4];
2900 switch (iter.next(pts, false)) {
2901 case SkPath::kMove_Verb:
2902 case SkPath::kClose_Verb:
2903 break;
2904 case SkPath::kLine_Verb:
2905 w += winding_line(pts, x, y);
2906 break;
2907 case SkPath::kQuad_Verb:
2908 w += winding_quad(pts, x, y);
2909 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002910 case SkPath::kConic_Verb:
2911 SkASSERT(0);
2912 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002913 case SkPath::kCubic_Verb:
2914 w += winding_cubic(pts, x, y);
2915 break;
2916 case SkPath::kDone_Verb:
2917 done = true;
2918 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002919 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002920 } while (!done);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002921
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002922 switch (this->getFillType()) {
2923 case SkPath::kEvenOdd_FillType:
2924 case SkPath::kInverseEvenOdd_FillType:
2925 w &= 1;
2926 break;
reed@google.come9bb6232012-07-11 18:56:10 +00002927 default:
2928 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002929 }
2930 return SkToBool(w);
2931}