blob: b92b47f5c6b00f20315618c766b15aff89f0d609 [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);
caryclark63c684a2015-02-25 09:04:04 -0800866 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
reed@google.com744faba2012-05-29 19:54:52 +0000867 }
868
reed@google.com744faba2012-05-29 19:54:52 +0000869 DIRTY_AFTER_EDIT;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000870 SkDEBUGCODE(this->validate();)
reed@google.com744faba2012-05-29 19:54:52 +0000871}
872
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000873#include "SkGeometry.h"
874
reedf90ea012015-01-29 12:03:58 -0800875static bool arc_is_lone_point(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
876 SkPoint* pt) {
877 if (0 == sweepAngle && (0 == startAngle || SkIntToScalar(360) == startAngle)) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000878 // Chrome uses this path to move into and out of ovals. If not
879 // treated as a special case the moves can distort the oval's
880 // bounding box (and break the circle special case).
reedf90ea012015-01-29 12:03:58 -0800881 pt->set(oval.fRight, oval.centerY());
882 return true;
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000883 } else if (0 == oval.width() && 0 == oval.height()) {
884 // Chrome will sometimes create 0 radius round rects. Having degenerate
885 // quad segments in the path prevents the path from being recognized as
886 // a rect.
887 // TODO: optimizing the case where only one of width or height is zero
888 // should also be considered. This case, however, doesn't seem to be
889 // as common as the single point case.
reedf90ea012015-01-29 12:03:58 -0800890 pt->set(oval.fRight, oval.fTop);
891 return true;
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000892 }
reedf90ea012015-01-29 12:03:58 -0800893 return false;
894}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000895
reedd5d27d92015-02-09 13:54:43 -0800896// Return the unit vectors pointing at the start/stop points for the given start/sweep angles
897//
898static void angles_to_unit_vectors(SkScalar startAngle, SkScalar sweepAngle,
899 SkVector* startV, SkVector* stopV, SkRotationDirection* dir) {
900 startV->fY = SkScalarSinCos(SkDegreesToRadians(startAngle), &startV->fX);
901 stopV->fY = SkScalarSinCos(SkDegreesToRadians(startAngle + sweepAngle), &stopV->fX);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000902
903 /* If the sweep angle is nearly (but less than) 360, then due to precision
reedd5d27d92015-02-09 13:54:43 -0800904 loss in radians-conversion and/or sin/cos, we may end up with coincident
905 vectors, which will fool SkBuildQuadArc into doing nothing (bad) instead
906 of drawing a nearly complete circle (good).
907 e.g. canvas.drawArc(0, 359.99, ...)
908 -vs- canvas.drawArc(0, 359.9, ...)
909 We try to detect this edge case, and tweak the stop vector
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000910 */
reedd5d27d92015-02-09 13:54:43 -0800911 if (*startV == *stopV) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000912 SkScalar sw = SkScalarAbs(sweepAngle);
913 if (sw < SkIntToScalar(360) && sw > SkIntToScalar(359)) {
914 SkScalar stopRad = SkDegreesToRadians(startAngle + sweepAngle);
915 // make a guess at a tiny angle (in radians) to tweak by
916 SkScalar deltaRad = SkScalarCopySign(SK_Scalar1/512, sweepAngle);
917 // not sure how much will be enough, so we use a loop
918 do {
919 stopRad -= deltaRad;
reedd5d27d92015-02-09 13:54:43 -0800920 stopV->fY = SkScalarSinCos(stopRad, &stopV->fX);
921 } while (*startV == *stopV);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000922 }
923 }
reedd5d27d92015-02-09 13:54:43 -0800924 *dir = sweepAngle > 0 ? kCW_SkRotationDirection : kCCW_SkRotationDirection;
925}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000926
reedd5d27d92015-02-09 13:54:43 -0800927#ifdef SK_SUPPORT_LEGACY_ARCTO_QUADS
928static int build_arc_points(const SkRect& oval, const SkVector& start, const SkVector& stop,
929 SkRotationDirection dir, SkPoint pts[kSkBuildQuadArcStorage]) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000930 SkMatrix matrix;
931
932 matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height()));
933 matrix.postTranslate(oval.centerX(), oval.centerY());
934
reedd5d27d92015-02-09 13:54:43 -0800935 return SkBuildQuadArc(start, stop, dir, &matrix, pts);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000936}
reedd5d27d92015-02-09 13:54:43 -0800937#else
reed9e779d42015-02-17 11:43:14 -0800938/**
939 * If this returns 0, then the caller should just line-to the singlePt, else it should
940 * ignore singlePt and append the specified number of conics.
941 */
reedd5d27d92015-02-09 13:54:43 -0800942static int build_arc_conics(const SkRect& oval, const SkVector& start, const SkVector& stop,
reed9e779d42015-02-17 11:43:14 -0800943 SkRotationDirection dir, SkConic conics[SkConic::kMaxConicsForArc],
944 SkPoint* singlePt) {
reedd5d27d92015-02-09 13:54:43 -0800945 SkMatrix matrix;
946
947 matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height()));
948 matrix.postTranslate(oval.centerX(), oval.centerY());
949
reed9e779d42015-02-17 11:43:14 -0800950 int count = SkConic::BuildUnitArc(start, stop, dir, &matrix, conics);
951 if (0 == count) {
952 matrix.mapXY(start.x(), start.y(), singlePt);
953 }
954 return count;
reedd5d27d92015-02-09 13:54:43 -0800955}
956#endif
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000957
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +0000958void SkPath::addRoundRect(const SkRect& rect, const SkScalar radii[],
reed@android.com8a1c16f2008-12-17 15:59:43 +0000959 Direction dir) {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +0000960 SkRRect rrect;
961 rrect.setRectRadii(rect, (const SkVector*) radii);
962 this->addRRect(rrect, dir);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000963}
964
reed@google.com4ed0fb72012-12-12 20:48:18 +0000965void SkPath::addRRect(const SkRRect& rrect, Direction dir) {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +0000966 assert_known_direction(dir);
967
968 if (rrect.isEmpty()) {
969 return;
970 }
971
reed@google.com4ed0fb72012-12-12 20:48:18 +0000972 const SkRect& bounds = rrect.getBounds();
973
974 if (rrect.isRect()) {
975 this->addRect(bounds, dir);
976 } else if (rrect.isOval()) {
977 this->addOval(bounds, dir);
reed@google.com4ed0fb72012-12-12 20:48:18 +0000978 } else {
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +0000979 fDirection = this->hasOnlyMoveTos() ? dir : kUnknown_Direction;
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +0000980
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +0000981 SkAutoPathBoundsUpdate apbu(this, bounds);
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +0000982 SkAutoDisableDirectionCheck addc(this);
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +0000983
reed1b28a3a2014-12-17 14:42:09 -0800984 const SkScalar L = bounds.fLeft;
985 const SkScalar T = bounds.fTop;
986 const SkScalar R = bounds.fRight;
987 const SkScalar B = bounds.fBottom;
988 const SkScalar W = SK_ScalarRoot2Over2;
989
990 this->incReserve(13);
991 if (kCW_Direction == dir) {
992 this->moveTo(L, B - rrect.fRadii[SkRRect::kLowerLeft_Corner].fY);
993
994 this->lineTo(L, T + rrect.fRadii[SkRRect::kUpperLeft_Corner].fY);
995 this->conicTo(L, T, L + rrect.fRadii[SkRRect::kUpperLeft_Corner].fX, T, W);
996
997 this->lineTo(R - rrect.fRadii[SkRRect::kUpperRight_Corner].fX, T);
998 this->conicTo(R, T, R, T + rrect.fRadii[SkRRect::kUpperRight_Corner].fY, W);
999
1000 this->lineTo(R, B - rrect.fRadii[SkRRect::kLowerRight_Corner].fY);
1001 this->conicTo(R, B, R - rrect.fRadii[SkRRect::kLowerRight_Corner].fX, B, W);
1002
1003 this->lineTo(L + rrect.fRadii[SkRRect::kLowerLeft_Corner].fX, B);
1004 this->conicTo(L, B, L, B - rrect.fRadii[SkRRect::kLowerLeft_Corner].fY, W);
1005 } else {
1006 this->moveTo(L, T + rrect.fRadii[SkRRect::kUpperLeft_Corner].fY);
1007
1008 this->lineTo(L, B - rrect.fRadii[SkRRect::kLowerLeft_Corner].fY);
1009 this->conicTo(L, B, L + rrect.fRadii[SkRRect::kLowerLeft_Corner].fX, B, W);
1010
1011 this->lineTo(R - rrect.fRadii[SkRRect::kLowerRight_Corner].fX, B);
1012 this->conicTo(R, B, R, B - rrect.fRadii[SkRRect::kLowerRight_Corner].fY, W);
1013
1014 this->lineTo(R, T + rrect.fRadii[SkRRect::kUpperRight_Corner].fY);
1015 this->conicTo(R, T, R - rrect.fRadii[SkRRect::kUpperRight_Corner].fX, T, W);
1016
1017 this->lineTo(L + rrect.fRadii[SkRRect::kUpperLeft_Corner].fX, T);
1018 this->conicTo(L, T, L, T + rrect.fRadii[SkRRect::kUpperLeft_Corner].fY, W);
1019 }
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001020 this->close();
reed@google.com4ed0fb72012-12-12 20:48:18 +00001021 }
reed5bcbe912014-12-15 12:28:33 -08001022 SkDEBUGCODE(fPathRef->validate();)
reed@google.com4ed0fb72012-12-12 20:48:18 +00001023}
1024
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001025bool SkPath::hasOnlyMoveTos() const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001026 int count = fPathRef->countVerbs();
1027 const uint8_t* verbs = const_cast<const SkPathRef*>(fPathRef.get())->verbsMemBegin();
1028 for (int i = 0; i < count; ++i) {
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001029 if (*verbs == kLine_Verb ||
1030 *verbs == kQuad_Verb ||
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001031 *verbs == kConic_Verb ||
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001032 *verbs == kCubic_Verb) {
1033 return false;
1034 }
1035 ++verbs;
1036 }
1037 return true;
1038}
1039
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001040void SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
1041 Direction dir) {
1042 assert_known_direction(dir);
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001043
humper@google.com75e3ca12013-04-08 21:44:11 +00001044 if (rx < 0 || ry < 0) {
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001045 SkErrorInternals::SetError( kInvalidArgument_SkError,
humper@google.com75e3ca12013-04-08 21:44:11 +00001046 "I got %f and %f as radii to SkPath::AddRoundRect, "
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001047 "but negative radii are not allowed.",
humper@google.com75e3ca12013-04-08 21:44:11 +00001048 SkScalarToDouble(rx), SkScalarToDouble(ry) );
1049 return;
1050 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001051
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001052 SkRRect rrect;
1053 rrect.setRectXY(rect, rx, ry);
1054 this->addRRect(rrect, dir);
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001055}
1056
reed@android.com8a1c16f2008-12-17 15:59:43 +00001057void SkPath::addOval(const SkRect& oval, Direction dir) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +00001058 assert_known_direction(dir);
1059
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001060 /* If addOval() is called after previous moveTo(),
1061 this path is still marked as an oval. This is used to
1062 fit into WebKit's calling sequences.
1063 We can't simply check isEmpty() in this case, as additional
1064 moveTo() would mark the path non empty.
1065 */
robertphillips@google.com466310d2013-12-03 16:43:54 +00001066 bool isOval = hasOnlyMoveTos();
1067 if (isOval) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001068 fDirection = dir;
1069 } else {
1070 fDirection = kUnknown_Direction;
1071 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001072
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001073 SkAutoDisableDirectionCheck addc(this);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001074
reed@android.com8a1c16f2008-12-17 15:59:43 +00001075 SkAutoPathBoundsUpdate apbu(this, oval);
1076
reed220f9262014-12-17 08:21:04 -08001077#ifdef SK_SUPPORT_LEGACY_ADDOVAL
reed@android.com8a1c16f2008-12-17 15:59:43 +00001078 SkScalar cx = oval.centerX();
1079 SkScalar cy = oval.centerY();
1080 SkScalar rx = SkScalarHalf(oval.width());
1081 SkScalar ry = SkScalarHalf(oval.height());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001082
reed@android.com8a1c16f2008-12-17 15:59:43 +00001083 SkScalar sx = SkScalarMul(rx, SK_ScalarTanPIOver8);
1084 SkScalar sy = SkScalarMul(ry, SK_ScalarTanPIOver8);
1085 SkScalar mx = SkScalarMul(rx, SK_ScalarRoot2Over2);
1086 SkScalar my = SkScalarMul(ry, SK_ScalarRoot2Over2);
1087
1088 /*
reed220f9262014-12-17 08:21:04 -08001089 To handle imprecision in computing the center and radii, we revert to
1090 the provided bounds when we can (i.e. use oval.fLeft instead of cx-rx)
1091 to ensure that we don't exceed the oval's bounds *ever*, since we want
1092 to use oval for our fast-bounds, rather than have to recompute it.
1093 */
reed@android.com8a1c16f2008-12-17 15:59:43 +00001094 const SkScalar L = oval.fLeft; // cx - rx
1095 const SkScalar T = oval.fTop; // cy - ry
1096 const SkScalar R = oval.fRight; // cx + rx
1097 const SkScalar B = oval.fBottom; // cy + ry
1098
1099 this->incReserve(17); // 8 quads + close
1100 this->moveTo(R, cy);
1101 if (dir == kCCW_Direction) {
1102 this->quadTo( R, cy - sy, cx + mx, cy - my);
1103 this->quadTo(cx + sx, T, cx , T);
1104 this->quadTo(cx - sx, T, cx - mx, cy - my);
1105 this->quadTo( L, cy - sy, L, cy );
1106 this->quadTo( L, cy + sy, cx - mx, cy + my);
1107 this->quadTo(cx - sx, B, cx , B);
1108 this->quadTo(cx + sx, B, cx + mx, cy + my);
1109 this->quadTo( R, cy + sy, R, cy );
1110 } else {
1111 this->quadTo( R, cy + sy, cx + mx, cy + my);
1112 this->quadTo(cx + sx, B, cx , B);
1113 this->quadTo(cx - sx, B, cx - mx, cy + my);
1114 this->quadTo( L, cy + sy, L, cy );
1115 this->quadTo( L, cy - sy, cx - mx, cy - my);
1116 this->quadTo(cx - sx, T, cx , T);
1117 this->quadTo(cx + sx, T, cx + mx, cy - my);
1118 this->quadTo( R, cy - sy, R, cy );
1119 }
reed220f9262014-12-17 08:21:04 -08001120#else
1121 const SkScalar L = oval.fLeft;
1122 const SkScalar T = oval.fTop;
1123 const SkScalar R = oval.fRight;
1124 const SkScalar B = oval.fBottom;
1125 const SkScalar cx = oval.centerX();
1126 const SkScalar cy = oval.centerY();
1127 const SkScalar weight = SK_ScalarRoot2Over2;
1128
1129 this->incReserve(9); // move + 4 conics
1130 this->moveTo(R, cy);
1131 if (dir == kCCW_Direction) {
1132 this->conicTo(R, T, cx, T, weight);
1133 this->conicTo(L, T, L, cy, weight);
1134 this->conicTo(L, B, cx, B, weight);
1135 this->conicTo(R, B, R, cy, weight);
1136 } else {
1137 this->conicTo(R, B, cx, B, weight);
1138 this->conicTo(L, B, L, cy, weight);
1139 this->conicTo(L, T, cx, T, weight);
1140 this->conicTo(R, T, R, cy, weight);
1141 }
1142#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +00001143 this->close();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001144
robertphillips@google.com466310d2013-12-03 16:43:54 +00001145 SkPathRef::Editor ed(&fPathRef);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001146
robertphillips@google.com466310d2013-12-03 16:43:54 +00001147 ed.setIsOval(isOval);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001148}
1149
reed@android.com8a1c16f2008-12-17 15:59:43 +00001150void SkPath::addCircle(SkScalar x, SkScalar y, SkScalar r, Direction dir) {
1151 if (r > 0) {
1152 SkRect rect;
1153 rect.set(x - r, y - r, x + r, y + r);
1154 this->addOval(rect, dir);
1155 }
1156}
1157
reed@android.com8a1c16f2008-12-17 15:59:43 +00001158void SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
1159 bool forceMoveTo) {
1160 if (oval.width() < 0 || oval.height() < 0) {
1161 return;
1162 }
1163
reedf90ea012015-01-29 12:03:58 -08001164 if (fPathRef->countVerbs() == 0) {
1165 forceMoveTo = true;
1166 }
1167
1168 SkPoint lonePt;
1169 if (arc_is_lone_point(oval, startAngle, sweepAngle, &lonePt)) {
1170 forceMoveTo ? this->moveTo(lonePt) : this->lineTo(lonePt);
1171 return;
1172 }
1173
reedd5d27d92015-02-09 13:54:43 -08001174 SkVector startV, stopV;
1175 SkRotationDirection dir;
1176 angles_to_unit_vectors(startAngle, sweepAngle, &startV, &stopV, &dir);
1177
1178#ifdef SK_SUPPORT_LEGACY_ARCTO_QUADS
reed@android.com8a1c16f2008-12-17 15:59:43 +00001179 SkPoint pts[kSkBuildQuadArcStorage];
reedd5d27d92015-02-09 13:54:43 -08001180 int count = build_arc_points(oval, startV, stopV, dir, pts);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001181 SkASSERT((count & 1) == 1);
1182
reed@android.com8a1c16f2008-12-17 15:59:43 +00001183 this->incReserve(count);
1184 forceMoveTo ? this->moveTo(pts[0]) : this->lineTo(pts[0]);
1185 for (int i = 1; i < count; i += 2) {
1186 this->quadTo(pts[i], pts[i+1]);
1187 }
reedd5d27d92015-02-09 13:54:43 -08001188#else
reed9e779d42015-02-17 11:43:14 -08001189 SkPoint singlePt;
reedd5d27d92015-02-09 13:54:43 -08001190 SkConic conics[SkConic::kMaxConicsForArc];
reed9e779d42015-02-17 11:43:14 -08001191 int count = build_arc_conics(oval, startV, stopV, dir, conics, &singlePt);
reedd5d27d92015-02-09 13:54:43 -08001192 if (count) {
1193 this->incReserve(count * 2 + 1);
1194 const SkPoint& pt = conics[0].fPts[0];
1195 forceMoveTo ? this->moveTo(pt) : this->lineTo(pt);
1196 for (int i = 0; i < count; ++i) {
1197 this->conicTo(conics[i].fPts[1], conics[i].fPts[2], conics[i].fW);
1198 }
reed9e779d42015-02-17 11:43:14 -08001199 } else {
1200 forceMoveTo ? this->moveTo(singlePt) : this->lineTo(singlePt);
reedd5d27d92015-02-09 13:54:43 -08001201 }
1202#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +00001203}
1204
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001205void SkPath::addArc(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001206 if (oval.isEmpty() || 0 == sweepAngle) {
1207 return;
1208 }
1209
1210 const SkScalar kFullCircleAngle = SkIntToScalar(360);
1211
1212 if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
1213 this->addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction);
reedc7789042015-01-29 12:59:11 -08001214 } else {
1215 this->arcTo(oval, startAngle, sweepAngle, true);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001216 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001217}
1218
1219/*
1220 Need to handle the case when the angle is sharp, and our computed end-points
1221 for the arc go behind pt1 and/or p2...
1222*/
reedc7789042015-01-29 12:59:11 -08001223void SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2, SkScalar radius) {
reeda8b326c2014-12-09 11:50:32 -08001224 if (radius == 0) {
1225 this->lineTo(x1, y1);
1226 return;
1227 }
1228
1229 SkVector before, after;
reed@google.comabf15c12011-01-18 20:35:51 +00001230
reed@android.com8a1c16f2008-12-17 15:59:43 +00001231 // need to know our prev pt so we can construct tangent vectors
1232 {
1233 SkPoint start;
1234 this->getLastPt(&start);
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001235 // Handle degenerate cases by adding a line to the first point and
1236 // bailing out.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001237 before.setNormalize(x1 - start.fX, y1 - start.fY);
1238 after.setNormalize(x2 - x1, y2 - y1);
1239 }
reed@google.comabf15c12011-01-18 20:35:51 +00001240
reed@android.com8a1c16f2008-12-17 15:59:43 +00001241 SkScalar cosh = SkPoint::DotProduct(before, after);
1242 SkScalar sinh = SkPoint::CrossProduct(before, after);
1243
1244 if (SkScalarNearlyZero(sinh)) { // angle is too tight
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001245 this->lineTo(x1, y1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001246 return;
1247 }
reed@google.comabf15c12011-01-18 20:35:51 +00001248
reed@android.com8a1c16f2008-12-17 15:59:43 +00001249 SkScalar dist = SkScalarMulDiv(radius, SK_Scalar1 - cosh, sinh);
1250 if (dist < 0) {
1251 dist = -dist;
1252 }
1253
1254 SkScalar xx = x1 - SkScalarMul(dist, before.fX);
1255 SkScalar yy = y1 - SkScalarMul(dist, before.fY);
1256 SkRotationDirection arcDir;
1257
1258 // now turn before/after into normals
1259 if (sinh > 0) {
1260 before.rotateCCW();
1261 after.rotateCCW();
1262 arcDir = kCW_SkRotationDirection;
1263 } else {
1264 before.rotateCW();
1265 after.rotateCW();
1266 arcDir = kCCW_SkRotationDirection;
1267 }
1268
1269 SkMatrix matrix;
1270 SkPoint pts[kSkBuildQuadArcStorage];
reed@google.comabf15c12011-01-18 20:35:51 +00001271
reed@android.com8a1c16f2008-12-17 15:59:43 +00001272 matrix.setScale(radius, radius);
1273 matrix.postTranslate(xx - SkScalarMul(radius, before.fX),
1274 yy - SkScalarMul(radius, before.fY));
reed@google.comabf15c12011-01-18 20:35:51 +00001275
reed@android.com8a1c16f2008-12-17 15:59:43 +00001276 int count = SkBuildQuadArc(before, after, arcDir, &matrix, pts);
reed@google.comabf15c12011-01-18 20:35:51 +00001277
reed@android.com8a1c16f2008-12-17 15:59:43 +00001278 this->incReserve(count);
1279 // [xx,yy] == pts[0]
1280 this->lineTo(xx, yy);
1281 for (int i = 1; i < count; i += 2) {
1282 this->quadTo(pts[i], pts[i+1]);
1283 }
1284}
1285
1286///////////////////////////////////////////////////////////////////////////////
1287
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001288void SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy, AddPathMode mode) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001289 SkMatrix matrix;
1290
1291 matrix.setTranslate(dx, dy);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001292 this->addPath(path, matrix, mode);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001293}
1294
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001295void SkPath::addPath(const SkPath& path, const SkMatrix& matrix, AddPathMode mode) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001296 SkPathRef::Editor(&fPathRef, path.countVerbs(), path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001297
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001298 RawIter iter(path);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001299 SkPoint pts[4];
1300 Verb verb;
1301
1302 SkMatrix::MapPtsProc proc = matrix.getMapPtsProc();
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001303 bool firstVerb = true;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001304 while ((verb = iter.next(pts)) != kDone_Verb) {
1305 switch (verb) {
1306 case kMove_Verb:
1307 proc(matrix, &pts[0], &pts[0], 1);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001308 if (firstVerb && mode == kExtend_AddPathMode && !isEmpty()) {
1309 injectMoveToIfNeeded(); // In case last contour is closed
1310 this->lineTo(pts[0]);
1311 } else {
1312 this->moveTo(pts[0]);
1313 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001314 break;
1315 case kLine_Verb:
1316 proc(matrix, &pts[1], &pts[1], 1);
1317 this->lineTo(pts[1]);
1318 break;
1319 case kQuad_Verb:
1320 proc(matrix, &pts[1], &pts[1], 2);
1321 this->quadTo(pts[1], pts[2]);
1322 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001323 case kConic_Verb:
1324 proc(matrix, &pts[1], &pts[1], 2);
1325 this->conicTo(pts[1], pts[2], iter.conicWeight());
1326 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001327 case kCubic_Verb:
1328 proc(matrix, &pts[1], &pts[1], 3);
1329 this->cubicTo(pts[1], pts[2], pts[3]);
1330 break;
1331 case kClose_Verb:
1332 this->close();
1333 break;
1334 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001335 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001336 }
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001337 firstVerb = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001338 }
1339}
1340
1341///////////////////////////////////////////////////////////////////////////////
1342
reed@google.com277c3f82013-05-31 15:17:50 +00001343static int pts_in_verb(unsigned verb) {
1344 static const uint8_t gPtsInVerb[] = {
1345 1, // kMove
1346 1, // kLine
1347 2, // kQuad
1348 2, // kConic
1349 3, // kCubic
1350 0, // kClose
1351 0 // kDone
1352 };
1353
1354 SkASSERT(verb < SK_ARRAY_COUNT(gPtsInVerb));
1355 return gPtsInVerb[verb];
1356}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001357
reed@android.com8a1c16f2008-12-17 15:59:43 +00001358// ignore the last point of the 1st contour
1359void SkPath::reversePathTo(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001360 int i, vcount = path.fPathRef->countVerbs();
1361 // exit early if the path is empty, or just has a moveTo.
1362 if (vcount < 2) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001363 return;
1364 }
1365
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001366 SkPathRef::Editor(&fPathRef, vcount, path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001367
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001368 const uint8_t* verbs = path.fPathRef->verbs();
1369 const SkPoint* pts = path.fPathRef->points();
reed@google.com277c3f82013-05-31 15:17:50 +00001370 const SkScalar* conicWeights = path.fPathRef->conicWeights();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001371
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001372 SkASSERT(verbs[~0] == kMove_Verb);
1373 for (i = 1; i < vcount; ++i) {
reed@google.com277c3f82013-05-31 15:17:50 +00001374 unsigned v = verbs[~i];
1375 int n = pts_in_verb(v);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001376 if (n == 0) {
1377 break;
1378 }
1379 pts += n;
reed@google.com277c3f82013-05-31 15:17:50 +00001380 conicWeights += (SkPath::kConic_Verb == v);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001381 }
1382
1383 while (--i > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001384 switch (verbs[~i]) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001385 case kLine_Verb:
1386 this->lineTo(pts[-1].fX, pts[-1].fY);
1387 break;
1388 case kQuad_Verb:
1389 this->quadTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY);
1390 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001391 case kConic_Verb:
1392 this->conicTo(pts[-1], pts[-2], *--conicWeights);
1393 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001394 case kCubic_Verb:
1395 this->cubicTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY,
1396 pts[-3].fX, pts[-3].fY);
1397 break;
1398 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001399 SkDEBUGFAIL("bad verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001400 break;
1401 }
reed@google.com277c3f82013-05-31 15:17:50 +00001402 pts -= pts_in_verb(verbs[~i]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001403 }
1404}
1405
reed@google.com63d73742012-01-10 15:33:12 +00001406void SkPath::reverseAddPath(const SkPath& src) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001407 SkPathRef::Editor ed(&fPathRef, src.fPathRef->countPoints(), src.fPathRef->countVerbs());
reed@google.com63d73742012-01-10 15:33:12 +00001408
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001409 const SkPoint* pts = src.fPathRef->pointsEnd();
1410 // we will iterator through src's verbs backwards
1411 const uint8_t* verbs = src.fPathRef->verbsMemBegin(); // points at the last verb
1412 const uint8_t* verbsEnd = src.fPathRef->verbs(); // points just past the first verb
reed@google.com277c3f82013-05-31 15:17:50 +00001413 const SkScalar* conicWeights = src.fPathRef->conicWeightsEnd();
reed@google.com63d73742012-01-10 15:33:12 +00001414
1415 bool needMove = true;
1416 bool needClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001417 while (verbs < verbsEnd) {
1418 uint8_t v = *(verbs++);
reed@google.com277c3f82013-05-31 15:17:50 +00001419 int n = pts_in_verb(v);
reed@google.com63d73742012-01-10 15:33:12 +00001420
1421 if (needMove) {
1422 --pts;
1423 this->moveTo(pts->fX, pts->fY);
1424 needMove = false;
1425 }
1426 pts -= n;
1427 switch (v) {
1428 case kMove_Verb:
1429 if (needClose) {
1430 this->close();
1431 needClose = false;
1432 }
1433 needMove = true;
1434 pts += 1; // so we see the point in "if (needMove)" above
1435 break;
1436 case kLine_Verb:
1437 this->lineTo(pts[0]);
1438 break;
1439 case kQuad_Verb:
1440 this->quadTo(pts[1], pts[0]);
1441 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001442 case kConic_Verb:
1443 this->conicTo(pts[1], pts[0], *--conicWeights);
1444 break;
reed@google.com63d73742012-01-10 15:33:12 +00001445 case kCubic_Verb:
1446 this->cubicTo(pts[2], pts[1], pts[0]);
1447 break;
1448 case kClose_Verb:
1449 needClose = true;
1450 break;
1451 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00001452 SkDEBUGFAIL("unexpected verb");
reed@google.com63d73742012-01-10 15:33:12 +00001453 }
1454 }
1455}
1456
reed@android.com8a1c16f2008-12-17 15:59:43 +00001457///////////////////////////////////////////////////////////////////////////////
1458
1459void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
1460 SkMatrix matrix;
1461
1462 matrix.setTranslate(dx, dy);
1463 this->transform(matrix, dst);
1464}
1465
reed@android.com8a1c16f2008-12-17 15:59:43 +00001466static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
1467 int level = 2) {
1468 if (--level >= 0) {
1469 SkPoint tmp[7];
1470
1471 SkChopCubicAtHalf(pts, tmp);
1472 subdivide_cubic_to(path, &tmp[0], level);
1473 subdivide_cubic_to(path, &tmp[3], level);
1474 } else {
1475 path->cubicTo(pts[1], pts[2], pts[3]);
1476 }
1477}
1478
1479void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
1480 SkDEBUGCODE(this->validate();)
1481 if (dst == NULL) {
1482 dst = (SkPath*)this;
1483 }
1484
tomhudson@google.com8d430182011-06-06 19:11:19 +00001485 if (matrix.hasPerspective()) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001486 SkPath tmp;
1487 tmp.fFillType = fFillType;
1488
1489 SkPath::Iter iter(*this, false);
1490 SkPoint pts[4];
1491 SkPath::Verb verb;
1492
reed@google.com4a3b7142012-05-16 17:16:46 +00001493 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001494 switch (verb) {
1495 case kMove_Verb:
1496 tmp.moveTo(pts[0]);
1497 break;
1498 case kLine_Verb:
1499 tmp.lineTo(pts[1]);
1500 break;
1501 case kQuad_Verb:
reed220f9262014-12-17 08:21:04 -08001502 // promote the quad to a conic
1503 tmp.conicTo(pts[1], pts[2],
1504 SkConic::TransformW(pts, SK_Scalar1, matrix));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001505 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001506 case kConic_Verb:
reed220f9262014-12-17 08:21:04 -08001507 tmp.conicTo(pts[1], pts[2],
1508 SkConic::TransformW(pts, iter.conicWeight(), matrix));
reed@google.com277c3f82013-05-31 15:17:50 +00001509 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001510 case kCubic_Verb:
1511 subdivide_cubic_to(&tmp, pts);
1512 break;
1513 case kClose_Verb:
1514 tmp.close();
1515 break;
1516 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001517 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001518 break;
1519 }
1520 }
1521
1522 dst->swap(tmp);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001523 SkPathRef::Editor ed(&dst->fPathRef);
1524 matrix.mapPoints(ed.points(), ed.pathRef()->countPoints());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001525 dst->fDirection = kUnknown_Direction;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001526 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001527 SkPathRef::CreateTransformedCopy(&dst->fPathRef, *fPathRef.get(), matrix);
1528
reed@android.com8a1c16f2008-12-17 15:59:43 +00001529 if (this != dst) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001530 dst->fFillType = fFillType;
reed@google.com2a6f8ab2011-10-25 18:41:23 +00001531 dst->fConvexity = fConvexity;
jvanverthb3eb6872014-10-24 07:12:51 -07001532 dst->fIsVolatile = fIsVolatile;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001533 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001534
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001535 if (kUnknown_Direction == fDirection) {
1536 dst->fDirection = kUnknown_Direction;
1537 } else {
1538 SkScalar det2x2 =
1539 SkScalarMul(matrix.get(SkMatrix::kMScaleX), matrix.get(SkMatrix::kMScaleY)) -
1540 SkScalarMul(matrix.get(SkMatrix::kMSkewX), matrix.get(SkMatrix::kMSkewY));
1541 if (det2x2 < 0) {
1542 dst->fDirection = SkPath::OppositeDirection(static_cast<Direction>(fDirection));
1543 } else if (det2x2 > 0) {
1544 dst->fDirection = fDirection;
1545 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001546 dst->fConvexity = kUnknown_Convexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001547 dst->fDirection = kUnknown_Direction;
1548 }
1549 }
1550
reed@android.com8a1c16f2008-12-17 15:59:43 +00001551 SkDEBUGCODE(dst->validate();)
1552 }
1553}
1554
reed@android.com8a1c16f2008-12-17 15:59:43 +00001555///////////////////////////////////////////////////////////////////////////////
1556///////////////////////////////////////////////////////////////////////////////
1557
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001558enum SegmentState {
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001559 kEmptyContour_SegmentState, // The current contour is empty. We may be
1560 // starting processing or we may have just
1561 // closed a contour.
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001562 kAfterMove_SegmentState, // We have seen a move, but nothing else.
1563 kAfterPrimitive_SegmentState // We have seen a primitive but not yet
1564 // closed the path. Also the initial state.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001565};
1566
1567SkPath::Iter::Iter() {
1568#ifdef SK_DEBUG
1569 fPts = NULL;
reed@google.com277c3f82013-05-31 15:17:50 +00001570 fConicWeights = NULL;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001571 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001572 fForceClose = fCloseLine = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001573 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001574#endif
1575 // need to init enough to make next() harmlessly return kDone_Verb
1576 fVerbs = NULL;
1577 fVerbStop = NULL;
1578 fNeedClose = false;
1579}
1580
1581SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
1582 this->setPath(path, forceClose);
1583}
1584
1585void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001586 fPts = path.fPathRef->points();
1587 fVerbs = path.fPathRef->verbs();
1588 fVerbStop = path.fPathRef->verbsMemBegin();
reed@google.com277c3f82013-05-31 15:17:50 +00001589 fConicWeights = path.fPathRef->conicWeights() - 1; // begin one behind
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001590 fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org72785c42011-12-29 21:03:28 +00001591 fMoveTo.fX = fMoveTo.fY = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001592 fForceClose = SkToU8(forceClose);
1593 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001594 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001595}
1596
1597bool SkPath::Iter::isClosedContour() const {
1598 if (fVerbs == NULL || fVerbs == fVerbStop) {
1599 return false;
1600 }
1601 if (fForceClose) {
1602 return true;
1603 }
1604
1605 const uint8_t* verbs = fVerbs;
1606 const uint8_t* stop = fVerbStop;
reed@google.comabf15c12011-01-18 20:35:51 +00001607
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001608 if (kMove_Verb == *(verbs - 1)) {
1609 verbs -= 1; // skip the initial moveto
reed@android.com8a1c16f2008-12-17 15:59:43 +00001610 }
1611
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001612 while (verbs > stop) {
1613 // verbs points one beyond the current verb, decrement first.
1614 unsigned v = *(--verbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001615 if (kMove_Verb == v) {
1616 break;
1617 }
1618 if (kClose_Verb == v) {
1619 return true;
1620 }
1621 }
1622 return false;
1623}
1624
1625SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001626 SkASSERT(pts);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001627 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001628 // A special case: if both points are NaN, SkPoint::operation== returns
1629 // false, but the iterator expects that they are treated as the same.
1630 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001631 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1632 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001633 return kClose_Verb;
1634 }
1635
reed@google.com9e25dbf2012-05-15 17:05:38 +00001636 pts[0] = fLastPt;
1637 pts[1] = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001638 fLastPt = fMoveTo;
1639 fCloseLine = true;
1640 return kLine_Verb;
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +00001641 } else {
1642 pts[0] = fMoveTo;
1643 return kClose_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001644 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001645}
1646
reed@google.com9e25dbf2012-05-15 17:05:38 +00001647const SkPoint& SkPath::Iter::cons_moveTo() {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001648 if (fSegmentState == kAfterMove_SegmentState) {
1649 // Set the first return pt to the move pt
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001650 fSegmentState = kAfterPrimitive_SegmentState;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001651 return fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001652 } else {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001653 SkASSERT(fSegmentState == kAfterPrimitive_SegmentState);
1654 // Set the first return pt to the last pt of the previous primitive.
reed@google.com9e25dbf2012-05-15 17:05:38 +00001655 return fPts[-1];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001656 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001657}
1658
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001659void SkPath::Iter::consumeDegenerateSegments() {
1660 // We need to step over anything that will not move the current draw point
1661 // forward before the next move is seen
1662 const uint8_t* lastMoveVerb = 0;
1663 const SkPoint* lastMovePt = 0;
robertphillipsb8de1f42015-02-23 11:17:01 -08001664 const SkScalar* lastMoveWeight = NULL;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001665 SkPoint lastPt = fLastPt;
1666 while (fVerbs != fVerbStop) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001667 unsigned verb = *(fVerbs - 1); // fVerbs is one beyond the current verb
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001668 switch (verb) {
1669 case kMove_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001670 // Keep a record of this most recent move
1671 lastMoveVerb = fVerbs;
1672 lastMovePt = fPts;
robertphillipsb8de1f42015-02-23 11:17:01 -08001673 lastMoveWeight = fConicWeights;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001674 lastPt = fPts[0];
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001675 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001676 fPts++;
1677 break;
1678
1679 case kClose_Verb:
schenney@chromium.org7e963602012-06-13 17:05:43 +00001680 // A close when we are in a segment is always valid except when it
1681 // follows a move which follows a segment.
1682 if (fSegmentState == kAfterPrimitive_SegmentState && !lastMoveVerb) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001683 return;
1684 }
1685 // A close at any other time must be ignored
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001686 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001687 break;
1688
1689 case kLine_Verb:
1690 if (!IsLineDegenerate(lastPt, fPts[0])) {
1691 if (lastMoveVerb) {
1692 fVerbs = lastMoveVerb;
1693 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001694 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001695 return;
1696 }
1697 return;
1698 }
1699 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001700 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001701 fPts++;
1702 break;
1703
reed@google.com277c3f82013-05-31 15:17:50 +00001704 case kConic_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001705 case kQuad_Verb:
1706 if (!IsQuadDegenerate(lastPt, fPts[0], fPts[1])) {
1707 if (lastMoveVerb) {
1708 fVerbs = lastMoveVerb;
1709 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001710 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001711 return;
1712 }
1713 return;
1714 }
1715 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001716 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001717 fPts += 2;
reed@google.com277c3f82013-05-31 15:17:50 +00001718 fConicWeights += (kConic_Verb == verb);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001719 break;
1720
1721 case kCubic_Verb:
1722 if (!IsCubicDegenerate(lastPt, fPts[0], fPts[1], fPts[2])) {
1723 if (lastMoveVerb) {
1724 fVerbs = lastMoveVerb;
1725 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001726 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001727 return;
1728 }
1729 return;
1730 }
1731 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001732 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001733 fPts += 3;
1734 break;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001735
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001736 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001737 SkDEBUGFAIL("Should never see kDone_Verb");
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001738 }
1739 }
1740}
1741
reed@google.com4a3b7142012-05-16 17:16:46 +00001742SkPath::Verb SkPath::Iter::doNext(SkPoint ptsParam[4]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001743 SkASSERT(ptsParam);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001744
reed@android.com8a1c16f2008-12-17 15:59:43 +00001745 if (fVerbs == fVerbStop) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001746 // Close the curve if requested and if there is some curve to close
1747 if (fNeedClose && fSegmentState == kAfterPrimitive_SegmentState) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001748 if (kLine_Verb == this->autoClose(ptsParam)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001749 return kLine_Verb;
1750 }
1751 fNeedClose = false;
1752 return kClose_Verb;
1753 }
1754 return kDone_Verb;
1755 }
1756
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001757 // fVerbs is one beyond the current verb, decrement first
1758 unsigned verb = *(--fVerbs);
reed@google.com9e25dbf2012-05-15 17:05:38 +00001759 const SkPoint* SK_RESTRICT srcPts = fPts;
1760 SkPoint* SK_RESTRICT pts = ptsParam;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001761
1762 switch (verb) {
1763 case kMove_Verb:
1764 if (fNeedClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001765 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001766 verb = this->autoClose(pts);
1767 if (verb == kClose_Verb) {
1768 fNeedClose = false;
1769 }
1770 return (Verb)verb;
1771 }
1772 if (fVerbs == fVerbStop) { // might be a trailing moveto
1773 return kDone_Verb;
1774 }
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001775 fMoveTo = *srcPts;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001776 pts[0] = *srcPts;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001777 srcPts += 1;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001778 fSegmentState = kAfterMove_SegmentState;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001779 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001780 fNeedClose = fForceClose;
1781 break;
1782 case kLine_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001783 pts[0] = this->cons_moveTo();
1784 pts[1] = srcPts[0];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001785 fLastPt = srcPts[0];
1786 fCloseLine = false;
1787 srcPts += 1;
1788 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001789 case kConic_Verb:
1790 fConicWeights += 1;
1791 // fall-through
reed@android.com8a1c16f2008-12-17 15:59:43 +00001792 case kQuad_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001793 pts[0] = this->cons_moveTo();
1794 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001795 fLastPt = srcPts[1];
1796 srcPts += 2;
1797 break;
1798 case kCubic_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001799 pts[0] = this->cons_moveTo();
1800 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001801 fLastPt = srcPts[2];
1802 srcPts += 3;
1803 break;
1804 case kClose_Verb:
1805 verb = this->autoClose(pts);
1806 if (verb == kLine_Verb) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001807 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001808 } else {
1809 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001810 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001811 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001812 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001813 break;
1814 }
1815 fPts = srcPts;
1816 return (Verb)verb;
1817}
1818
1819///////////////////////////////////////////////////////////////////////////////
1820
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001821SkPath::RawIter::RawIter() {
1822#ifdef SK_DEBUG
1823 fPts = NULL;
reed@google.com277c3f82013-05-31 15:17:50 +00001824 fConicWeights = NULL;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001825 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
1826#endif
1827 // need to init enough to make next() harmlessly return kDone_Verb
1828 fVerbs = NULL;
1829 fVerbStop = NULL;
1830}
1831
1832SkPath::RawIter::RawIter(const SkPath& path) {
1833 this->setPath(path);
1834}
1835
1836void SkPath::RawIter::setPath(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001837 fPts = path.fPathRef->points();
1838 fVerbs = path.fPathRef->verbs();
1839 fVerbStop = path.fPathRef->verbsMemBegin();
reed@google.com277c3f82013-05-31 15:17:50 +00001840 fConicWeights = path.fPathRef->conicWeights() - 1; // begin one behind
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001841 fMoveTo.fX = fMoveTo.fY = 0;
1842 fLastPt.fX = fLastPt.fY = 0;
1843}
1844
1845SkPath::Verb SkPath::RawIter::next(SkPoint pts[4]) {
bsalomon49f085d2014-09-05 13:34:00 -07001846 SkASSERT(pts);
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001847 if (fVerbs == fVerbStop) {
1848 return kDone_Verb;
1849 }
1850
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001851 // fVerbs points one beyond next verb so decrement first.
1852 unsigned verb = *(--fVerbs);
1853 const SkPoint* srcPts = fPts;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001854
1855 switch (verb) {
1856 case kMove_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001857 pts[0] = *srcPts;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001858 fMoveTo = srcPts[0];
1859 fLastPt = fMoveTo;
1860 srcPts += 1;
1861 break;
1862 case kLine_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001863 pts[0] = fLastPt;
1864 pts[1] = srcPts[0];
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001865 fLastPt = srcPts[0];
1866 srcPts += 1;
1867 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001868 case kConic_Verb:
1869 fConicWeights += 1;
1870 // fall-through
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001871 case kQuad_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001872 pts[0] = fLastPt;
1873 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001874 fLastPt = srcPts[1];
1875 srcPts += 2;
1876 break;
1877 case kCubic_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001878 pts[0] = fLastPt;
1879 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001880 fLastPt = srcPts[2];
1881 srcPts += 3;
1882 break;
1883 case kClose_Verb:
1884 fLastPt = fMoveTo;
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001885 pts[0] = fMoveTo;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001886 break;
1887 }
1888 fPts = srcPts;
1889 return (Verb)verb;
1890}
1891
1892///////////////////////////////////////////////////////////////////////////////
1893
reed@android.com8a1c16f2008-12-17 15:59:43 +00001894/*
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001895 Format in compressed buffer: [ptCount, verbCount, pts[], verbs[]]
reed@android.com8a1c16f2008-12-17 15:59:43 +00001896*/
1897
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00001898size_t SkPath::writeToMemory(void* storage) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001899 SkDEBUGCODE(this->validate();)
1900
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001901 if (NULL == storage) {
robertphillips@google.comca0c8382013-09-26 12:18:23 +00001902 const int byteCount = sizeof(int32_t) + fPathRef->writeSize();
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001903 return SkAlign4(byteCount);
1904 }
1905
1906 SkWBuffer buffer(storage);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001907
robertphillips@google.com466310d2013-12-03 16:43:54 +00001908 int32_t packed = (fConvexity << kConvexity_SerializationShift) |
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001909 (fFillType << kFillType_SerializationShift) |
jvanverthb3eb6872014-10-24 07:12:51 -07001910 (fDirection << kDirection_SerializationShift) |
1911 (fIsVolatile << kIsVolatile_SerializationShift);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001912
robertphillips@google.com2972bb52012-08-07 17:32:51 +00001913 buffer.write32(packed);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001914
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001915 fPathRef->writeToBuffer(&buffer);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001916
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001917 buffer.padToAlign4();
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00001918 return buffer.pos();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001919}
1920
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00001921size_t SkPath::readFromMemory(const void* storage, size_t length) {
1922 SkRBufferWithSizeCheck buffer(storage, length);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001923
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00001924 int32_t packed;
1925 if (!buffer.readS32(&packed)) {
1926 return 0;
1927 }
1928
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001929 fConvexity = (packed >> kConvexity_SerializationShift) & 0xFF;
1930 fFillType = (packed >> kFillType_SerializationShift) & 0xFF;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001931 fDirection = (packed >> kDirection_SerializationShift) & 0x3;
jvanverthb3eb6872014-10-24 07:12:51 -07001932 fIsVolatile = (packed >> kIsVolatile_SerializationShift) & 0x1;
commit-bot@chromium.orgfed2ab62014-01-23 15:16:05 +00001933 SkPathRef* pathRef = SkPathRef::CreateFromBuffer(&buffer);
reed@google.comabf15c12011-01-18 20:35:51 +00001934
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00001935 size_t sizeRead = 0;
1936 if (buffer.isValid()) {
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00001937 fPathRef.reset(pathRef);
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00001938 SkDEBUGCODE(this->validate();)
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00001939 buffer.skipToAlign4();
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00001940 sizeRead = buffer.pos();
bsalomon49f085d2014-09-05 13:34:00 -07001941 } else if (pathRef) {
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00001942 // If the buffer is not valid, pathRef should be NULL
1943 sk_throw();
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00001944 }
1945 return sizeRead;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001946}
1947
1948///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00001949
reede05fed02014-12-15 07:59:53 -08001950#include "SkStringUtils.h"
caryclark66a5d8b2014-06-24 08:30:15 -07001951#include "SkStream.h"
reed@google.com51bbe752013-01-17 22:07:50 +00001952
reed@google.com51bbe752013-01-17 22:07:50 +00001953static void append_params(SkString* str, const char label[], const SkPoint pts[],
reede05fed02014-12-15 07:59:53 -08001954 int count, SkScalarAsStringType strType, SkScalar conicWeight = -1) {
reed@google.com51bbe752013-01-17 22:07:50 +00001955 str->append(label);
1956 str->append("(");
skia.committer@gmail.com15dd3002013-01-18 07:07:28 +00001957
reed@google.com51bbe752013-01-17 22:07:50 +00001958 const SkScalar* values = &pts[0].fX;
1959 count *= 2;
1960
1961 for (int i = 0; i < count; ++i) {
reede05fed02014-12-15 07:59:53 -08001962 SkAppendScalar(str, values[i], strType);
reed@google.com51bbe752013-01-17 22:07:50 +00001963 if (i < count - 1) {
1964 str->append(", ");
1965 }
1966 }
reed@google.com277c3f82013-05-31 15:17:50 +00001967 if (conicWeight >= 0) {
1968 str->append(", ");
reede05fed02014-12-15 07:59:53 -08001969 SkAppendScalar(str, conicWeight, strType);
reed@google.com277c3f82013-05-31 15:17:50 +00001970 }
caryclark08fa28c2014-10-23 13:08:56 -07001971 str->append(");");
reede05fed02014-12-15 07:59:53 -08001972 if (kHex_SkScalarAsStringType == strType) {
caryclark08fa28c2014-10-23 13:08:56 -07001973 str->append(" // ");
1974 for (int i = 0; i < count; ++i) {
reede05fed02014-12-15 07:59:53 -08001975 SkAppendScalarDec(str, values[i]);
caryclark08fa28c2014-10-23 13:08:56 -07001976 if (i < count - 1) {
1977 str->append(", ");
1978 }
1979 }
1980 if (conicWeight >= 0) {
1981 str->append(", ");
reede05fed02014-12-15 07:59:53 -08001982 SkAppendScalarDec(str, conicWeight);
caryclark08fa28c2014-10-23 13:08:56 -07001983 }
1984 }
1985 str->append("\n");
reed@google.com51bbe752013-01-17 22:07:50 +00001986}
1987
caryclarke9562592014-09-15 09:26:09 -07001988void SkPath::dump(SkWStream* wStream, bool forceClose, bool dumpAsHex) const {
reede05fed02014-12-15 07:59:53 -08001989 SkScalarAsStringType asType = dumpAsHex ? kHex_SkScalarAsStringType : kDec_SkScalarAsStringType;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001990 Iter iter(*this, forceClose);
1991 SkPoint pts[4];
1992 Verb verb;
1993
caryclark66a5d8b2014-06-24 08:30:15 -07001994 if (!wStream) {
1995 SkDebugf("path: forceClose=%s\n", forceClose ? "true" : "false");
1996 }
reed@google.com51bbe752013-01-17 22:07:50 +00001997 SkString builder;
1998
reed@google.com4a3b7142012-05-16 17:16:46 +00001999 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002000 switch (verb) {
2001 case kMove_Verb:
reede05fed02014-12-15 07:59:53 -08002002 append_params(&builder, "path.moveTo", &pts[0], 1, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002003 break;
2004 case kLine_Verb:
reede05fed02014-12-15 07:59:53 -08002005 append_params(&builder, "path.lineTo", &pts[1], 1, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002006 break;
2007 case kQuad_Verb:
reede05fed02014-12-15 07:59:53 -08002008 append_params(&builder, "path.quadTo", &pts[1], 2, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002009 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002010 case kConic_Verb:
reede05fed02014-12-15 07:59:53 -08002011 append_params(&builder, "path.conicTo", &pts[1], 2, asType, iter.conicWeight());
reed@google.com277c3f82013-05-31 15:17:50 +00002012 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002013 case kCubic_Verb:
reede05fed02014-12-15 07:59:53 -08002014 append_params(&builder, "path.cubicTo", &pts[1], 3, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002015 break;
2016 case kClose_Verb:
caryclark66a5d8b2014-06-24 08:30:15 -07002017 builder.append("path.close();\n");
reed@android.com8a1c16f2008-12-17 15:59:43 +00002018 break;
2019 default:
2020 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
2021 verb = kDone_Verb; // stop the loop
2022 break;
2023 }
2024 }
caryclark66a5d8b2014-06-24 08:30:15 -07002025 if (wStream) {
2026 wStream->writeText(builder.c_str());
2027 } else {
2028 SkDebugf("%s", builder.c_str());
2029 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002030}
2031
reed@android.come522ca52009-11-23 20:10:41 +00002032void SkPath::dump() const {
caryclarke9562592014-09-15 09:26:09 -07002033 this->dump(NULL, false, false);
2034}
2035
2036void SkPath::dumpHex() const {
2037 this->dump(NULL, false, true);
reed@android.come522ca52009-11-23 20:10:41 +00002038}
2039
2040#ifdef SK_DEBUG
2041void SkPath::validate() const {
2042 SkASSERT(this != NULL);
2043 SkASSERT((fFillType & ~3) == 0);
reed@google.comabf15c12011-01-18 20:35:51 +00002044
djsollen@google.com077348c2012-10-22 20:23:32 +00002045#ifdef SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002046 if (!fBoundsIsDirty) {
2047 SkRect bounds;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002048
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002049 bool isFinite = compute_pt_bounds(&bounds, *fPathRef.get());
robertphillips@google.com5d8d1862012-08-15 14:36:41 +00002050 SkASSERT(SkToBool(fIsFinite) == isFinite);
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002051
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002052 if (fPathRef->countPoints() <= 1) {
reed@android.come522ca52009-11-23 20:10:41 +00002053 // if we're empty, fBounds may be empty but translated, so we can't
2054 // necessarily compare to bounds directly
2055 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
2056 // be [2, 2, 2, 2]
2057 SkASSERT(bounds.isEmpty());
2058 SkASSERT(fBounds.isEmpty());
2059 } else {
reed@google.comeac52bd2011-11-14 18:13:59 +00002060 if (bounds.isEmpty()) {
2061 SkASSERT(fBounds.isEmpty());
2062 } else {
reed@google.com3563c9e2011-11-14 19:34:57 +00002063 if (!fBounds.isEmpty()) {
2064 SkASSERT(fBounds.contains(bounds));
2065 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002066 }
reed@android.come522ca52009-11-23 20:10:41 +00002067 }
2068 }
djsollen@google.com077348c2012-10-22 20:23:32 +00002069#endif // SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002070}
djsollen@google.com077348c2012-10-22 20:23:32 +00002071#endif // SK_DEBUG
reed@android.come522ca52009-11-23 20:10:41 +00002072
reed@google.com04863fa2011-05-15 04:08:24 +00002073///////////////////////////////////////////////////////////////////////////////
2074
reed@google.com0b7b9822011-05-16 12:29:27 +00002075static int sign(SkScalar x) { return x < 0; }
2076#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00002077
robertphillipsc506e302014-09-16 09:43:31 -07002078enum DirChange {
2079 kLeft_DirChange,
2080 kRight_DirChange,
2081 kStraight_DirChange,
2082 kBackwards_DirChange,
2083
2084 kInvalid_DirChange
2085};
2086
2087
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002088static bool almost_equal(SkScalar compA, SkScalar compB) {
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002089 // The error epsilon was empirically derived; worse case round rects
2090 // with a mid point outset by 2x float epsilon in tests had an error
2091 // of 12.
2092 const int epsilon = 16;
2093 if (!SkScalarIsFinite(compA) || !SkScalarIsFinite(compB)) {
2094 return false;
2095 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002096 // no need to check for small numbers because SkPath::Iter has removed degenerate values
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002097 int aBits = SkFloatAs2sCompliment(compA);
2098 int bBits = SkFloatAs2sCompliment(compB);
2099 return aBits < bBits + epsilon && bBits < aBits + epsilon;
reed@google.com04863fa2011-05-15 04:08:24 +00002100}
2101
caryclarkb4216502015-03-02 13:02:34 -08002102static bool approximately_zero_when_compared_to(double x, double y) {
2103 return x == 0 || fabs(x) < fabs(y * FLT_EPSILON);
robertphillipsc506e302014-09-16 09:43:31 -07002104}
2105
caryclarkb4216502015-03-02 13:02:34 -08002106
reed@google.com04863fa2011-05-15 04:08:24 +00002107// only valid for a single contour
2108struct Convexicator {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002109 Convexicator()
2110 : fPtCount(0)
2111 , fConvexity(SkPath::kConvex_Convexity)
caryclarkd3d1a982014-12-08 04:57:38 -08002112 , fDirection(SkPath::kUnknown_Direction)
caryclark5ccef572015-03-02 10:07:56 -08002113 , fIsFinite(true)
2114 , fIsCurve(false) {
robertphillipsc506e302014-09-16 09:43:31 -07002115 fExpectedDir = kInvalid_DirChange;
reed@google.com04863fa2011-05-15 04:08:24 +00002116 // warnings
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002117 fLastPt.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002118 fCurrPt.set(0, 0);
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002119 fLastVec.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002120 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00002121
2122 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00002123 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00002124 }
2125
2126 SkPath::Convexity getConvexity() const { return fConvexity; }
2127
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002128 /** The direction returned is only valid if the path is determined convex */
2129 SkPath::Direction getDirection() const { return fDirection; }
2130
reed@google.com04863fa2011-05-15 04:08:24 +00002131 void addPt(const SkPoint& pt) {
caryclarkd3d1a982014-12-08 04:57:38 -08002132 if (SkPath::kConcave_Convexity == fConvexity || !fIsFinite) {
reed@google.com04863fa2011-05-15 04:08:24 +00002133 return;
2134 }
2135
2136 if (0 == fPtCount) {
2137 fCurrPt = pt;
2138 ++fPtCount;
2139 } else {
2140 SkVector vec = pt - fCurrPt;
caryclarkd3d1a982014-12-08 04:57:38 -08002141 SkScalar lengthSqd = vec.lengthSqd();
2142 if (!SkScalarIsFinite(lengthSqd)) {
2143 fIsFinite = false;
2144 } else if (!SkScalarNearlyZero(lengthSqd, SK_ScalarNearlyZero*SK_ScalarNearlyZero)) {
caryclarkb4216502015-03-02 13:02:34 -08002145 fPriorPt = fLastPt;
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002146 fLastPt = fCurrPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002147 fCurrPt = pt;
2148 if (++fPtCount == 2) {
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002149 fFirstVec = fLastVec = vec;
reed@google.com04863fa2011-05-15 04:08:24 +00002150 } else {
2151 SkASSERT(fPtCount > 2);
2152 this->addVec(vec);
2153 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002154
reed@google.com85b6e392011-05-15 20:25:17 +00002155 int sx = sign(vec.fX);
2156 int sy = sign(vec.fY);
2157 fDx += (sx != fSx);
2158 fDy += (sy != fSy);
2159 fSx = sx;
2160 fSy = sy;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002161
reed@google.com85b6e392011-05-15 20:25:17 +00002162 if (fDx > 3 || fDy > 3) {
2163 fConvexity = SkPath::kConcave_Convexity;
2164 }
reed@google.com04863fa2011-05-15 04:08:24 +00002165 }
2166 }
2167 }
2168
2169 void close() {
2170 if (fPtCount > 2) {
2171 this->addVec(fFirstVec);
2172 }
2173 }
2174
caryclarkb4216502015-03-02 13:02:34 -08002175 DirChange directionChange(const SkVector& curVec) {
2176 SkScalar cross = SkPoint::CrossProduct(fLastVec, curVec);
2177
2178 SkScalar smallest = SkTMin(fCurrPt.fX, SkTMin(fCurrPt.fY, SkTMin(fLastPt.fX, fLastPt.fY)));
2179 SkScalar largest = SkTMax(fCurrPt.fX, SkTMax(fCurrPt.fY, SkTMax(fLastPt.fX, fLastPt.fY)));
2180 largest = SkTMax(largest, -smallest);
2181
2182 if (!almost_equal(largest, largest + cross)) {
2183 int sign = SkScalarSignAsInt(cross);
2184 if (sign) {
2185 return (1 == sign) ? kRight_DirChange : kLeft_DirChange;
2186 }
2187 }
2188
2189 if (cross) {
2190 double dLastVecX = SkScalarToDouble(fLastPt.fX) - SkScalarToDouble(fPriorPt.fX);
2191 double dLastVecY = SkScalarToDouble(fLastPt.fY) - SkScalarToDouble(fPriorPt.fY);
2192 double dCurrVecX = SkScalarToDouble(fCurrPt.fX) - SkScalarToDouble(fLastPt.fX);
2193 double dCurrVecY = SkScalarToDouble(fCurrPt.fY) - SkScalarToDouble(fLastPt.fY);
2194 double dCross = dLastVecX * dCurrVecY - dLastVecY * dCurrVecX;
2195 if (!approximately_zero_when_compared_to(dCross, SkScalarToDouble(largest))) {
2196 int sign = SkScalarSignAsInt(SkDoubleToScalar(dCross));
2197 if (sign) {
2198 return (1 == sign) ? kRight_DirChange : kLeft_DirChange;
2199 }
2200 }
2201 }
2202
2203 if (!SkScalarNearlyZero(fLastVec.lengthSqd(), SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
2204 !SkScalarNearlyZero(curVec.lengthSqd(), SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
2205 fLastVec.dot(curVec) < 0.0f) {
2206 return kBackwards_DirChange;
2207 }
2208
2209 return kStraight_DirChange;
2210 }
2211
2212
caryclarkd3d1a982014-12-08 04:57:38 -08002213 bool isFinite() const {
2214 return fIsFinite;
2215 }
2216
caryclark5ccef572015-03-02 10:07:56 -08002217 void setCurve(bool isCurve) {
2218 fIsCurve = isCurve;
2219 }
2220
reed@google.com04863fa2011-05-15 04:08:24 +00002221private:
2222 void addVec(const SkVector& vec) {
2223 SkASSERT(vec.fX || vec.fY);
caryclarkb4216502015-03-02 13:02:34 -08002224 DirChange dir = this->directionChange(vec);
robertphillipsc506e302014-09-16 09:43:31 -07002225 switch (dir) {
2226 case kLeft_DirChange: // fall through
2227 case kRight_DirChange:
2228 if (kInvalid_DirChange == fExpectedDir) {
2229 fExpectedDir = dir;
2230 fDirection = (kRight_DirChange == dir) ? SkPath::kCW_Direction
2231 : SkPath::kCCW_Direction;
2232 } else if (dir != fExpectedDir) {
2233 fConvexity = SkPath::kConcave_Convexity;
2234 fDirection = SkPath::kUnknown_Direction;
2235 }
2236 fLastVec = vec;
2237 break;
2238 case kStraight_DirChange:
2239 break;
2240 case kBackwards_DirChange:
caryclark5ccef572015-03-02 10:07:56 -08002241 if (fIsCurve) {
2242 fConvexity = SkPath::kConcave_Convexity;
2243 fDirection = SkPath::kUnknown_Direction;
2244 }
robertphillipsc506e302014-09-16 09:43:31 -07002245 fLastVec = vec;
2246 break;
2247 case kInvalid_DirChange:
2248 SkFAIL("Use of invalid direction change flag");
2249 break;
reed@google.com04863fa2011-05-15 04:08:24 +00002250 }
2251 }
2252
caryclarkb4216502015-03-02 13:02:34 -08002253 SkPoint fPriorPt;
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002254 SkPoint fLastPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002255 SkPoint fCurrPt;
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002256 // fLastVec does not necessarily start at fLastPt. We only advance it when the cross product
2257 // value with the current vec is deemed to be of a significant value.
2258 SkVector fLastVec, fFirstVec;
reed@google.com04863fa2011-05-15 04:08:24 +00002259 int fPtCount; // non-degenerate points
robertphillipsc506e302014-09-16 09:43:31 -07002260 DirChange fExpectedDir;
reed@google.com04863fa2011-05-15 04:08:24 +00002261 SkPath::Convexity fConvexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002262 SkPath::Direction fDirection;
reed@google.com0b7b9822011-05-16 12:29:27 +00002263 int fDx, fDy, fSx, fSy;
caryclarkd3d1a982014-12-08 04:57:38 -08002264 bool fIsFinite;
caryclark5ccef572015-03-02 10:07:56 -08002265 bool fIsCurve;
reed@google.com04863fa2011-05-15 04:08:24 +00002266};
2267
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002268SkPath::Convexity SkPath::internalGetConvexity() const {
2269 SkASSERT(kUnknown_Convexity == fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002270 SkPoint pts[4];
2271 SkPath::Verb verb;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002272 SkPath::Iter iter(*this, true);
reed@google.com04863fa2011-05-15 04:08:24 +00002273
2274 int contourCount = 0;
2275 int count;
2276 Convexicator state;
2277
caryclarkd3d1a982014-12-08 04:57:38 -08002278 if (!isFinite()) {
2279 return kUnknown_Convexity;
2280 }
reed@google.com04863fa2011-05-15 04:08:24 +00002281 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
2282 switch (verb) {
2283 case kMove_Verb:
2284 if (++contourCount > 1) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002285 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002286 return kConcave_Convexity;
2287 }
2288 pts[1] = pts[0];
caryclark5ccef572015-03-02 10:07:56 -08002289 // fall through
2290 case kLine_Verb:
reed@google.com04863fa2011-05-15 04:08:24 +00002291 count = 1;
caryclark5ccef572015-03-02 10:07:56 -08002292 state.setCurve(false);
reed@google.com04863fa2011-05-15 04:08:24 +00002293 break;
caryclark5ccef572015-03-02 10:07:56 -08002294 case kQuad_Verb:
2295 // fall through
2296 case kConic_Verb:
2297 // fall through
2298 case kCubic_Verb:
2299 count = 2 + (kCubic_Verb == verb);
2300 // As an additional enhancement, this could set curve true only
2301 // if the curve is nonlinear
2302 state.setCurve(true);
2303 break;
reed@google.com04863fa2011-05-15 04:08:24 +00002304 case kClose_Verb:
caryclark5ccef572015-03-02 10:07:56 -08002305 state.setCurve(false);
reed@google.com04863fa2011-05-15 04:08:24 +00002306 state.close();
2307 count = 0;
2308 break;
2309 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00002310 SkDEBUGFAIL("bad verb");
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002311 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002312 return kConcave_Convexity;
2313 }
2314
2315 for (int i = 1; i <= count; i++) {
2316 state.addPt(pts[i]);
2317 }
2318 // early exit
caryclarkd3d1a982014-12-08 04:57:38 -08002319 if (!state.isFinite()) {
2320 return kUnknown_Convexity;
2321 }
reed@google.com04863fa2011-05-15 04:08:24 +00002322 if (kConcave_Convexity == state.getConvexity()) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002323 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002324 return kConcave_Convexity;
2325 }
2326 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002327 fConvexity = state.getConvexity();
2328 if (kConvex_Convexity == fConvexity && kUnknown_Direction == fDirection) {
2329 fDirection = state.getDirection();
2330 }
2331 return static_cast<Convexity>(fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002332}
reed@google.com69a99432012-01-10 18:00:10 +00002333
2334///////////////////////////////////////////////////////////////////////////////
2335
2336class ContourIter {
2337public:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002338 ContourIter(const SkPathRef& pathRef);
reed@google.com69a99432012-01-10 18:00:10 +00002339
2340 bool done() const { return fDone; }
2341 // if !done() then these may be called
2342 int count() const { return fCurrPtCount; }
2343 const SkPoint* pts() const { return fCurrPt; }
2344 void next();
2345
2346private:
2347 int fCurrPtCount;
2348 const SkPoint* fCurrPt;
2349 const uint8_t* fCurrVerb;
2350 const uint8_t* fStopVerbs;
reed@google.com277c3f82013-05-31 15:17:50 +00002351 const SkScalar* fCurrConicWeight;
reed@google.com69a99432012-01-10 18:00:10 +00002352 bool fDone;
reed@google.comd1ab9322012-01-10 18:40:03 +00002353 SkDEBUGCODE(int fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002354};
2355
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002356ContourIter::ContourIter(const SkPathRef& pathRef) {
2357 fStopVerbs = pathRef.verbsMemBegin();
reed@google.com69a99432012-01-10 18:00:10 +00002358 fDone = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002359 fCurrPt = pathRef.points();
2360 fCurrVerb = pathRef.verbs();
reed@google.com277c3f82013-05-31 15:17:50 +00002361 fCurrConicWeight = pathRef.conicWeights();
reed@google.com69a99432012-01-10 18:00:10 +00002362 fCurrPtCount = 0;
reed@google.comd1ab9322012-01-10 18:40:03 +00002363 SkDEBUGCODE(fContourCounter = 0;)
reed@google.com69a99432012-01-10 18:00:10 +00002364 this->next();
2365}
2366
2367void ContourIter::next() {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002368 if (fCurrVerb <= fStopVerbs) {
reed@google.com69a99432012-01-10 18:00:10 +00002369 fDone = true;
2370 }
2371 if (fDone) {
2372 return;
2373 }
2374
2375 // skip pts of prev contour
2376 fCurrPt += fCurrPtCount;
2377
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002378 SkASSERT(SkPath::kMove_Verb == fCurrVerb[~0]);
reed@google.com69a99432012-01-10 18:00:10 +00002379 int ptCount = 1; // moveTo
2380 const uint8_t* verbs = fCurrVerb;
2381
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002382 for (--verbs; verbs > fStopVerbs; --verbs) {
2383 switch (verbs[~0]) {
reed@google.com69a99432012-01-10 18:00:10 +00002384 case SkPath::kMove_Verb:
reed@google.com69a99432012-01-10 18:00:10 +00002385 goto CONTOUR_END;
2386 case SkPath::kLine_Verb:
2387 ptCount += 1;
2388 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002389 case SkPath::kConic_Verb:
2390 fCurrConicWeight += 1;
2391 // fall-through
reed@google.com69a99432012-01-10 18:00:10 +00002392 case SkPath::kQuad_Verb:
2393 ptCount += 2;
2394 break;
2395 case SkPath::kCubic_Verb:
2396 ptCount += 3;
2397 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002398 case SkPath::kClose_Verb:
2399 break;
2400 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00002401 SkDEBUGFAIL("unexpected verb");
reed@google.com69a99432012-01-10 18:00:10 +00002402 break;
2403 }
2404 }
2405CONTOUR_END:
2406 fCurrPtCount = ptCount;
2407 fCurrVerb = verbs;
reed@google.comd1ab9322012-01-10 18:40:03 +00002408 SkDEBUGCODE(++fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002409}
2410
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002411// returns cross product of (p1 - p0) and (p2 - p0)
reed@google.com69a99432012-01-10 18:00:10 +00002412static SkScalar cross_prod(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002413 SkScalar cross = SkPoint::CrossProduct(p1 - p0, p2 - p0);
2414 // We may get 0 when the above subtracts underflow. We expect this to be
2415 // very rare and lazily promote to double.
2416 if (0 == cross) {
2417 double p0x = SkScalarToDouble(p0.fX);
2418 double p0y = SkScalarToDouble(p0.fY);
2419
2420 double p1x = SkScalarToDouble(p1.fX);
2421 double p1y = SkScalarToDouble(p1.fY);
2422
2423 double p2x = SkScalarToDouble(p2.fX);
2424 double p2y = SkScalarToDouble(p2.fY);
2425
2426 cross = SkDoubleToScalar((p1x - p0x) * (p2y - p0y) -
2427 (p1y - p0y) * (p2x - p0x));
2428
2429 }
2430 return cross;
reed@google.com69a99432012-01-10 18:00:10 +00002431}
2432
reed@google.comc1ea60a2012-01-31 15:15:36 +00002433// Returns the first pt with the maximum Y coordinate
reed@google.com69a99432012-01-10 18:00:10 +00002434static int find_max_y(const SkPoint pts[], int count) {
2435 SkASSERT(count > 0);
2436 SkScalar max = pts[0].fY;
reed@google.comc1ea60a2012-01-31 15:15:36 +00002437 int firstIndex = 0;
reed@google.com69a99432012-01-10 18:00:10 +00002438 for (int i = 1; i < count; ++i) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002439 SkScalar y = pts[i].fY;
2440 if (y > max) {
2441 max = y;
2442 firstIndex = i;
reed@google.com69a99432012-01-10 18:00:10 +00002443 }
2444 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002445 return firstIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002446}
2447
reed@google.comcabaf1d2012-01-11 21:03:05 +00002448static int find_diff_pt(const SkPoint pts[], int index, int n, int inc) {
2449 int i = index;
2450 for (;;) {
2451 i = (i + inc) % n;
2452 if (i == index) { // we wrapped around, so abort
2453 break;
2454 }
2455 if (pts[index] != pts[i]) { // found a different point, success!
2456 break;
2457 }
2458 }
2459 return i;
2460}
2461
reed@google.comc1ea60a2012-01-31 15:15:36 +00002462/**
2463 * Starting at index, and moving forward (incrementing), find the xmin and
2464 * xmax of the contiguous points that have the same Y.
2465 */
2466static int find_min_max_x_at_y(const SkPoint pts[], int index, int n,
2467 int* maxIndexPtr) {
2468 const SkScalar y = pts[index].fY;
2469 SkScalar min = pts[index].fX;
2470 SkScalar max = min;
2471 int minIndex = index;
2472 int maxIndex = index;
2473 for (int i = index + 1; i < n; ++i) {
2474 if (pts[i].fY != y) {
2475 break;
2476 }
2477 SkScalar x = pts[i].fX;
2478 if (x < min) {
2479 min = x;
2480 minIndex = i;
2481 } else if (x > max) {
2482 max = x;
2483 maxIndex = i;
2484 }
2485 }
2486 *maxIndexPtr = maxIndex;
2487 return minIndex;
2488}
2489
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002490static void crossToDir(SkScalar cross, SkPath::Direction* dir) {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002491 *dir = cross > 0 ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002492}
2493
reed@google.comac8543f2012-01-30 20:51:25 +00002494/*
2495 * We loop through all contours, and keep the computed cross-product of the
2496 * contour that contained the global y-max. If we just look at the first
2497 * contour, we may find one that is wound the opposite way (correctly) since
2498 * it is the interior of a hole (e.g. 'o'). Thus we must find the contour
2499 * that is outer most (or at least has the global y-max) before we can consider
2500 * its cross product.
2501 */
reed@google.com69a99432012-01-10 18:00:10 +00002502bool SkPath::cheapComputeDirection(Direction* dir) const {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002503 if (kUnknown_Direction != fDirection) {
2504 *dir = static_cast<Direction>(fDirection);
2505 return true;
2506 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002507
2508 // don't want to pay the cost for computing this if it
2509 // is unknown, so we don't call isConvex()
2510 if (kConvex_Convexity == this->getConvexityOrUnknown()) {
2511 SkASSERT(kUnknown_Direction == fDirection);
2512 *dir = static_cast<Direction>(fDirection);
2513 return false;
2514 }
reed@google.com69a99432012-01-10 18:00:10 +00002515
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002516 ContourIter iter(*fPathRef.get());
reed@google.com69a99432012-01-10 18:00:10 +00002517
reed@google.comac8543f2012-01-30 20:51:25 +00002518 // initialize with our logical y-min
2519 SkScalar ymax = this->getBounds().fTop;
2520 SkScalar ymaxCross = 0;
2521
reed@google.com69a99432012-01-10 18:00:10 +00002522 for (; !iter.done(); iter.next()) {
2523 int n = iter.count();
reed@google.comcabaf1d2012-01-11 21:03:05 +00002524 if (n < 3) {
2525 continue;
2526 }
djsollen@google.come63793a2012-03-21 15:39:03 +00002527
reed@google.comcabaf1d2012-01-11 21:03:05 +00002528 const SkPoint* pts = iter.pts();
reed@google.com69a99432012-01-10 18:00:10 +00002529 SkScalar cross = 0;
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002530 int index = find_max_y(pts, n);
2531 if (pts[index].fY < ymax) {
2532 continue;
2533 }
2534
2535 // If there is more than 1 distinct point at the y-max, we take the
2536 // x-min and x-max of them and just subtract to compute the dir.
2537 if (pts[(index + 1) % n].fY == pts[index].fY) {
2538 int maxIndex;
2539 int minIndex = find_min_max_x_at_y(pts, index, n, &maxIndex);
2540 if (minIndex == maxIndex) {
2541 goto TRY_CROSSPROD;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002542 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002543 SkASSERT(pts[minIndex].fY == pts[index].fY);
2544 SkASSERT(pts[maxIndex].fY == pts[index].fY);
2545 SkASSERT(pts[minIndex].fX <= pts[maxIndex].fX);
2546 // we just subtract the indices, and let that auto-convert to
2547 // SkScalar, since we just want - or + to signal the direction.
2548 cross = minIndex - maxIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002549 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002550 TRY_CROSSPROD:
2551 // Find a next and prev index to use for the cross-product test,
2552 // but we try to find pts that form non-zero vectors from pts[index]
2553 //
2554 // Its possible that we can't find two non-degenerate vectors, so
2555 // we have to guard our search (e.g. all the pts could be in the
2556 // same place).
2557
2558 // we pass n - 1 instead of -1 so we don't foul up % operator by
2559 // passing it a negative LH argument.
2560 int prev = find_diff_pt(pts, index, n, n - 1);
2561 if (prev == index) {
2562 // completely degenerate, skip to next contour
reed@google.comac8543f2012-01-30 20:51:25 +00002563 continue;
2564 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002565 int next = find_diff_pt(pts, index, n, 1);
2566 SkASSERT(next != index);
2567 cross = cross_prod(pts[prev], pts[index], pts[next]);
2568 // if we get a zero and the points are horizontal, then we look at the spread in
2569 // x-direction. We really should continue to walk away from the degeneracy until
2570 // there is a divergence.
2571 if (0 == cross && pts[prev].fY == pts[index].fY && pts[next].fY == pts[index].fY) {
2572 // construct the subtract so we get the correct Direction below
2573 cross = pts[index].fX - pts[next].fX;
reed@google.com188bfcf2012-01-17 18:26:38 +00002574 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002575 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002576
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002577 if (cross) {
2578 // record our best guess so far
2579 ymax = pts[index].fY;
2580 ymaxCross = cross;
reed@google.com69a99432012-01-10 18:00:10 +00002581 }
2582 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002583 if (ymaxCross) {
2584 crossToDir(ymaxCross, dir);
2585 fDirection = *dir;
2586 return true;
2587 } else {
2588 return false;
2589 }
reed@google.comac8543f2012-01-30 20:51:25 +00002590}
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002591
2592///////////////////////////////////////////////////////////////////////////////
2593
2594static SkScalar eval_cubic_coeff(SkScalar A, SkScalar B, SkScalar C,
2595 SkScalar D, SkScalar t) {
2596 return SkScalarMulAdd(SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C), t, D);
2597}
2598
2599static SkScalar eval_cubic_pts(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2600 SkScalar t) {
2601 SkScalar A = c3 + 3*(c1 - c2) - c0;
2602 SkScalar B = 3*(c2 - c1 - c1 + c0);
2603 SkScalar C = 3*(c1 - c0);
2604 SkScalar D = c0;
2605 return eval_cubic_coeff(A, B, C, D, t);
2606}
2607
2608/* Given 4 cubic points (either Xs or Ys), and a target X or Y, compute the
2609 t value such that cubic(t) = target
2610 */
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002611static void chopMonoCubicAt(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002612 SkScalar target, SkScalar* t) {
2613 // SkASSERT(c0 <= c1 && c1 <= c2 && c2 <= c3);
2614 SkASSERT(c0 < target && target < c3);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002615
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002616 SkScalar D = c0 - target;
2617 SkScalar A = c3 + 3*(c1 - c2) - c0;
2618 SkScalar B = 3*(c2 - c1 - c1 + c0);
2619 SkScalar C = 3*(c1 - c0);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002620
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002621 const SkScalar TOLERANCE = SK_Scalar1 / 4096;
2622 SkScalar minT = 0;
2623 SkScalar maxT = SK_Scalar1;
2624 SkScalar mid;
2625 int i;
2626 for (i = 0; i < 16; i++) {
2627 mid = SkScalarAve(minT, maxT);
2628 SkScalar delta = eval_cubic_coeff(A, B, C, D, mid);
2629 if (delta < 0) {
2630 minT = mid;
2631 delta = -delta;
2632 } else {
2633 maxT = mid;
2634 }
2635 if (delta < TOLERANCE) {
2636 break;
2637 }
2638 }
2639 *t = mid;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002640}
2641
2642template <size_t N> static void find_minmax(const SkPoint pts[],
2643 SkScalar* minPtr, SkScalar* maxPtr) {
2644 SkScalar min, max;
2645 min = max = pts[0].fX;
2646 for (size_t i = 1; i < N; ++i) {
2647 min = SkMinScalar(min, pts[i].fX);
2648 max = SkMaxScalar(max, pts[i].fX);
2649 }
2650 *minPtr = min;
2651 *maxPtr = max;
2652}
2653
2654static int winding_mono_cubic(const SkPoint pts[], SkScalar x, SkScalar y) {
2655 SkPoint storage[4];
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002656
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002657 int dir = 1;
2658 if (pts[0].fY > pts[3].fY) {
2659 storage[0] = pts[3];
2660 storage[1] = pts[2];
2661 storage[2] = pts[1];
2662 storage[3] = pts[0];
2663 pts = storage;
2664 dir = -1;
2665 }
2666 if (y < pts[0].fY || y >= pts[3].fY) {
2667 return 0;
2668 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002669
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002670 // quickreject or quickaccept
2671 SkScalar min, max;
2672 find_minmax<4>(pts, &min, &max);
2673 if (x < min) {
2674 return 0;
2675 }
2676 if (x > max) {
2677 return dir;
2678 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002679
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002680 // compute the actual x(t) value
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002681 SkScalar t;
2682 chopMonoCubicAt(pts[0].fY, pts[1].fY, pts[2].fY, pts[3].fY, y, &t);
2683 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 +00002684 return xt < x ? dir : 0;
2685}
2686
2687static int winding_cubic(const SkPoint pts[], SkScalar x, SkScalar y) {
2688 SkPoint dst[10];
2689 int n = SkChopCubicAtYExtrema(pts, dst);
2690 int w = 0;
2691 for (int i = 0; i <= n; ++i) {
2692 w += winding_mono_cubic(&dst[i * 3], x, y);
2693 }
2694 return w;
2695}
2696
2697static int winding_mono_quad(const SkPoint pts[], SkScalar x, SkScalar y) {
2698 SkScalar y0 = pts[0].fY;
2699 SkScalar y2 = pts[2].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002700
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002701 int dir = 1;
2702 if (y0 > y2) {
2703 SkTSwap(y0, y2);
2704 dir = -1;
2705 }
2706 if (y < y0 || y >= y2) {
2707 return 0;
2708 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002709
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002710 // bounds check on X (not required. is it faster?)
2711#if 0
2712 if (pts[0].fX > x && pts[1].fX > x && pts[2].fX > x) {
2713 return 0;
2714 }
2715#endif
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002716
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002717 SkScalar roots[2];
2718 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
2719 2 * (pts[1].fY - pts[0].fY),
2720 pts[0].fY - y,
2721 roots);
2722 SkASSERT(n <= 1);
2723 SkScalar xt;
2724 if (0 == n) {
2725 SkScalar mid = SkScalarAve(y0, y2);
2726 // Need [0] and [2] if dir == 1
2727 // and [2] and [0] if dir == -1
2728 xt = y < mid ? pts[1 - dir].fX : pts[dir - 1].fX;
2729 } else {
2730 SkScalar t = roots[0];
2731 SkScalar C = pts[0].fX;
2732 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
2733 SkScalar B = 2 * (pts[1].fX - C);
2734 xt = SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C);
2735 }
2736 return xt < x ? dir : 0;
2737}
2738
2739static bool is_mono_quad(SkScalar y0, SkScalar y1, SkScalar y2) {
2740 // return SkScalarSignAsInt(y0 - y1) + SkScalarSignAsInt(y1 - y2) != 0;
2741 if (y0 == y1) {
2742 return true;
2743 }
2744 if (y0 < y1) {
2745 return y1 <= y2;
2746 } else {
2747 return y1 >= y2;
2748 }
2749}
2750
2751static int winding_quad(const SkPoint pts[], SkScalar x, SkScalar y) {
2752 SkPoint dst[5];
2753 int n = 0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002754
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002755 if (!is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY)) {
2756 n = SkChopQuadAtYExtrema(pts, dst);
2757 pts = dst;
2758 }
2759 int w = winding_mono_quad(pts, x, y);
2760 if (n > 0) {
2761 w += winding_mono_quad(&pts[2], x, y);
2762 }
2763 return w;
2764}
2765
2766static int winding_line(const SkPoint pts[], SkScalar x, SkScalar y) {
2767 SkScalar x0 = pts[0].fX;
2768 SkScalar y0 = pts[0].fY;
2769 SkScalar x1 = pts[1].fX;
2770 SkScalar y1 = pts[1].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002771
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002772 SkScalar dy = y1 - y0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002773
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002774 int dir = 1;
2775 if (y0 > y1) {
2776 SkTSwap(y0, y1);
2777 dir = -1;
2778 }
2779 if (y < y0 || y >= y1) {
2780 return 0;
2781 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002782
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002783 SkScalar cross = SkScalarMul(x1 - x0, y - pts[0].fY) -
2784 SkScalarMul(dy, x - pts[0].fX);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002785
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002786 if (SkScalarSignAsInt(cross) == dir) {
2787 dir = 0;
2788 }
2789 return dir;
2790}
2791
reed@google.com4db592c2013-10-30 17:39:43 +00002792static bool contains_inclusive(const SkRect& r, SkScalar x, SkScalar y) {
2793 return r.fLeft <= x && x <= r.fRight && r.fTop <= y && y <= r.fBottom;
2794}
2795
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002796bool SkPath::contains(SkScalar x, SkScalar y) const {
2797 bool isInverse = this->isInverseFillType();
2798 if (this->isEmpty()) {
2799 return isInverse;
2800 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002801
reed@google.com4db592c2013-10-30 17:39:43 +00002802 if (!contains_inclusive(this->getBounds(), x, y)) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002803 return isInverse;
2804 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002805
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002806 SkPath::Iter iter(*this, true);
2807 bool done = false;
2808 int w = 0;
2809 do {
2810 SkPoint pts[4];
2811 switch (iter.next(pts, false)) {
2812 case SkPath::kMove_Verb:
2813 case SkPath::kClose_Verb:
2814 break;
2815 case SkPath::kLine_Verb:
2816 w += winding_line(pts, x, y);
2817 break;
2818 case SkPath::kQuad_Verb:
2819 w += winding_quad(pts, x, y);
2820 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002821 case SkPath::kConic_Verb:
2822 SkASSERT(0);
2823 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002824 case SkPath::kCubic_Verb:
2825 w += winding_cubic(pts, x, y);
2826 break;
2827 case SkPath::kDone_Verb:
2828 done = true;
2829 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002830 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002831 } while (!done);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002832
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002833 switch (this->getFillType()) {
2834 case SkPath::kEvenOdd_FillType:
2835 case SkPath::kInverseEvenOdd_FillType:
2836 w &= 1;
2837 break;
reed@google.come9bb6232012-07-11 18:56:10 +00002838 default:
2839 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002840 }
2841 return SkToBool(w);
2842}