blob: de3d297da3fe87d7465b3f169d599f692a6cf37e [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
reed9e779d42015-02-17 11:43:14 -0800937/**
938 * If this returns 0, then the caller should just line-to the singlePt, else it should
939 * ignore singlePt and append the specified number of conics.
940 */
reedd5d27d92015-02-09 13:54:43 -0800941static int build_arc_conics(const SkRect& oval, const SkVector& start, const SkVector& stop,
reed9e779d42015-02-17 11:43:14 -0800942 SkRotationDirection dir, SkConic conics[SkConic::kMaxConicsForArc],
943 SkPoint* singlePt) {
reedd5d27d92015-02-09 13:54:43 -0800944 SkMatrix matrix;
945
946 matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height()));
947 matrix.postTranslate(oval.centerX(), oval.centerY());
948
reed9e779d42015-02-17 11:43:14 -0800949 int count = SkConic::BuildUnitArc(start, stop, dir, &matrix, conics);
950 if (0 == count) {
951 matrix.mapXY(start.x(), start.y(), singlePt);
952 }
953 return count;
reedd5d27d92015-02-09 13:54:43 -0800954}
955#endif
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000956
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +0000957void SkPath::addRoundRect(const SkRect& rect, const SkScalar radii[],
reed@android.com8a1c16f2008-12-17 15:59:43 +0000958 Direction dir) {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +0000959 SkRRect rrect;
960 rrect.setRectRadii(rect, (const SkVector*) radii);
961 this->addRRect(rrect, dir);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000962}
963
reed@google.com4ed0fb72012-12-12 20:48:18 +0000964void SkPath::addRRect(const SkRRect& rrect, Direction dir) {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +0000965 assert_known_direction(dir);
966
967 if (rrect.isEmpty()) {
968 return;
969 }
970
reed@google.com4ed0fb72012-12-12 20:48:18 +0000971 const SkRect& bounds = rrect.getBounds();
972
973 if (rrect.isRect()) {
974 this->addRect(bounds, dir);
975 } else if (rrect.isOval()) {
976 this->addOval(bounds, dir);
reed@google.com4ed0fb72012-12-12 20:48:18 +0000977 } else {
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +0000978 fDirection = this->hasOnlyMoveTos() ? dir : kUnknown_Direction;
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +0000979
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +0000980 SkAutoPathBoundsUpdate apbu(this, bounds);
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +0000981 SkAutoDisableDirectionCheck addc(this);
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +0000982
reed1b28a3a2014-12-17 14:42:09 -0800983 const SkScalar L = bounds.fLeft;
984 const SkScalar T = bounds.fTop;
985 const SkScalar R = bounds.fRight;
986 const SkScalar B = bounds.fBottom;
987 const SkScalar W = SK_ScalarRoot2Over2;
988
989 this->incReserve(13);
990 if (kCW_Direction == dir) {
991 this->moveTo(L, B - rrect.fRadii[SkRRect::kLowerLeft_Corner].fY);
992
993 this->lineTo(L, T + rrect.fRadii[SkRRect::kUpperLeft_Corner].fY);
994 this->conicTo(L, T, L + rrect.fRadii[SkRRect::kUpperLeft_Corner].fX, T, W);
995
996 this->lineTo(R - rrect.fRadii[SkRRect::kUpperRight_Corner].fX, T);
997 this->conicTo(R, T, R, T + rrect.fRadii[SkRRect::kUpperRight_Corner].fY, W);
998
999 this->lineTo(R, B - rrect.fRadii[SkRRect::kLowerRight_Corner].fY);
1000 this->conicTo(R, B, R - rrect.fRadii[SkRRect::kLowerRight_Corner].fX, B, W);
1001
1002 this->lineTo(L + rrect.fRadii[SkRRect::kLowerLeft_Corner].fX, B);
1003 this->conicTo(L, B, L, B - rrect.fRadii[SkRRect::kLowerLeft_Corner].fY, W);
1004 } else {
1005 this->moveTo(L, T + rrect.fRadii[SkRRect::kUpperLeft_Corner].fY);
1006
1007 this->lineTo(L, B - rrect.fRadii[SkRRect::kLowerLeft_Corner].fY);
1008 this->conicTo(L, B, L + rrect.fRadii[SkRRect::kLowerLeft_Corner].fX, B, W);
1009
1010 this->lineTo(R - rrect.fRadii[SkRRect::kLowerRight_Corner].fX, B);
1011 this->conicTo(R, B, R, B - rrect.fRadii[SkRRect::kLowerRight_Corner].fY, W);
1012
1013 this->lineTo(R, T + rrect.fRadii[SkRRect::kUpperRight_Corner].fY);
1014 this->conicTo(R, T, R - rrect.fRadii[SkRRect::kUpperRight_Corner].fX, T, W);
1015
1016 this->lineTo(L + rrect.fRadii[SkRRect::kUpperLeft_Corner].fX, T);
1017 this->conicTo(L, T, L, T + rrect.fRadii[SkRRect::kUpperLeft_Corner].fY, W);
1018 }
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001019 this->close();
reed@google.com4ed0fb72012-12-12 20:48:18 +00001020 }
reed5bcbe912014-12-15 12:28:33 -08001021 SkDEBUGCODE(fPathRef->validate();)
reed@google.com4ed0fb72012-12-12 20:48:18 +00001022}
1023
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001024bool SkPath::hasOnlyMoveTos() const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001025 int count = fPathRef->countVerbs();
1026 const uint8_t* verbs = const_cast<const SkPathRef*>(fPathRef.get())->verbsMemBegin();
1027 for (int i = 0; i < count; ++i) {
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001028 if (*verbs == kLine_Verb ||
1029 *verbs == kQuad_Verb ||
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001030 *verbs == kConic_Verb ||
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001031 *verbs == kCubic_Verb) {
1032 return false;
1033 }
1034 ++verbs;
1035 }
1036 return true;
1037}
1038
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001039void SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
1040 Direction dir) {
1041 assert_known_direction(dir);
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001042
humper@google.com75e3ca12013-04-08 21:44:11 +00001043 if (rx < 0 || ry < 0) {
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001044 SkErrorInternals::SetError( kInvalidArgument_SkError,
humper@google.com75e3ca12013-04-08 21:44:11 +00001045 "I got %f and %f as radii to SkPath::AddRoundRect, "
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001046 "but negative radii are not allowed.",
humper@google.com75e3ca12013-04-08 21:44:11 +00001047 SkScalarToDouble(rx), SkScalarToDouble(ry) );
1048 return;
1049 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001050
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001051 SkRRect rrect;
1052 rrect.setRectXY(rect, rx, ry);
1053 this->addRRect(rrect, dir);
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001054}
1055
reed@android.com8a1c16f2008-12-17 15:59:43 +00001056void SkPath::addOval(const SkRect& oval, Direction dir) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +00001057 assert_known_direction(dir);
1058
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001059 /* If addOval() is called after previous moveTo(),
1060 this path is still marked as an oval. This is used to
1061 fit into WebKit's calling sequences.
1062 We can't simply check isEmpty() in this case, as additional
1063 moveTo() would mark the path non empty.
1064 */
robertphillips@google.com466310d2013-12-03 16:43:54 +00001065 bool isOval = hasOnlyMoveTos();
1066 if (isOval) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001067 fDirection = dir;
1068 } else {
1069 fDirection = kUnknown_Direction;
1070 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001071
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001072 SkAutoDisableDirectionCheck addc(this);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001073
reed@android.com8a1c16f2008-12-17 15:59:43 +00001074 SkAutoPathBoundsUpdate apbu(this, oval);
1075
reed220f9262014-12-17 08:21:04 -08001076#ifdef SK_SUPPORT_LEGACY_ADDOVAL
reed@android.com8a1c16f2008-12-17 15:59:43 +00001077 SkScalar cx = oval.centerX();
1078 SkScalar cy = oval.centerY();
1079 SkScalar rx = SkScalarHalf(oval.width());
1080 SkScalar ry = SkScalarHalf(oval.height());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001081
reed@android.com8a1c16f2008-12-17 15:59:43 +00001082 SkScalar sx = SkScalarMul(rx, SK_ScalarTanPIOver8);
1083 SkScalar sy = SkScalarMul(ry, SK_ScalarTanPIOver8);
1084 SkScalar mx = SkScalarMul(rx, SK_ScalarRoot2Over2);
1085 SkScalar my = SkScalarMul(ry, SK_ScalarRoot2Over2);
1086
1087 /*
reed220f9262014-12-17 08:21:04 -08001088 To handle imprecision in computing the center and radii, we revert to
1089 the provided bounds when we can (i.e. use oval.fLeft instead of cx-rx)
1090 to ensure that we don't exceed the oval's bounds *ever*, since we want
1091 to use oval for our fast-bounds, rather than have to recompute it.
1092 */
reed@android.com8a1c16f2008-12-17 15:59:43 +00001093 const SkScalar L = oval.fLeft; // cx - rx
1094 const SkScalar T = oval.fTop; // cy - ry
1095 const SkScalar R = oval.fRight; // cx + rx
1096 const SkScalar B = oval.fBottom; // cy + ry
1097
1098 this->incReserve(17); // 8 quads + close
1099 this->moveTo(R, cy);
1100 if (dir == kCCW_Direction) {
1101 this->quadTo( R, cy - sy, cx + mx, cy - my);
1102 this->quadTo(cx + sx, T, cx , T);
1103 this->quadTo(cx - sx, T, cx - mx, cy - my);
1104 this->quadTo( L, cy - sy, L, cy );
1105 this->quadTo( L, cy + sy, cx - mx, cy + my);
1106 this->quadTo(cx - sx, B, cx , B);
1107 this->quadTo(cx + sx, B, cx + mx, cy + my);
1108 this->quadTo( R, cy + sy, R, cy );
1109 } else {
1110 this->quadTo( R, cy + sy, cx + mx, cy + my);
1111 this->quadTo(cx + sx, B, cx , B);
1112 this->quadTo(cx - sx, B, cx - mx, cy + my);
1113 this->quadTo( L, cy + sy, L, cy );
1114 this->quadTo( L, cy - sy, cx - mx, cy - my);
1115 this->quadTo(cx - sx, T, cx , T);
1116 this->quadTo(cx + sx, T, cx + mx, cy - my);
1117 this->quadTo( R, cy - sy, R, cy );
1118 }
reed220f9262014-12-17 08:21:04 -08001119#else
1120 const SkScalar L = oval.fLeft;
1121 const SkScalar T = oval.fTop;
1122 const SkScalar R = oval.fRight;
1123 const SkScalar B = oval.fBottom;
1124 const SkScalar cx = oval.centerX();
1125 const SkScalar cy = oval.centerY();
1126 const SkScalar weight = SK_ScalarRoot2Over2;
1127
1128 this->incReserve(9); // move + 4 conics
1129 this->moveTo(R, cy);
1130 if (dir == kCCW_Direction) {
1131 this->conicTo(R, T, cx, T, weight);
1132 this->conicTo(L, T, L, cy, weight);
1133 this->conicTo(L, B, cx, B, weight);
1134 this->conicTo(R, B, R, cy, weight);
1135 } else {
1136 this->conicTo(R, B, cx, B, weight);
1137 this->conicTo(L, B, L, cy, weight);
1138 this->conicTo(L, T, cx, T, weight);
1139 this->conicTo(R, T, R, cy, weight);
1140 }
1141#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +00001142 this->close();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001143
robertphillips@google.com466310d2013-12-03 16:43:54 +00001144 SkPathRef::Editor ed(&fPathRef);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001145
robertphillips@google.com466310d2013-12-03 16:43:54 +00001146 ed.setIsOval(isOval);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001147}
1148
reed@android.com8a1c16f2008-12-17 15:59:43 +00001149void SkPath::addCircle(SkScalar x, SkScalar y, SkScalar r, Direction dir) {
1150 if (r > 0) {
1151 SkRect rect;
1152 rect.set(x - r, y - r, x + r, y + r);
1153 this->addOval(rect, dir);
1154 }
1155}
1156
reed@android.com8a1c16f2008-12-17 15:59:43 +00001157void SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
1158 bool forceMoveTo) {
1159 if (oval.width() < 0 || oval.height() < 0) {
1160 return;
1161 }
1162
reedf90ea012015-01-29 12:03:58 -08001163 if (fPathRef->countVerbs() == 0) {
1164 forceMoveTo = true;
1165 }
1166
1167 SkPoint lonePt;
1168 if (arc_is_lone_point(oval, startAngle, sweepAngle, &lonePt)) {
1169 forceMoveTo ? this->moveTo(lonePt) : this->lineTo(lonePt);
1170 return;
1171 }
1172
reedd5d27d92015-02-09 13:54:43 -08001173 SkVector startV, stopV;
1174 SkRotationDirection dir;
1175 angles_to_unit_vectors(startAngle, sweepAngle, &startV, &stopV, &dir);
1176
1177#ifdef SK_SUPPORT_LEGACY_ARCTO_QUADS
reed@android.com8a1c16f2008-12-17 15:59:43 +00001178 SkPoint pts[kSkBuildQuadArcStorage];
reedd5d27d92015-02-09 13:54:43 -08001179 int count = build_arc_points(oval, startV, stopV, dir, pts);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001180 SkASSERT((count & 1) == 1);
1181
reed@android.com8a1c16f2008-12-17 15:59:43 +00001182 this->incReserve(count);
1183 forceMoveTo ? this->moveTo(pts[0]) : this->lineTo(pts[0]);
1184 for (int i = 1; i < count; i += 2) {
1185 this->quadTo(pts[i], pts[i+1]);
1186 }
reedd5d27d92015-02-09 13:54:43 -08001187#else
reed9e779d42015-02-17 11:43:14 -08001188 SkPoint singlePt;
reedd5d27d92015-02-09 13:54:43 -08001189 SkConic conics[SkConic::kMaxConicsForArc];
reed9e779d42015-02-17 11:43:14 -08001190 int count = build_arc_conics(oval, startV, stopV, dir, conics, &singlePt);
reedd5d27d92015-02-09 13:54:43 -08001191 if (count) {
1192 this->incReserve(count * 2 + 1);
1193 const SkPoint& pt = conics[0].fPts[0];
1194 forceMoveTo ? this->moveTo(pt) : this->lineTo(pt);
1195 for (int i = 0; i < count; ++i) {
1196 this->conicTo(conics[i].fPts[1], conics[i].fPts[2], conics[i].fW);
1197 }
reed9e779d42015-02-17 11:43:14 -08001198 } else {
1199 forceMoveTo ? this->moveTo(singlePt) : this->lineTo(singlePt);
reedd5d27d92015-02-09 13:54:43 -08001200 }
1201#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +00001202}
1203
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001204void SkPath::addArc(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001205 if (oval.isEmpty() || 0 == sweepAngle) {
1206 return;
1207 }
1208
1209 const SkScalar kFullCircleAngle = SkIntToScalar(360);
1210
1211 if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
1212 this->addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction);
reedc7789042015-01-29 12:59:11 -08001213 } else {
1214 this->arcTo(oval, startAngle, sweepAngle, true);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001215 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001216}
1217
1218/*
1219 Need to handle the case when the angle is sharp, and our computed end-points
1220 for the arc go behind pt1 and/or p2...
1221*/
reedc7789042015-01-29 12:59:11 -08001222void SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2, SkScalar radius) {
reeda8b326c2014-12-09 11:50:32 -08001223 if (radius == 0) {
1224 this->lineTo(x1, y1);
1225 return;
1226 }
1227
1228 SkVector before, after;
reed@google.comabf15c12011-01-18 20:35:51 +00001229
reed@android.com8a1c16f2008-12-17 15:59:43 +00001230 // need to know our prev pt so we can construct tangent vectors
1231 {
1232 SkPoint start;
1233 this->getLastPt(&start);
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001234 // Handle degenerate cases by adding a line to the first point and
1235 // bailing out.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001236 before.setNormalize(x1 - start.fX, y1 - start.fY);
1237 after.setNormalize(x2 - x1, y2 - y1);
1238 }
reed@google.comabf15c12011-01-18 20:35:51 +00001239
reed@android.com8a1c16f2008-12-17 15:59:43 +00001240 SkScalar cosh = SkPoint::DotProduct(before, after);
1241 SkScalar sinh = SkPoint::CrossProduct(before, after);
1242
1243 if (SkScalarNearlyZero(sinh)) { // angle is too tight
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001244 this->lineTo(x1, y1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001245 return;
1246 }
reed@google.comabf15c12011-01-18 20:35:51 +00001247
reed@android.com8a1c16f2008-12-17 15:59:43 +00001248 SkScalar dist = SkScalarMulDiv(radius, SK_Scalar1 - cosh, sinh);
1249 if (dist < 0) {
1250 dist = -dist;
1251 }
1252
1253 SkScalar xx = x1 - SkScalarMul(dist, before.fX);
1254 SkScalar yy = y1 - SkScalarMul(dist, before.fY);
1255 SkRotationDirection arcDir;
1256
1257 // now turn before/after into normals
1258 if (sinh > 0) {
1259 before.rotateCCW();
1260 after.rotateCCW();
1261 arcDir = kCW_SkRotationDirection;
1262 } else {
1263 before.rotateCW();
1264 after.rotateCW();
1265 arcDir = kCCW_SkRotationDirection;
1266 }
1267
1268 SkMatrix matrix;
1269 SkPoint pts[kSkBuildQuadArcStorage];
reed@google.comabf15c12011-01-18 20:35:51 +00001270
reed@android.com8a1c16f2008-12-17 15:59:43 +00001271 matrix.setScale(radius, radius);
1272 matrix.postTranslate(xx - SkScalarMul(radius, before.fX),
1273 yy - SkScalarMul(radius, before.fY));
reed@google.comabf15c12011-01-18 20:35:51 +00001274
reed@android.com8a1c16f2008-12-17 15:59:43 +00001275 int count = SkBuildQuadArc(before, after, arcDir, &matrix, pts);
reed@google.comabf15c12011-01-18 20:35:51 +00001276
reed@android.com8a1c16f2008-12-17 15:59:43 +00001277 this->incReserve(count);
1278 // [xx,yy] == pts[0]
1279 this->lineTo(xx, yy);
1280 for (int i = 1; i < count; i += 2) {
1281 this->quadTo(pts[i], pts[i+1]);
1282 }
1283}
1284
1285///////////////////////////////////////////////////////////////////////////////
1286
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001287void SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy, AddPathMode mode) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001288 SkMatrix matrix;
1289
1290 matrix.setTranslate(dx, dy);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001291 this->addPath(path, matrix, mode);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001292}
1293
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001294void SkPath::addPath(const SkPath& path, const SkMatrix& matrix, AddPathMode mode) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001295 SkPathRef::Editor(&fPathRef, path.countVerbs(), path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001296
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001297 RawIter iter(path);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001298 SkPoint pts[4];
1299 Verb verb;
1300
1301 SkMatrix::MapPtsProc proc = matrix.getMapPtsProc();
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001302 bool firstVerb = true;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001303 while ((verb = iter.next(pts)) != kDone_Verb) {
1304 switch (verb) {
1305 case kMove_Verb:
1306 proc(matrix, &pts[0], &pts[0], 1);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001307 if (firstVerb && mode == kExtend_AddPathMode && !isEmpty()) {
1308 injectMoveToIfNeeded(); // In case last contour is closed
1309 this->lineTo(pts[0]);
1310 } else {
1311 this->moveTo(pts[0]);
1312 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001313 break;
1314 case kLine_Verb:
1315 proc(matrix, &pts[1], &pts[1], 1);
1316 this->lineTo(pts[1]);
1317 break;
1318 case kQuad_Verb:
1319 proc(matrix, &pts[1], &pts[1], 2);
1320 this->quadTo(pts[1], pts[2]);
1321 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001322 case kConic_Verb:
1323 proc(matrix, &pts[1], &pts[1], 2);
1324 this->conicTo(pts[1], pts[2], iter.conicWeight());
1325 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001326 case kCubic_Verb:
1327 proc(matrix, &pts[1], &pts[1], 3);
1328 this->cubicTo(pts[1], pts[2], pts[3]);
1329 break;
1330 case kClose_Verb:
1331 this->close();
1332 break;
1333 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001334 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001335 }
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001336 firstVerb = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001337 }
1338}
1339
1340///////////////////////////////////////////////////////////////////////////////
1341
reed@google.com277c3f82013-05-31 15:17:50 +00001342static int pts_in_verb(unsigned verb) {
1343 static const uint8_t gPtsInVerb[] = {
1344 1, // kMove
1345 1, // kLine
1346 2, // kQuad
1347 2, // kConic
1348 3, // kCubic
1349 0, // kClose
1350 0 // kDone
1351 };
1352
1353 SkASSERT(verb < SK_ARRAY_COUNT(gPtsInVerb));
1354 return gPtsInVerb[verb];
1355}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001356
reed@android.com8a1c16f2008-12-17 15:59:43 +00001357// ignore the last point of the 1st contour
1358void SkPath::reversePathTo(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001359 int i, vcount = path.fPathRef->countVerbs();
1360 // exit early if the path is empty, or just has a moveTo.
1361 if (vcount < 2) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001362 return;
1363 }
1364
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001365 SkPathRef::Editor(&fPathRef, vcount, path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001366
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001367 const uint8_t* verbs = path.fPathRef->verbs();
1368 const SkPoint* pts = path.fPathRef->points();
reed@google.com277c3f82013-05-31 15:17:50 +00001369 const SkScalar* conicWeights = path.fPathRef->conicWeights();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001370
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001371 SkASSERT(verbs[~0] == kMove_Verb);
1372 for (i = 1; i < vcount; ++i) {
reed@google.com277c3f82013-05-31 15:17:50 +00001373 unsigned v = verbs[~i];
1374 int n = pts_in_verb(v);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001375 if (n == 0) {
1376 break;
1377 }
1378 pts += n;
reed@google.com277c3f82013-05-31 15:17:50 +00001379 conicWeights += (SkPath::kConic_Verb == v);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001380 }
1381
1382 while (--i > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001383 switch (verbs[~i]) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001384 case kLine_Verb:
1385 this->lineTo(pts[-1].fX, pts[-1].fY);
1386 break;
1387 case kQuad_Verb:
1388 this->quadTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY);
1389 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001390 case kConic_Verb:
1391 this->conicTo(pts[-1], pts[-2], *--conicWeights);
1392 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001393 case kCubic_Verb:
1394 this->cubicTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY,
1395 pts[-3].fX, pts[-3].fY);
1396 break;
1397 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001398 SkDEBUGFAIL("bad verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001399 break;
1400 }
reed@google.com277c3f82013-05-31 15:17:50 +00001401 pts -= pts_in_verb(verbs[~i]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001402 }
1403}
1404
reed@google.com63d73742012-01-10 15:33:12 +00001405void SkPath::reverseAddPath(const SkPath& src) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001406 SkPathRef::Editor ed(&fPathRef, src.fPathRef->countPoints(), src.fPathRef->countVerbs());
reed@google.com63d73742012-01-10 15:33:12 +00001407
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001408 const SkPoint* pts = src.fPathRef->pointsEnd();
1409 // we will iterator through src's verbs backwards
1410 const uint8_t* verbs = src.fPathRef->verbsMemBegin(); // points at the last verb
1411 const uint8_t* verbsEnd = src.fPathRef->verbs(); // points just past the first verb
reed@google.com277c3f82013-05-31 15:17:50 +00001412 const SkScalar* conicWeights = src.fPathRef->conicWeightsEnd();
reed@google.com63d73742012-01-10 15:33:12 +00001413
1414 bool needMove = true;
1415 bool needClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001416 while (verbs < verbsEnd) {
1417 uint8_t v = *(verbs++);
reed@google.com277c3f82013-05-31 15:17:50 +00001418 int n = pts_in_verb(v);
reed@google.com63d73742012-01-10 15:33:12 +00001419
1420 if (needMove) {
1421 --pts;
1422 this->moveTo(pts->fX, pts->fY);
1423 needMove = false;
1424 }
1425 pts -= n;
1426 switch (v) {
1427 case kMove_Verb:
1428 if (needClose) {
1429 this->close();
1430 needClose = false;
1431 }
1432 needMove = true;
1433 pts += 1; // so we see the point in "if (needMove)" above
1434 break;
1435 case kLine_Verb:
1436 this->lineTo(pts[0]);
1437 break;
1438 case kQuad_Verb:
1439 this->quadTo(pts[1], pts[0]);
1440 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001441 case kConic_Verb:
1442 this->conicTo(pts[1], pts[0], *--conicWeights);
1443 break;
reed@google.com63d73742012-01-10 15:33:12 +00001444 case kCubic_Verb:
1445 this->cubicTo(pts[2], pts[1], pts[0]);
1446 break;
1447 case kClose_Verb:
1448 needClose = true;
1449 break;
1450 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00001451 SkDEBUGFAIL("unexpected verb");
reed@google.com63d73742012-01-10 15:33:12 +00001452 }
1453 }
1454}
1455
reed@android.com8a1c16f2008-12-17 15:59:43 +00001456///////////////////////////////////////////////////////////////////////////////
1457
1458void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
1459 SkMatrix matrix;
1460
1461 matrix.setTranslate(dx, dy);
1462 this->transform(matrix, dst);
1463}
1464
reed@android.com8a1c16f2008-12-17 15:59:43 +00001465static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
1466 int level = 2) {
1467 if (--level >= 0) {
1468 SkPoint tmp[7];
1469
1470 SkChopCubicAtHalf(pts, tmp);
1471 subdivide_cubic_to(path, &tmp[0], level);
1472 subdivide_cubic_to(path, &tmp[3], level);
1473 } else {
1474 path->cubicTo(pts[1], pts[2], pts[3]);
1475 }
1476}
1477
1478void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
1479 SkDEBUGCODE(this->validate();)
1480 if (dst == NULL) {
1481 dst = (SkPath*)this;
1482 }
1483
tomhudson@google.com8d430182011-06-06 19:11:19 +00001484 if (matrix.hasPerspective()) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001485 SkPath tmp;
1486 tmp.fFillType = fFillType;
1487
1488 SkPath::Iter iter(*this, false);
1489 SkPoint pts[4];
1490 SkPath::Verb verb;
1491
reed@google.com4a3b7142012-05-16 17:16:46 +00001492 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001493 switch (verb) {
1494 case kMove_Verb:
1495 tmp.moveTo(pts[0]);
1496 break;
1497 case kLine_Verb:
1498 tmp.lineTo(pts[1]);
1499 break;
1500 case kQuad_Verb:
reed220f9262014-12-17 08:21:04 -08001501 // promote the quad to a conic
1502 tmp.conicTo(pts[1], pts[2],
1503 SkConic::TransformW(pts, SK_Scalar1, matrix));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001504 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001505 case kConic_Verb:
reed220f9262014-12-17 08:21:04 -08001506 tmp.conicTo(pts[1], pts[2],
1507 SkConic::TransformW(pts, iter.conicWeight(), matrix));
reed@google.com277c3f82013-05-31 15:17:50 +00001508 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001509 case kCubic_Verb:
1510 subdivide_cubic_to(&tmp, pts);
1511 break;
1512 case kClose_Verb:
1513 tmp.close();
1514 break;
1515 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001516 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001517 break;
1518 }
1519 }
1520
1521 dst->swap(tmp);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001522 SkPathRef::Editor ed(&dst->fPathRef);
1523 matrix.mapPoints(ed.points(), ed.pathRef()->countPoints());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001524 dst->fDirection = kUnknown_Direction;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001525 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001526 SkPathRef::CreateTransformedCopy(&dst->fPathRef, *fPathRef.get(), matrix);
1527
reed@android.com8a1c16f2008-12-17 15:59:43 +00001528 if (this != dst) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001529 dst->fFillType = fFillType;
reed@google.com2a6f8ab2011-10-25 18:41:23 +00001530 dst->fConvexity = fConvexity;
jvanverthb3eb6872014-10-24 07:12:51 -07001531 dst->fIsVolatile = fIsVolatile;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001532 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001533
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001534 if (kUnknown_Direction == fDirection) {
1535 dst->fDirection = kUnknown_Direction;
1536 } else {
1537 SkScalar det2x2 =
1538 SkScalarMul(matrix.get(SkMatrix::kMScaleX), matrix.get(SkMatrix::kMScaleY)) -
1539 SkScalarMul(matrix.get(SkMatrix::kMSkewX), matrix.get(SkMatrix::kMSkewY));
1540 if (det2x2 < 0) {
1541 dst->fDirection = SkPath::OppositeDirection(static_cast<Direction>(fDirection));
1542 } else if (det2x2 > 0) {
1543 dst->fDirection = fDirection;
1544 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001545 dst->fConvexity = kUnknown_Convexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001546 dst->fDirection = kUnknown_Direction;
1547 }
1548 }
1549
reed@android.com8a1c16f2008-12-17 15:59:43 +00001550 SkDEBUGCODE(dst->validate();)
1551 }
1552}
1553
reed@android.com8a1c16f2008-12-17 15:59:43 +00001554///////////////////////////////////////////////////////////////////////////////
1555///////////////////////////////////////////////////////////////////////////////
1556
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001557enum SegmentState {
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001558 kEmptyContour_SegmentState, // The current contour is empty. We may be
1559 // starting processing or we may have just
1560 // closed a contour.
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001561 kAfterMove_SegmentState, // We have seen a move, but nothing else.
1562 kAfterPrimitive_SegmentState // We have seen a primitive but not yet
1563 // closed the path. Also the initial state.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001564};
1565
1566SkPath::Iter::Iter() {
1567#ifdef SK_DEBUG
1568 fPts = NULL;
reed@google.com277c3f82013-05-31 15:17:50 +00001569 fConicWeights = NULL;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001570 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001571 fForceClose = fCloseLine = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001572 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001573#endif
1574 // need to init enough to make next() harmlessly return kDone_Verb
1575 fVerbs = NULL;
1576 fVerbStop = NULL;
1577 fNeedClose = false;
1578}
1579
1580SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
1581 this->setPath(path, forceClose);
1582}
1583
1584void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001585 fPts = path.fPathRef->points();
1586 fVerbs = path.fPathRef->verbs();
1587 fVerbStop = path.fPathRef->verbsMemBegin();
reed@google.com277c3f82013-05-31 15:17:50 +00001588 fConicWeights = path.fPathRef->conicWeights() - 1; // begin one behind
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001589 fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org72785c42011-12-29 21:03:28 +00001590 fMoveTo.fX = fMoveTo.fY = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001591 fForceClose = SkToU8(forceClose);
1592 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001593 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001594}
1595
1596bool SkPath::Iter::isClosedContour() const {
1597 if (fVerbs == NULL || fVerbs == fVerbStop) {
1598 return false;
1599 }
1600 if (fForceClose) {
1601 return true;
1602 }
1603
1604 const uint8_t* verbs = fVerbs;
1605 const uint8_t* stop = fVerbStop;
reed@google.comabf15c12011-01-18 20:35:51 +00001606
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001607 if (kMove_Verb == *(verbs - 1)) {
1608 verbs -= 1; // skip the initial moveto
reed@android.com8a1c16f2008-12-17 15:59:43 +00001609 }
1610
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001611 while (verbs > stop) {
1612 // verbs points one beyond the current verb, decrement first.
1613 unsigned v = *(--verbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001614 if (kMove_Verb == v) {
1615 break;
1616 }
1617 if (kClose_Verb == v) {
1618 return true;
1619 }
1620 }
1621 return false;
1622}
1623
1624SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001625 SkASSERT(pts);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001626 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001627 // A special case: if both points are NaN, SkPoint::operation== returns
1628 // false, but the iterator expects that they are treated as the same.
1629 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001630 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1631 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001632 return kClose_Verb;
1633 }
1634
reed@google.com9e25dbf2012-05-15 17:05:38 +00001635 pts[0] = fLastPt;
1636 pts[1] = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001637 fLastPt = fMoveTo;
1638 fCloseLine = true;
1639 return kLine_Verb;
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +00001640 } else {
1641 pts[0] = fMoveTo;
1642 return kClose_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001643 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001644}
1645
reed@google.com9e25dbf2012-05-15 17:05:38 +00001646const SkPoint& SkPath::Iter::cons_moveTo() {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001647 if (fSegmentState == kAfterMove_SegmentState) {
1648 // Set the first return pt to the move pt
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001649 fSegmentState = kAfterPrimitive_SegmentState;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001650 return fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001651 } else {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001652 SkASSERT(fSegmentState == kAfterPrimitive_SegmentState);
1653 // Set the first return pt to the last pt of the previous primitive.
reed@google.com9e25dbf2012-05-15 17:05:38 +00001654 return fPts[-1];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001655 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001656}
1657
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001658void SkPath::Iter::consumeDegenerateSegments() {
1659 // We need to step over anything that will not move the current draw point
1660 // forward before the next move is seen
1661 const uint8_t* lastMoveVerb = 0;
1662 const SkPoint* lastMovePt = 0;
1663 SkPoint lastPt = fLastPt;
1664 while (fVerbs != fVerbStop) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001665 unsigned verb = *(fVerbs - 1); // fVerbs is one beyond the current verb
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001666 switch (verb) {
1667 case kMove_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001668 // Keep a record of this most recent move
1669 lastMoveVerb = fVerbs;
1670 lastMovePt = fPts;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001671 lastPt = fPts[0];
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001672 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001673 fPts++;
1674 break;
1675
1676 case kClose_Verb:
schenney@chromium.org7e963602012-06-13 17:05:43 +00001677 // A close when we are in a segment is always valid except when it
1678 // follows a move which follows a segment.
1679 if (fSegmentState == kAfterPrimitive_SegmentState && !lastMoveVerb) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001680 return;
1681 }
1682 // A close at any other time must be ignored
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001683 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001684 break;
1685
1686 case kLine_Verb:
1687 if (!IsLineDegenerate(lastPt, fPts[0])) {
1688 if (lastMoveVerb) {
1689 fVerbs = lastMoveVerb;
1690 fPts = lastMovePt;
1691 return;
1692 }
1693 return;
1694 }
1695 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001696 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001697 fPts++;
1698 break;
1699
reed@google.com277c3f82013-05-31 15:17:50 +00001700 case kConic_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001701 case kQuad_Verb:
1702 if (!IsQuadDegenerate(lastPt, fPts[0], fPts[1])) {
1703 if (lastMoveVerb) {
1704 fVerbs = lastMoveVerb;
1705 fPts = lastMovePt;
1706 return;
1707 }
1708 return;
1709 }
1710 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001711 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001712 fPts += 2;
reed@google.com277c3f82013-05-31 15:17:50 +00001713 fConicWeights += (kConic_Verb == verb);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001714 break;
1715
1716 case kCubic_Verb:
1717 if (!IsCubicDegenerate(lastPt, fPts[0], fPts[1], fPts[2])) {
1718 if (lastMoveVerb) {
1719 fVerbs = lastMoveVerb;
1720 fPts = lastMovePt;
1721 return;
1722 }
1723 return;
1724 }
1725 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001726 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001727 fPts += 3;
1728 break;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001729
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001730 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001731 SkDEBUGFAIL("Should never see kDone_Verb");
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001732 }
1733 }
1734}
1735
reed@google.com4a3b7142012-05-16 17:16:46 +00001736SkPath::Verb SkPath::Iter::doNext(SkPoint ptsParam[4]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001737 SkASSERT(ptsParam);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001738
reed@android.com8a1c16f2008-12-17 15:59:43 +00001739 if (fVerbs == fVerbStop) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001740 // Close the curve if requested and if there is some curve to close
1741 if (fNeedClose && fSegmentState == kAfterPrimitive_SegmentState) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001742 if (kLine_Verb == this->autoClose(ptsParam)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001743 return kLine_Verb;
1744 }
1745 fNeedClose = false;
1746 return kClose_Verb;
1747 }
1748 return kDone_Verb;
1749 }
1750
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001751 // fVerbs is one beyond the current verb, decrement first
1752 unsigned verb = *(--fVerbs);
reed@google.com9e25dbf2012-05-15 17:05:38 +00001753 const SkPoint* SK_RESTRICT srcPts = fPts;
1754 SkPoint* SK_RESTRICT pts = ptsParam;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001755
1756 switch (verb) {
1757 case kMove_Verb:
1758 if (fNeedClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001759 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001760 verb = this->autoClose(pts);
1761 if (verb == kClose_Verb) {
1762 fNeedClose = false;
1763 }
1764 return (Verb)verb;
1765 }
1766 if (fVerbs == fVerbStop) { // might be a trailing moveto
1767 return kDone_Verb;
1768 }
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001769 fMoveTo = *srcPts;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001770 pts[0] = *srcPts;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001771 srcPts += 1;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001772 fSegmentState = kAfterMove_SegmentState;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001773 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001774 fNeedClose = fForceClose;
1775 break;
1776 case kLine_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001777 pts[0] = this->cons_moveTo();
1778 pts[1] = srcPts[0];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001779 fLastPt = srcPts[0];
1780 fCloseLine = false;
1781 srcPts += 1;
1782 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001783 case kConic_Verb:
1784 fConicWeights += 1;
1785 // fall-through
reed@android.com8a1c16f2008-12-17 15:59:43 +00001786 case kQuad_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001787 pts[0] = this->cons_moveTo();
1788 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001789 fLastPt = srcPts[1];
1790 srcPts += 2;
1791 break;
1792 case kCubic_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001793 pts[0] = this->cons_moveTo();
1794 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001795 fLastPt = srcPts[2];
1796 srcPts += 3;
1797 break;
1798 case kClose_Verb:
1799 verb = this->autoClose(pts);
1800 if (verb == kLine_Verb) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001801 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001802 } else {
1803 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001804 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001805 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001806 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001807 break;
1808 }
1809 fPts = srcPts;
1810 return (Verb)verb;
1811}
1812
1813///////////////////////////////////////////////////////////////////////////////
1814
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001815SkPath::RawIter::RawIter() {
1816#ifdef SK_DEBUG
1817 fPts = NULL;
reed@google.com277c3f82013-05-31 15:17:50 +00001818 fConicWeights = NULL;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001819 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
1820#endif
1821 // need to init enough to make next() harmlessly return kDone_Verb
1822 fVerbs = NULL;
1823 fVerbStop = NULL;
1824}
1825
1826SkPath::RawIter::RawIter(const SkPath& path) {
1827 this->setPath(path);
1828}
1829
1830void SkPath::RawIter::setPath(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001831 fPts = path.fPathRef->points();
1832 fVerbs = path.fPathRef->verbs();
1833 fVerbStop = path.fPathRef->verbsMemBegin();
reed@google.com277c3f82013-05-31 15:17:50 +00001834 fConicWeights = path.fPathRef->conicWeights() - 1; // begin one behind
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001835 fMoveTo.fX = fMoveTo.fY = 0;
1836 fLastPt.fX = fLastPt.fY = 0;
1837}
1838
1839SkPath::Verb SkPath::RawIter::next(SkPoint pts[4]) {
bsalomon49f085d2014-09-05 13:34:00 -07001840 SkASSERT(pts);
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001841 if (fVerbs == fVerbStop) {
1842 return kDone_Verb;
1843 }
1844
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001845 // fVerbs points one beyond next verb so decrement first.
1846 unsigned verb = *(--fVerbs);
1847 const SkPoint* srcPts = fPts;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001848
1849 switch (verb) {
1850 case kMove_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001851 pts[0] = *srcPts;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001852 fMoveTo = srcPts[0];
1853 fLastPt = fMoveTo;
1854 srcPts += 1;
1855 break;
1856 case kLine_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001857 pts[0] = fLastPt;
1858 pts[1] = srcPts[0];
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001859 fLastPt = srcPts[0];
1860 srcPts += 1;
1861 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001862 case kConic_Verb:
1863 fConicWeights += 1;
1864 // fall-through
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001865 case kQuad_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001866 pts[0] = fLastPt;
1867 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001868 fLastPt = srcPts[1];
1869 srcPts += 2;
1870 break;
1871 case kCubic_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001872 pts[0] = fLastPt;
1873 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001874 fLastPt = srcPts[2];
1875 srcPts += 3;
1876 break;
1877 case kClose_Verb:
1878 fLastPt = fMoveTo;
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001879 pts[0] = fMoveTo;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001880 break;
1881 }
1882 fPts = srcPts;
1883 return (Verb)verb;
1884}
1885
1886///////////////////////////////////////////////////////////////////////////////
1887
reed@android.com8a1c16f2008-12-17 15:59:43 +00001888/*
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001889 Format in compressed buffer: [ptCount, verbCount, pts[], verbs[]]
reed@android.com8a1c16f2008-12-17 15:59:43 +00001890*/
1891
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00001892size_t SkPath::writeToMemory(void* storage) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001893 SkDEBUGCODE(this->validate();)
1894
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001895 if (NULL == storage) {
robertphillips@google.comca0c8382013-09-26 12:18:23 +00001896 const int byteCount = sizeof(int32_t) + fPathRef->writeSize();
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001897 return SkAlign4(byteCount);
1898 }
1899
1900 SkWBuffer buffer(storage);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001901
robertphillips@google.com466310d2013-12-03 16:43:54 +00001902 int32_t packed = (fConvexity << kConvexity_SerializationShift) |
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001903 (fFillType << kFillType_SerializationShift) |
jvanverthb3eb6872014-10-24 07:12:51 -07001904 (fDirection << kDirection_SerializationShift) |
1905 (fIsVolatile << kIsVolatile_SerializationShift);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001906
robertphillips@google.com2972bb52012-08-07 17:32:51 +00001907 buffer.write32(packed);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001908
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001909 fPathRef->writeToBuffer(&buffer);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001910
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001911 buffer.padToAlign4();
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00001912 return buffer.pos();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001913}
1914
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00001915size_t SkPath::readFromMemory(const void* storage, size_t length) {
1916 SkRBufferWithSizeCheck buffer(storage, length);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001917
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00001918 int32_t packed;
1919 if (!buffer.readS32(&packed)) {
1920 return 0;
1921 }
1922
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001923 fConvexity = (packed >> kConvexity_SerializationShift) & 0xFF;
1924 fFillType = (packed >> kFillType_SerializationShift) & 0xFF;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001925 fDirection = (packed >> kDirection_SerializationShift) & 0x3;
jvanverthb3eb6872014-10-24 07:12:51 -07001926 fIsVolatile = (packed >> kIsVolatile_SerializationShift) & 0x1;
commit-bot@chromium.orgfed2ab62014-01-23 15:16:05 +00001927 SkPathRef* pathRef = SkPathRef::CreateFromBuffer(&buffer);
reed@google.comabf15c12011-01-18 20:35:51 +00001928
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00001929 size_t sizeRead = 0;
1930 if (buffer.isValid()) {
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00001931 fPathRef.reset(pathRef);
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00001932 SkDEBUGCODE(this->validate();)
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00001933 buffer.skipToAlign4();
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00001934 sizeRead = buffer.pos();
bsalomon49f085d2014-09-05 13:34:00 -07001935 } else if (pathRef) {
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00001936 // If the buffer is not valid, pathRef should be NULL
1937 sk_throw();
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00001938 }
1939 return sizeRead;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001940}
1941
1942///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00001943
reede05fed02014-12-15 07:59:53 -08001944#include "SkStringUtils.h"
caryclark66a5d8b2014-06-24 08:30:15 -07001945#include "SkStream.h"
reed@google.com51bbe752013-01-17 22:07:50 +00001946
reed@google.com51bbe752013-01-17 22:07:50 +00001947static void append_params(SkString* str, const char label[], const SkPoint pts[],
reede05fed02014-12-15 07:59:53 -08001948 int count, SkScalarAsStringType strType, SkScalar conicWeight = -1) {
reed@google.com51bbe752013-01-17 22:07:50 +00001949 str->append(label);
1950 str->append("(");
skia.committer@gmail.com15dd3002013-01-18 07:07:28 +00001951
reed@google.com51bbe752013-01-17 22:07:50 +00001952 const SkScalar* values = &pts[0].fX;
1953 count *= 2;
1954
1955 for (int i = 0; i < count; ++i) {
reede05fed02014-12-15 07:59:53 -08001956 SkAppendScalar(str, values[i], strType);
reed@google.com51bbe752013-01-17 22:07:50 +00001957 if (i < count - 1) {
1958 str->append(", ");
1959 }
1960 }
reed@google.com277c3f82013-05-31 15:17:50 +00001961 if (conicWeight >= 0) {
1962 str->append(", ");
reede05fed02014-12-15 07:59:53 -08001963 SkAppendScalar(str, conicWeight, strType);
reed@google.com277c3f82013-05-31 15:17:50 +00001964 }
caryclark08fa28c2014-10-23 13:08:56 -07001965 str->append(");");
reede05fed02014-12-15 07:59:53 -08001966 if (kHex_SkScalarAsStringType == strType) {
caryclark08fa28c2014-10-23 13:08:56 -07001967 str->append(" // ");
1968 for (int i = 0; i < count; ++i) {
reede05fed02014-12-15 07:59:53 -08001969 SkAppendScalarDec(str, values[i]);
caryclark08fa28c2014-10-23 13:08:56 -07001970 if (i < count - 1) {
1971 str->append(", ");
1972 }
1973 }
1974 if (conicWeight >= 0) {
1975 str->append(", ");
reede05fed02014-12-15 07:59:53 -08001976 SkAppendScalarDec(str, conicWeight);
caryclark08fa28c2014-10-23 13:08:56 -07001977 }
1978 }
1979 str->append("\n");
reed@google.com51bbe752013-01-17 22:07:50 +00001980}
1981
caryclarke9562592014-09-15 09:26:09 -07001982void SkPath::dump(SkWStream* wStream, bool forceClose, bool dumpAsHex) const {
reede05fed02014-12-15 07:59:53 -08001983 SkScalarAsStringType asType = dumpAsHex ? kHex_SkScalarAsStringType : kDec_SkScalarAsStringType;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001984 Iter iter(*this, forceClose);
1985 SkPoint pts[4];
1986 Verb verb;
1987
caryclark66a5d8b2014-06-24 08:30:15 -07001988 if (!wStream) {
1989 SkDebugf("path: forceClose=%s\n", forceClose ? "true" : "false");
1990 }
reed@google.com51bbe752013-01-17 22:07:50 +00001991 SkString builder;
1992
reed@google.com4a3b7142012-05-16 17:16:46 +00001993 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001994 switch (verb) {
1995 case kMove_Verb:
reede05fed02014-12-15 07:59:53 -08001996 append_params(&builder, "path.moveTo", &pts[0], 1, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001997 break;
1998 case kLine_Verb:
reede05fed02014-12-15 07:59:53 -08001999 append_params(&builder, "path.lineTo", &pts[1], 1, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002000 break;
2001 case kQuad_Verb:
reede05fed02014-12-15 07:59:53 -08002002 append_params(&builder, "path.quadTo", &pts[1], 2, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002003 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002004 case kConic_Verb:
reede05fed02014-12-15 07:59:53 -08002005 append_params(&builder, "path.conicTo", &pts[1], 2, asType, iter.conicWeight());
reed@google.com277c3f82013-05-31 15:17:50 +00002006 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002007 case kCubic_Verb:
reede05fed02014-12-15 07:59:53 -08002008 append_params(&builder, "path.cubicTo", &pts[1], 3, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002009 break;
2010 case kClose_Verb:
caryclark66a5d8b2014-06-24 08:30:15 -07002011 builder.append("path.close();\n");
reed@android.com8a1c16f2008-12-17 15:59:43 +00002012 break;
2013 default:
2014 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
2015 verb = kDone_Verb; // stop the loop
2016 break;
2017 }
2018 }
caryclark66a5d8b2014-06-24 08:30:15 -07002019 if (wStream) {
2020 wStream->writeText(builder.c_str());
2021 } else {
2022 SkDebugf("%s", builder.c_str());
2023 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002024}
2025
reed@android.come522ca52009-11-23 20:10:41 +00002026void SkPath::dump() const {
caryclarke9562592014-09-15 09:26:09 -07002027 this->dump(NULL, false, false);
2028}
2029
2030void SkPath::dumpHex() const {
2031 this->dump(NULL, false, true);
reed@android.come522ca52009-11-23 20:10:41 +00002032}
2033
2034#ifdef SK_DEBUG
2035void SkPath::validate() const {
2036 SkASSERT(this != NULL);
2037 SkASSERT((fFillType & ~3) == 0);
reed@google.comabf15c12011-01-18 20:35:51 +00002038
djsollen@google.com077348c2012-10-22 20:23:32 +00002039#ifdef SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002040 if (!fBoundsIsDirty) {
2041 SkRect bounds;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002042
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002043 bool isFinite = compute_pt_bounds(&bounds, *fPathRef.get());
robertphillips@google.com5d8d1862012-08-15 14:36:41 +00002044 SkASSERT(SkToBool(fIsFinite) == isFinite);
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002045
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002046 if (fPathRef->countPoints() <= 1) {
reed@android.come522ca52009-11-23 20:10:41 +00002047 // if we're empty, fBounds may be empty but translated, so we can't
2048 // necessarily compare to bounds directly
2049 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
2050 // be [2, 2, 2, 2]
2051 SkASSERT(bounds.isEmpty());
2052 SkASSERT(fBounds.isEmpty());
2053 } else {
reed@google.comeac52bd2011-11-14 18:13:59 +00002054 if (bounds.isEmpty()) {
2055 SkASSERT(fBounds.isEmpty());
2056 } else {
reed@google.com3563c9e2011-11-14 19:34:57 +00002057 if (!fBounds.isEmpty()) {
2058 SkASSERT(fBounds.contains(bounds));
2059 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002060 }
reed@android.come522ca52009-11-23 20:10:41 +00002061 }
2062 }
djsollen@google.com077348c2012-10-22 20:23:32 +00002063#endif // SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002064}
djsollen@google.com077348c2012-10-22 20:23:32 +00002065#endif // SK_DEBUG
reed@android.come522ca52009-11-23 20:10:41 +00002066
reed@google.com04863fa2011-05-15 04:08:24 +00002067///////////////////////////////////////////////////////////////////////////////
2068
reed@google.com0b7b9822011-05-16 12:29:27 +00002069static int sign(SkScalar x) { return x < 0; }
2070#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00002071
robertphillipsc506e302014-09-16 09:43:31 -07002072enum DirChange {
2073 kLeft_DirChange,
2074 kRight_DirChange,
2075 kStraight_DirChange,
2076 kBackwards_DirChange,
2077
2078 kInvalid_DirChange
2079};
2080
2081
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002082static bool almost_equal(SkScalar compA, SkScalar compB) {
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002083 // The error epsilon was empirically derived; worse case round rects
2084 // with a mid point outset by 2x float epsilon in tests had an error
2085 // of 12.
2086 const int epsilon = 16;
2087 if (!SkScalarIsFinite(compA) || !SkScalarIsFinite(compB)) {
2088 return false;
2089 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002090 // no need to check for small numbers because SkPath::Iter has removed degenerate values
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002091 int aBits = SkFloatAs2sCompliment(compA);
2092 int bBits = SkFloatAs2sCompliment(compB);
2093 return aBits < bBits + epsilon && bBits < aBits + epsilon;
reed@google.com04863fa2011-05-15 04:08:24 +00002094}
2095
robertphillipsc506e302014-09-16 09:43:31 -07002096static DirChange direction_change(const SkPoint& lastPt, const SkVector& curPt,
2097 const SkVector& lastVec, const SkVector& curVec) {
2098 SkScalar cross = SkPoint::CrossProduct(lastVec, curVec);
2099
2100 SkScalar smallest = SkTMin(curPt.fX, SkTMin(curPt.fY, SkTMin(lastPt.fX, lastPt.fY)));
2101 SkScalar largest = SkTMax(curPt.fX, SkTMax(curPt.fY, SkTMax(lastPt.fX, lastPt.fY)));
2102 largest = SkTMax(largest, -smallest);
2103
2104 if (!almost_equal(largest, largest + cross)) {
2105 int sign = SkScalarSignAsInt(cross);
2106 if (sign) {
2107 return (1 == sign) ? kRight_DirChange : kLeft_DirChange;
2108 }
2109 }
2110
2111 if (!SkScalarNearlyZero(lastVec.lengthSqd(), SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
2112 !SkScalarNearlyZero(curVec.lengthSqd(), SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
2113 lastVec.dot(curVec) < 0.0f) {
2114 return kBackwards_DirChange;
2115 }
2116
2117 return kStraight_DirChange;
2118}
2119
reed@google.com04863fa2011-05-15 04:08:24 +00002120// only valid for a single contour
2121struct Convexicator {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002122 Convexicator()
2123 : fPtCount(0)
2124 , fConvexity(SkPath::kConvex_Convexity)
caryclarkd3d1a982014-12-08 04:57:38 -08002125 , fDirection(SkPath::kUnknown_Direction)
2126 , fIsFinite(true) {
robertphillipsc506e302014-09-16 09:43:31 -07002127 fExpectedDir = kInvalid_DirChange;
reed@google.com04863fa2011-05-15 04:08:24 +00002128 // warnings
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002129 fLastPt.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002130 fCurrPt.set(0, 0);
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002131 fLastVec.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002132 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00002133
2134 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00002135 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00002136 }
2137
2138 SkPath::Convexity getConvexity() const { return fConvexity; }
2139
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002140 /** The direction returned is only valid if the path is determined convex */
2141 SkPath::Direction getDirection() const { return fDirection; }
2142
reed@google.com04863fa2011-05-15 04:08:24 +00002143 void addPt(const SkPoint& pt) {
caryclarkd3d1a982014-12-08 04:57:38 -08002144 if (SkPath::kConcave_Convexity == fConvexity || !fIsFinite) {
reed@google.com04863fa2011-05-15 04:08:24 +00002145 return;
2146 }
2147
2148 if (0 == fPtCount) {
2149 fCurrPt = pt;
2150 ++fPtCount;
2151 } else {
2152 SkVector vec = pt - fCurrPt;
caryclarkd3d1a982014-12-08 04:57:38 -08002153 SkScalar lengthSqd = vec.lengthSqd();
2154 if (!SkScalarIsFinite(lengthSqd)) {
2155 fIsFinite = false;
2156 } else if (!SkScalarNearlyZero(lengthSqd, SK_ScalarNearlyZero*SK_ScalarNearlyZero)) {
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002157 fLastPt = fCurrPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002158 fCurrPt = pt;
2159 if (++fPtCount == 2) {
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002160 fFirstVec = fLastVec = vec;
reed@google.com04863fa2011-05-15 04:08:24 +00002161 } else {
2162 SkASSERT(fPtCount > 2);
2163 this->addVec(vec);
2164 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002165
reed@google.com85b6e392011-05-15 20:25:17 +00002166 int sx = sign(vec.fX);
2167 int sy = sign(vec.fY);
2168 fDx += (sx != fSx);
2169 fDy += (sy != fSy);
2170 fSx = sx;
2171 fSy = sy;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002172
reed@google.com85b6e392011-05-15 20:25:17 +00002173 if (fDx > 3 || fDy > 3) {
2174 fConvexity = SkPath::kConcave_Convexity;
2175 }
reed@google.com04863fa2011-05-15 04:08:24 +00002176 }
2177 }
2178 }
2179
2180 void close() {
2181 if (fPtCount > 2) {
2182 this->addVec(fFirstVec);
2183 }
2184 }
2185
caryclarkd3d1a982014-12-08 04:57:38 -08002186 bool isFinite() const {
2187 return fIsFinite;
2188 }
2189
reed@google.com04863fa2011-05-15 04:08:24 +00002190private:
2191 void addVec(const SkVector& vec) {
2192 SkASSERT(vec.fX || vec.fY);
robertphillipsc506e302014-09-16 09:43:31 -07002193 DirChange dir = direction_change(fLastPt, fCurrPt, fLastVec, vec);
2194 switch (dir) {
2195 case kLeft_DirChange: // fall through
2196 case kRight_DirChange:
2197 if (kInvalid_DirChange == fExpectedDir) {
2198 fExpectedDir = dir;
2199 fDirection = (kRight_DirChange == dir) ? SkPath::kCW_Direction
2200 : SkPath::kCCW_Direction;
2201 } else if (dir != fExpectedDir) {
2202 fConvexity = SkPath::kConcave_Convexity;
2203 fDirection = SkPath::kUnknown_Direction;
2204 }
2205 fLastVec = vec;
2206 break;
2207 case kStraight_DirChange:
2208 break;
2209 case kBackwards_DirChange:
2210 fLastVec = vec;
2211 break;
2212 case kInvalid_DirChange:
2213 SkFAIL("Use of invalid direction change flag");
2214 break;
reed@google.com04863fa2011-05-15 04:08:24 +00002215 }
2216 }
2217
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002218 SkPoint fLastPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002219 SkPoint fCurrPt;
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002220 // fLastVec does not necessarily start at fLastPt. We only advance it when the cross product
2221 // value with the current vec is deemed to be of a significant value.
2222 SkVector fLastVec, fFirstVec;
reed@google.com04863fa2011-05-15 04:08:24 +00002223 int fPtCount; // non-degenerate points
robertphillipsc506e302014-09-16 09:43:31 -07002224 DirChange fExpectedDir;
reed@google.com04863fa2011-05-15 04:08:24 +00002225 SkPath::Convexity fConvexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002226 SkPath::Direction fDirection;
reed@google.com0b7b9822011-05-16 12:29:27 +00002227 int fDx, fDy, fSx, fSy;
caryclarkd3d1a982014-12-08 04:57:38 -08002228 bool fIsFinite;
reed@google.com04863fa2011-05-15 04:08:24 +00002229};
2230
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002231SkPath::Convexity SkPath::internalGetConvexity() const {
2232 SkASSERT(kUnknown_Convexity == fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002233 SkPoint pts[4];
2234 SkPath::Verb verb;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002235 SkPath::Iter iter(*this, true);
reed@google.com04863fa2011-05-15 04:08:24 +00002236
2237 int contourCount = 0;
2238 int count;
2239 Convexicator state;
2240
caryclarkd3d1a982014-12-08 04:57:38 -08002241 if (!isFinite()) {
2242 return kUnknown_Convexity;
2243 }
reed@google.com04863fa2011-05-15 04:08:24 +00002244 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
2245 switch (verb) {
2246 case kMove_Verb:
2247 if (++contourCount > 1) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002248 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002249 return kConcave_Convexity;
2250 }
2251 pts[1] = pts[0];
2252 count = 1;
2253 break;
2254 case kLine_Verb: count = 1; break;
2255 case kQuad_Verb: count = 2; break;
reed@google.com277c3f82013-05-31 15:17:50 +00002256 case kConic_Verb: count = 2; break;
reed@google.com04863fa2011-05-15 04:08:24 +00002257 case kCubic_Verb: count = 3; break;
2258 case kClose_Verb:
2259 state.close();
2260 count = 0;
2261 break;
2262 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00002263 SkDEBUGFAIL("bad verb");
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002264 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002265 return kConcave_Convexity;
2266 }
2267
2268 for (int i = 1; i <= count; i++) {
2269 state.addPt(pts[i]);
2270 }
2271 // early exit
caryclarkd3d1a982014-12-08 04:57:38 -08002272 if (!state.isFinite()) {
2273 return kUnknown_Convexity;
2274 }
reed@google.com04863fa2011-05-15 04:08:24 +00002275 if (kConcave_Convexity == state.getConvexity()) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002276 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002277 return kConcave_Convexity;
2278 }
2279 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002280 fConvexity = state.getConvexity();
2281 if (kConvex_Convexity == fConvexity && kUnknown_Direction == fDirection) {
2282 fDirection = state.getDirection();
2283 }
2284 return static_cast<Convexity>(fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002285}
reed@google.com69a99432012-01-10 18:00:10 +00002286
2287///////////////////////////////////////////////////////////////////////////////
2288
2289class ContourIter {
2290public:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002291 ContourIter(const SkPathRef& pathRef);
reed@google.com69a99432012-01-10 18:00:10 +00002292
2293 bool done() const { return fDone; }
2294 // if !done() then these may be called
2295 int count() const { return fCurrPtCount; }
2296 const SkPoint* pts() const { return fCurrPt; }
2297 void next();
2298
2299private:
2300 int fCurrPtCount;
2301 const SkPoint* fCurrPt;
2302 const uint8_t* fCurrVerb;
2303 const uint8_t* fStopVerbs;
reed@google.com277c3f82013-05-31 15:17:50 +00002304 const SkScalar* fCurrConicWeight;
reed@google.com69a99432012-01-10 18:00:10 +00002305 bool fDone;
reed@google.comd1ab9322012-01-10 18:40:03 +00002306 SkDEBUGCODE(int fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002307};
2308
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002309ContourIter::ContourIter(const SkPathRef& pathRef) {
2310 fStopVerbs = pathRef.verbsMemBegin();
reed@google.com69a99432012-01-10 18:00:10 +00002311 fDone = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002312 fCurrPt = pathRef.points();
2313 fCurrVerb = pathRef.verbs();
reed@google.com277c3f82013-05-31 15:17:50 +00002314 fCurrConicWeight = pathRef.conicWeights();
reed@google.com69a99432012-01-10 18:00:10 +00002315 fCurrPtCount = 0;
reed@google.comd1ab9322012-01-10 18:40:03 +00002316 SkDEBUGCODE(fContourCounter = 0;)
reed@google.com69a99432012-01-10 18:00:10 +00002317 this->next();
2318}
2319
2320void ContourIter::next() {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002321 if (fCurrVerb <= fStopVerbs) {
reed@google.com69a99432012-01-10 18:00:10 +00002322 fDone = true;
2323 }
2324 if (fDone) {
2325 return;
2326 }
2327
2328 // skip pts of prev contour
2329 fCurrPt += fCurrPtCount;
2330
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002331 SkASSERT(SkPath::kMove_Verb == fCurrVerb[~0]);
reed@google.com69a99432012-01-10 18:00:10 +00002332 int ptCount = 1; // moveTo
2333 const uint8_t* verbs = fCurrVerb;
2334
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002335 for (--verbs; verbs > fStopVerbs; --verbs) {
2336 switch (verbs[~0]) {
reed@google.com69a99432012-01-10 18:00:10 +00002337 case SkPath::kMove_Verb:
reed@google.com69a99432012-01-10 18:00:10 +00002338 goto CONTOUR_END;
2339 case SkPath::kLine_Verb:
2340 ptCount += 1;
2341 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002342 case SkPath::kConic_Verb:
2343 fCurrConicWeight += 1;
2344 // fall-through
reed@google.com69a99432012-01-10 18:00:10 +00002345 case SkPath::kQuad_Verb:
2346 ptCount += 2;
2347 break;
2348 case SkPath::kCubic_Verb:
2349 ptCount += 3;
2350 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002351 case SkPath::kClose_Verb:
2352 break;
2353 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00002354 SkDEBUGFAIL("unexpected verb");
reed@google.com69a99432012-01-10 18:00:10 +00002355 break;
2356 }
2357 }
2358CONTOUR_END:
2359 fCurrPtCount = ptCount;
2360 fCurrVerb = verbs;
reed@google.comd1ab9322012-01-10 18:40:03 +00002361 SkDEBUGCODE(++fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002362}
2363
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002364// returns cross product of (p1 - p0) and (p2 - p0)
reed@google.com69a99432012-01-10 18:00:10 +00002365static SkScalar cross_prod(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002366 SkScalar cross = SkPoint::CrossProduct(p1 - p0, p2 - p0);
2367 // We may get 0 when the above subtracts underflow. We expect this to be
2368 // very rare and lazily promote to double.
2369 if (0 == cross) {
2370 double p0x = SkScalarToDouble(p0.fX);
2371 double p0y = SkScalarToDouble(p0.fY);
2372
2373 double p1x = SkScalarToDouble(p1.fX);
2374 double p1y = SkScalarToDouble(p1.fY);
2375
2376 double p2x = SkScalarToDouble(p2.fX);
2377 double p2y = SkScalarToDouble(p2.fY);
2378
2379 cross = SkDoubleToScalar((p1x - p0x) * (p2y - p0y) -
2380 (p1y - p0y) * (p2x - p0x));
2381
2382 }
2383 return cross;
reed@google.com69a99432012-01-10 18:00:10 +00002384}
2385
reed@google.comc1ea60a2012-01-31 15:15:36 +00002386// Returns the first pt with the maximum Y coordinate
reed@google.com69a99432012-01-10 18:00:10 +00002387static int find_max_y(const SkPoint pts[], int count) {
2388 SkASSERT(count > 0);
2389 SkScalar max = pts[0].fY;
reed@google.comc1ea60a2012-01-31 15:15:36 +00002390 int firstIndex = 0;
reed@google.com69a99432012-01-10 18:00:10 +00002391 for (int i = 1; i < count; ++i) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002392 SkScalar y = pts[i].fY;
2393 if (y > max) {
2394 max = y;
2395 firstIndex = i;
reed@google.com69a99432012-01-10 18:00:10 +00002396 }
2397 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002398 return firstIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002399}
2400
reed@google.comcabaf1d2012-01-11 21:03:05 +00002401static int find_diff_pt(const SkPoint pts[], int index, int n, int inc) {
2402 int i = index;
2403 for (;;) {
2404 i = (i + inc) % n;
2405 if (i == index) { // we wrapped around, so abort
2406 break;
2407 }
2408 if (pts[index] != pts[i]) { // found a different point, success!
2409 break;
2410 }
2411 }
2412 return i;
2413}
2414
reed@google.comc1ea60a2012-01-31 15:15:36 +00002415/**
2416 * Starting at index, and moving forward (incrementing), find the xmin and
2417 * xmax of the contiguous points that have the same Y.
2418 */
2419static int find_min_max_x_at_y(const SkPoint pts[], int index, int n,
2420 int* maxIndexPtr) {
2421 const SkScalar y = pts[index].fY;
2422 SkScalar min = pts[index].fX;
2423 SkScalar max = min;
2424 int minIndex = index;
2425 int maxIndex = index;
2426 for (int i = index + 1; i < n; ++i) {
2427 if (pts[i].fY != y) {
2428 break;
2429 }
2430 SkScalar x = pts[i].fX;
2431 if (x < min) {
2432 min = x;
2433 minIndex = i;
2434 } else if (x > max) {
2435 max = x;
2436 maxIndex = i;
2437 }
2438 }
2439 *maxIndexPtr = maxIndex;
2440 return minIndex;
2441}
2442
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002443static void crossToDir(SkScalar cross, SkPath::Direction* dir) {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002444 *dir = cross > 0 ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002445}
2446
reed@google.comac8543f2012-01-30 20:51:25 +00002447/*
2448 * We loop through all contours, and keep the computed cross-product of the
2449 * contour that contained the global y-max. If we just look at the first
2450 * contour, we may find one that is wound the opposite way (correctly) since
2451 * it is the interior of a hole (e.g. 'o'). Thus we must find the contour
2452 * that is outer most (or at least has the global y-max) before we can consider
2453 * its cross product.
2454 */
reed@google.com69a99432012-01-10 18:00:10 +00002455bool SkPath::cheapComputeDirection(Direction* dir) const {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002456 if (kUnknown_Direction != fDirection) {
2457 *dir = static_cast<Direction>(fDirection);
2458 return true;
2459 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002460
2461 // don't want to pay the cost for computing this if it
2462 // is unknown, so we don't call isConvex()
2463 if (kConvex_Convexity == this->getConvexityOrUnknown()) {
2464 SkASSERT(kUnknown_Direction == fDirection);
2465 *dir = static_cast<Direction>(fDirection);
2466 return false;
2467 }
reed@google.com69a99432012-01-10 18:00:10 +00002468
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002469 ContourIter iter(*fPathRef.get());
reed@google.com69a99432012-01-10 18:00:10 +00002470
reed@google.comac8543f2012-01-30 20:51:25 +00002471 // initialize with our logical y-min
2472 SkScalar ymax = this->getBounds().fTop;
2473 SkScalar ymaxCross = 0;
2474
reed@google.com69a99432012-01-10 18:00:10 +00002475 for (; !iter.done(); iter.next()) {
2476 int n = iter.count();
reed@google.comcabaf1d2012-01-11 21:03:05 +00002477 if (n < 3) {
2478 continue;
2479 }
djsollen@google.come63793a2012-03-21 15:39:03 +00002480
reed@google.comcabaf1d2012-01-11 21:03:05 +00002481 const SkPoint* pts = iter.pts();
reed@google.com69a99432012-01-10 18:00:10 +00002482 SkScalar cross = 0;
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002483 int index = find_max_y(pts, n);
2484 if (pts[index].fY < ymax) {
2485 continue;
2486 }
2487
2488 // If there is more than 1 distinct point at the y-max, we take the
2489 // x-min and x-max of them and just subtract to compute the dir.
2490 if (pts[(index + 1) % n].fY == pts[index].fY) {
2491 int maxIndex;
2492 int minIndex = find_min_max_x_at_y(pts, index, n, &maxIndex);
2493 if (minIndex == maxIndex) {
2494 goto TRY_CROSSPROD;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002495 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002496 SkASSERT(pts[minIndex].fY == pts[index].fY);
2497 SkASSERT(pts[maxIndex].fY == pts[index].fY);
2498 SkASSERT(pts[minIndex].fX <= pts[maxIndex].fX);
2499 // we just subtract the indices, and let that auto-convert to
2500 // SkScalar, since we just want - or + to signal the direction.
2501 cross = minIndex - maxIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002502 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002503 TRY_CROSSPROD:
2504 // Find a next and prev index to use for the cross-product test,
2505 // but we try to find pts that form non-zero vectors from pts[index]
2506 //
2507 // Its possible that we can't find two non-degenerate vectors, so
2508 // we have to guard our search (e.g. all the pts could be in the
2509 // same place).
2510
2511 // we pass n - 1 instead of -1 so we don't foul up % operator by
2512 // passing it a negative LH argument.
2513 int prev = find_diff_pt(pts, index, n, n - 1);
2514 if (prev == index) {
2515 // completely degenerate, skip to next contour
reed@google.comac8543f2012-01-30 20:51:25 +00002516 continue;
2517 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002518 int next = find_diff_pt(pts, index, n, 1);
2519 SkASSERT(next != index);
2520 cross = cross_prod(pts[prev], pts[index], pts[next]);
2521 // if we get a zero and the points are horizontal, then we look at the spread in
2522 // x-direction. We really should continue to walk away from the degeneracy until
2523 // there is a divergence.
2524 if (0 == cross && pts[prev].fY == pts[index].fY && pts[next].fY == pts[index].fY) {
2525 // construct the subtract so we get the correct Direction below
2526 cross = pts[index].fX - pts[next].fX;
reed@google.com188bfcf2012-01-17 18:26:38 +00002527 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002528 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002529
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002530 if (cross) {
2531 // record our best guess so far
2532 ymax = pts[index].fY;
2533 ymaxCross = cross;
reed@google.com69a99432012-01-10 18:00:10 +00002534 }
2535 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002536 if (ymaxCross) {
2537 crossToDir(ymaxCross, dir);
2538 fDirection = *dir;
2539 return true;
2540 } else {
2541 return false;
2542 }
reed@google.comac8543f2012-01-30 20:51:25 +00002543}
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002544
2545///////////////////////////////////////////////////////////////////////////////
2546
2547static SkScalar eval_cubic_coeff(SkScalar A, SkScalar B, SkScalar C,
2548 SkScalar D, SkScalar t) {
2549 return SkScalarMulAdd(SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C), t, D);
2550}
2551
2552static SkScalar eval_cubic_pts(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2553 SkScalar t) {
2554 SkScalar A = c3 + 3*(c1 - c2) - c0;
2555 SkScalar B = 3*(c2 - c1 - c1 + c0);
2556 SkScalar C = 3*(c1 - c0);
2557 SkScalar D = c0;
2558 return eval_cubic_coeff(A, B, C, D, t);
2559}
2560
2561/* Given 4 cubic points (either Xs or Ys), and a target X or Y, compute the
2562 t value such that cubic(t) = target
2563 */
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002564static void chopMonoCubicAt(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002565 SkScalar target, SkScalar* t) {
2566 // SkASSERT(c0 <= c1 && c1 <= c2 && c2 <= c3);
2567 SkASSERT(c0 < target && target < c3);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002568
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002569 SkScalar D = c0 - target;
2570 SkScalar A = c3 + 3*(c1 - c2) - c0;
2571 SkScalar B = 3*(c2 - c1 - c1 + c0);
2572 SkScalar C = 3*(c1 - c0);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002573
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002574 const SkScalar TOLERANCE = SK_Scalar1 / 4096;
2575 SkScalar minT = 0;
2576 SkScalar maxT = SK_Scalar1;
2577 SkScalar mid;
2578 int i;
2579 for (i = 0; i < 16; i++) {
2580 mid = SkScalarAve(minT, maxT);
2581 SkScalar delta = eval_cubic_coeff(A, B, C, D, mid);
2582 if (delta < 0) {
2583 minT = mid;
2584 delta = -delta;
2585 } else {
2586 maxT = mid;
2587 }
2588 if (delta < TOLERANCE) {
2589 break;
2590 }
2591 }
2592 *t = mid;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002593}
2594
2595template <size_t N> static void find_minmax(const SkPoint pts[],
2596 SkScalar* minPtr, SkScalar* maxPtr) {
2597 SkScalar min, max;
2598 min = max = pts[0].fX;
2599 for (size_t i = 1; i < N; ++i) {
2600 min = SkMinScalar(min, pts[i].fX);
2601 max = SkMaxScalar(max, pts[i].fX);
2602 }
2603 *minPtr = min;
2604 *maxPtr = max;
2605}
2606
2607static int winding_mono_cubic(const SkPoint pts[], SkScalar x, SkScalar y) {
2608 SkPoint storage[4];
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002609
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002610 int dir = 1;
2611 if (pts[0].fY > pts[3].fY) {
2612 storage[0] = pts[3];
2613 storage[1] = pts[2];
2614 storage[2] = pts[1];
2615 storage[3] = pts[0];
2616 pts = storage;
2617 dir = -1;
2618 }
2619 if (y < pts[0].fY || y >= pts[3].fY) {
2620 return 0;
2621 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002622
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002623 // quickreject or quickaccept
2624 SkScalar min, max;
2625 find_minmax<4>(pts, &min, &max);
2626 if (x < min) {
2627 return 0;
2628 }
2629 if (x > max) {
2630 return dir;
2631 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002632
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002633 // compute the actual x(t) value
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002634 SkScalar t;
2635 chopMonoCubicAt(pts[0].fY, pts[1].fY, pts[2].fY, pts[3].fY, y, &t);
2636 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 +00002637 return xt < x ? dir : 0;
2638}
2639
2640static int winding_cubic(const SkPoint pts[], SkScalar x, SkScalar y) {
2641 SkPoint dst[10];
2642 int n = SkChopCubicAtYExtrema(pts, dst);
2643 int w = 0;
2644 for (int i = 0; i <= n; ++i) {
2645 w += winding_mono_cubic(&dst[i * 3], x, y);
2646 }
2647 return w;
2648}
2649
2650static int winding_mono_quad(const SkPoint pts[], SkScalar x, SkScalar y) {
2651 SkScalar y0 = pts[0].fY;
2652 SkScalar y2 = pts[2].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002653
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002654 int dir = 1;
2655 if (y0 > y2) {
2656 SkTSwap(y0, y2);
2657 dir = -1;
2658 }
2659 if (y < y0 || y >= y2) {
2660 return 0;
2661 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002662
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002663 // bounds check on X (not required. is it faster?)
2664#if 0
2665 if (pts[0].fX > x && pts[1].fX > x && pts[2].fX > x) {
2666 return 0;
2667 }
2668#endif
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002669
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002670 SkScalar roots[2];
2671 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
2672 2 * (pts[1].fY - pts[0].fY),
2673 pts[0].fY - y,
2674 roots);
2675 SkASSERT(n <= 1);
2676 SkScalar xt;
2677 if (0 == n) {
2678 SkScalar mid = SkScalarAve(y0, y2);
2679 // Need [0] and [2] if dir == 1
2680 // and [2] and [0] if dir == -1
2681 xt = y < mid ? pts[1 - dir].fX : pts[dir - 1].fX;
2682 } else {
2683 SkScalar t = roots[0];
2684 SkScalar C = pts[0].fX;
2685 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
2686 SkScalar B = 2 * (pts[1].fX - C);
2687 xt = SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C);
2688 }
2689 return xt < x ? dir : 0;
2690}
2691
2692static bool is_mono_quad(SkScalar y0, SkScalar y1, SkScalar y2) {
2693 // return SkScalarSignAsInt(y0 - y1) + SkScalarSignAsInt(y1 - y2) != 0;
2694 if (y0 == y1) {
2695 return true;
2696 }
2697 if (y0 < y1) {
2698 return y1 <= y2;
2699 } else {
2700 return y1 >= y2;
2701 }
2702}
2703
2704static int winding_quad(const SkPoint pts[], SkScalar x, SkScalar y) {
2705 SkPoint dst[5];
2706 int n = 0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002707
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002708 if (!is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY)) {
2709 n = SkChopQuadAtYExtrema(pts, dst);
2710 pts = dst;
2711 }
2712 int w = winding_mono_quad(pts, x, y);
2713 if (n > 0) {
2714 w += winding_mono_quad(&pts[2], x, y);
2715 }
2716 return w;
2717}
2718
2719static int winding_line(const SkPoint pts[], SkScalar x, SkScalar y) {
2720 SkScalar x0 = pts[0].fX;
2721 SkScalar y0 = pts[0].fY;
2722 SkScalar x1 = pts[1].fX;
2723 SkScalar y1 = pts[1].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002724
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002725 SkScalar dy = y1 - y0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002726
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002727 int dir = 1;
2728 if (y0 > y1) {
2729 SkTSwap(y0, y1);
2730 dir = -1;
2731 }
2732 if (y < y0 || y >= y1) {
2733 return 0;
2734 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002735
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002736 SkScalar cross = SkScalarMul(x1 - x0, y - pts[0].fY) -
2737 SkScalarMul(dy, x - pts[0].fX);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002738
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002739 if (SkScalarSignAsInt(cross) == dir) {
2740 dir = 0;
2741 }
2742 return dir;
2743}
2744
reed@google.com4db592c2013-10-30 17:39:43 +00002745static bool contains_inclusive(const SkRect& r, SkScalar x, SkScalar y) {
2746 return r.fLeft <= x && x <= r.fRight && r.fTop <= y && y <= r.fBottom;
2747}
2748
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002749bool SkPath::contains(SkScalar x, SkScalar y) const {
2750 bool isInverse = this->isInverseFillType();
2751 if (this->isEmpty()) {
2752 return isInverse;
2753 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002754
reed@google.com4db592c2013-10-30 17:39:43 +00002755 if (!contains_inclusive(this->getBounds(), x, y)) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002756 return isInverse;
2757 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002758
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002759 SkPath::Iter iter(*this, true);
2760 bool done = false;
2761 int w = 0;
2762 do {
2763 SkPoint pts[4];
2764 switch (iter.next(pts, false)) {
2765 case SkPath::kMove_Verb:
2766 case SkPath::kClose_Verb:
2767 break;
2768 case SkPath::kLine_Verb:
2769 w += winding_line(pts, x, y);
2770 break;
2771 case SkPath::kQuad_Verb:
2772 w += winding_quad(pts, x, y);
2773 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002774 case SkPath::kConic_Verb:
2775 SkASSERT(0);
2776 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002777 case SkPath::kCubic_Verb:
2778 w += winding_cubic(pts, x, y);
2779 break;
2780 case SkPath::kDone_Verb:
2781 done = true;
2782 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002783 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002784 } while (!done);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002785
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002786 switch (this->getFillType()) {
2787 case SkPath::kEvenOdd_FillType:
2788 case SkPath::kInverseEvenOdd_FillType:
2789 w &= 1;
2790 break;
reed@google.come9bb6232012-07-11 18:56:10 +00002791 default:
2792 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002793 }
2794 return SkToBool(w);
2795}