blob: 19e7048869a4adb34703b389c2970361037739e3 [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;
caryclark95bc5f32015-04-08 08:34:15 -0700399 bool insertClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000400 int verbCnt = fPathRef->countVerbs();
caryclark@google.com56f233a2012-11-19 13:06:06 +0000401 while (*currVerb < verbCnt && (!allowPartial || !autoClose)) {
caryclark95bc5f32015-04-08 08:34:15 -0700402 uint8_t verb = insertClose ? (uint8_t) kClose_Verb : fPathRef->atVerb(*currVerb);
403 switch (verb) {
caryclark@google.comf1316942011-07-26 19:54:45 +0000404 case kClose_Verb:
caryclark@google.com56f233a2012-11-19 13:06:06 +0000405 savePts = pts;
406 pts = *ptsPtr;
caryclark@google.comf1316942011-07-26 19:54:45 +0000407 autoClose = true;
caryclark95bc5f32015-04-08 08:34:15 -0700408 insertClose = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000409 case kLine_Verb: {
410 SkScalar left = last.fX;
411 SkScalar top = last.fY;
412 SkScalar right = pts->fX;
413 SkScalar bottom = pts->fY;
414 ++pts;
415 if (left != right && top != bottom) {
416 return false; // diagonal
417 }
418 if (left == right && top == bottom) {
419 break; // single point on side OK
420 }
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000421 nextDirection = rect_make_dir(right - left, bottom - top);
caryclark@google.comf1316942011-07-26 19:54:45 +0000422 if (0 == corners) {
423 firstDirection = nextDirection;
424 first = last;
425 last = pts[-1];
426 corners = 1;
427 closedOrMoved = false;
428 break;
429 }
430 if (closedOrMoved) {
431 return false; // closed followed by a line
432 }
caryclark@google.combfe90372012-11-21 13:56:20 +0000433 if (autoClose && nextDirection == firstDirection) {
434 break; // colinear with first
435 }
caryclark@google.comf1316942011-07-26 19:54:45 +0000436 closedOrMoved = autoClose;
437 if (lastDirection != nextDirection) {
438 if (++corners > 4) {
439 return false; // too many direction changes
440 }
441 }
442 last = pts[-1];
443 if (lastDirection == nextDirection) {
444 break; // colinear segment
445 }
446 // Possible values for corners are 2, 3, and 4.
447 // When corners == 3, nextDirection opposes firstDirection.
448 // Otherwise, nextDirection at corner 2 opposes corner 4.
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000449 int turn = firstDirection ^ (corners - 1);
caryclark@google.comf1316942011-07-26 19:54:45 +0000450 int directionCycle = 3 == corners ? 0 : nextDirection ^ turn;
451 if ((directionCycle ^ turn) != nextDirection) {
452 return false; // direction didn't follow cycle
453 }
454 break;
455 }
456 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000457 case kConic_Verb:
caryclark@google.comf1316942011-07-26 19:54:45 +0000458 case kCubic_Verb:
459 return false; // quadratic, cubic not allowed
460 case kMove_Verb:
caryclark95bc5f32015-04-08 08:34:15 -0700461 if (allowPartial && !autoClose && firstDirection) {
462 insertClose = true;
463 *currVerb -= 1; // try move again afterwards
464 goto addMissingClose;
465 }
caryclark@google.comf1316942011-07-26 19:54:45 +0000466 last = *pts++;
467 closedOrMoved = true;
468 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000469 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000470 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000471 break;
caryclark@google.comf1316942011-07-26 19:54:45 +0000472 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000473 *currVerb += 1;
caryclark@google.comf1316942011-07-26 19:54:45 +0000474 lastDirection = nextDirection;
caryclark95bc5f32015-04-08 08:34:15 -0700475addMissingClose:
476 ;
caryclark@google.comf1316942011-07-26 19:54:45 +0000477 }
478 // Success if 4 corners and first point equals last
caryclark@google.combfe90372012-11-21 13:56:20 +0000479 bool result = 4 == corners && (first == last || autoClose);
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000480 if (!result) {
481 // check if we are just an incomplete rectangle, in which case we can
482 // return true, but not claim to be closed.
483 // e.g.
484 // 3 sided rectangle
485 // 4 sided but the last edge is not long enough to reach the start
486 //
487 SkScalar closeX = first.x() - last.x();
488 SkScalar closeY = first.y() - last.y();
489 if (closeX && closeY) {
490 return false; // we're diagonal, abort (can we ever reach this?)
491 }
492 int closeDirection = rect_make_dir(closeX, closeY);
493 // make sure the close-segment doesn't double-back on itself
494 if (3 == corners || (4 == corners && closeDirection == lastDirection)) {
495 result = true;
496 autoClose = false; // we are not closed
497 }
498 }
caryclark@google.combfe90372012-11-21 13:56:20 +0000499 if (savePts) {
500 *ptsPtr = savePts;
501 }
caryclark@google.comf68154a2012-11-21 15:18:06 +0000502 if (result && isClosed) {
503 *isClosed = autoClose;
504 }
505 if (result && direction) {
sugoi@google.com12b4e272012-12-06 20:13:11 +0000506 *direction = firstDirection == ((lastDirection + 1) & 3) ? kCCW_Direction : kCW_Direction;
caryclark@google.comf68154a2012-11-21 15:18:06 +0000507 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000508 return result;
509}
510
robertphillips4f662e62014-12-29 14:06:51 -0800511bool SkPath::isRect(SkRect* rect, bool* isClosed, Direction* direction) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000512 SkDEBUGCODE(this->validate();)
513 int currVerb = 0;
514 const SkPoint* pts = fPathRef->points();
robertphillipsfe7c4272014-12-29 11:36:39 -0800515 const SkPoint* first = pts;
robertphillips4f662e62014-12-29 14:06:51 -0800516 if (!this->isRectContour(false, &currVerb, &pts, isClosed, direction)) {
robertphillipsfe7c4272014-12-29 11:36:39 -0800517 return false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000518 }
robertphillipsfe7c4272014-12-29 11:36:39 -0800519 if (rect) {
robertphillips4f662e62014-12-29 14:06:51 -0800520 int32_t num = SkToS32(pts - first);
521 if (num) {
522 rect->set(first, num);
robertphillipsfe7c4272014-12-29 11:36:39 -0800523 } else {
524 // 'pts' isn't updated for open rects
525 *rect = this->getBounds();
526 }
527 }
528 return true;
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000529}
skia.committer@gmail.com020b25b2013-06-22 07:00:58 +0000530
caryclark95bc5f32015-04-08 08:34:15 -0700531bool SkPath::isNestedFillRects(SkRect rects[2], Direction dirs[2]) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000532 SkDEBUGCODE(this->validate();)
533 int currVerb = 0;
534 const SkPoint* pts = fPathRef->points();
535 const SkPoint* first = pts;
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000536 Direction testDirs[2];
537 if (!isRectContour(true, &currVerb, &pts, NULL, &testDirs[0])) {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000538 return false;
539 }
540 const SkPoint* last = pts;
541 SkRect testRects[2];
caryclark95bc5f32015-04-08 08:34:15 -0700542 bool isClosed;
543 if (isRectContour(false, &currVerb, &pts, &isClosed, &testDirs[1])) {
scroggo@google.com614f9e32013-05-09 18:05:32 +0000544 testRects[0].set(first, SkToS32(last - first));
caryclark95bc5f32015-04-08 08:34:15 -0700545 if (!isClosed) {
546 pts = fPathRef->points() + fPathRef->countPoints();
547 }
scroggo@google.com614f9e32013-05-09 18:05:32 +0000548 testRects[1].set(last, SkToS32(pts - last));
caryclark@google.com56f233a2012-11-19 13:06:06 +0000549 if (testRects[0].contains(testRects[1])) {
550 if (rects) {
551 rects[0] = testRects[0];
552 rects[1] = testRects[1];
553 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000554 if (dirs) {
555 dirs[0] = testDirs[0];
556 dirs[1] = testDirs[1];
557 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000558 return true;
559 }
560 if (testRects[1].contains(testRects[0])) {
561 if (rects) {
562 rects[0] = testRects[1];
563 rects[1] = testRects[0];
564 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000565 if (dirs) {
566 dirs[0] = testDirs[1];
567 dirs[1] = testDirs[0];
568 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000569 return true;
570 }
571 }
572 return false;
573}
574
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000575int SkPath::countPoints() const {
576 return fPathRef->countPoints();
577}
578
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000579int SkPath::getPoints(SkPoint dst[], int max) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000580 SkDEBUGCODE(this->validate();)
581
582 SkASSERT(max >= 0);
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000583 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000584 int count = SkMin32(max, fPathRef->countPoints());
585 memcpy(dst, fPathRef->points(), count * sizeof(SkPoint));
586 return fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000587}
588
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000589SkPoint SkPath::getPoint(int index) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000590 if ((unsigned)index < (unsigned)fPathRef->countPoints()) {
591 return fPathRef->atPoint(index);
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000592 }
593 return SkPoint::Make(0, 0);
594}
595
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000596int SkPath::countVerbs() const {
597 return fPathRef->countVerbs();
598}
599
600static inline void copy_verbs_reverse(uint8_t* inorderDst,
601 const uint8_t* reversedSrc,
602 int count) {
603 for (int i = 0; i < count; ++i) {
604 inorderDst[i] = reversedSrc[~i];
605 }
606}
607
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000608int SkPath::getVerbs(uint8_t dst[], int max) const {
609 SkDEBUGCODE(this->validate();)
610
611 SkASSERT(max >= 0);
612 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000613 int count = SkMin32(max, fPathRef->countVerbs());
614 copy_verbs_reverse(dst, fPathRef->verbs(), count);
615 return fPathRef->countVerbs();
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000616}
617
reed@google.com294dd7b2011-10-11 11:58:32 +0000618bool SkPath::getLastPt(SkPoint* lastPt) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000619 SkDEBUGCODE(this->validate();)
620
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000621 int count = fPathRef->countPoints();
reed@google.com294dd7b2011-10-11 11:58:32 +0000622 if (count > 0) {
623 if (lastPt) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000624 *lastPt = fPathRef->atPoint(count - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000625 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000626 return true;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000627 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000628 if (lastPt) {
629 lastPt->set(0, 0);
630 }
631 return false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000632}
633
634void SkPath::setLastPt(SkScalar x, SkScalar y) {
635 SkDEBUGCODE(this->validate();)
636
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000637 int count = fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000638 if (count == 0) {
639 this->moveTo(x, y);
640 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000641 SkPathRef::Editor ed(&fPathRef);
642 ed.atPoint(count-1)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000643 }
644}
645
reed@google.com04863fa2011-05-15 04:08:24 +0000646void SkPath::setConvexity(Convexity c) {
647 if (fConvexity != c) {
648 fConvexity = c;
reed@google.com04863fa2011-05-15 04:08:24 +0000649 }
650}
651
reed@android.com8a1c16f2008-12-17 15:59:43 +0000652//////////////////////////////////////////////////////////////////////////////
653// Construction methods
654
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000655#define DIRTY_AFTER_EDIT \
656 do { \
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000657 fConvexity = kUnknown_Convexity; \
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000658 fDirection = kUnknown_Direction; \
reed@google.comb54455e2011-05-16 14:16:04 +0000659 } while (0)
660
reed@android.com8a1c16f2008-12-17 15:59:43 +0000661void SkPath::incReserve(U16CPU inc) {
662 SkDEBUGCODE(this->validate();)
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000663 SkPathRef::Editor(&fPathRef, inc, inc);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000664 SkDEBUGCODE(this->validate();)
665}
666
667void SkPath::moveTo(SkScalar x, SkScalar y) {
668 SkDEBUGCODE(this->validate();)
669
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000670 SkPathRef::Editor ed(&fPathRef);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000671
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000672 // remember our index
673 fLastMoveToIndex = fPathRef->countPoints();
674
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000675 ed.growForVerb(kMove_Verb)->set(x, y);
bsalomonb17c1292014-08-28 14:04:55 -0700676
677 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000678}
679
680void SkPath::rMoveTo(SkScalar x, SkScalar y) {
681 SkPoint pt;
682 this->getLastPt(&pt);
683 this->moveTo(pt.fX + x, pt.fY + y);
684}
685
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000686void SkPath::injectMoveToIfNeeded() {
687 if (fLastMoveToIndex < 0) {
688 SkScalar x, y;
689 if (fPathRef->countVerbs() == 0) {
690 x = y = 0;
691 } else {
692 const SkPoint& pt = fPathRef->atPoint(~fLastMoveToIndex);
693 x = pt.fX;
694 y = pt.fY;
695 }
696 this->moveTo(x, y);
697 }
698}
699
reed@android.com8a1c16f2008-12-17 15:59:43 +0000700void SkPath::lineTo(SkScalar x, SkScalar y) {
701 SkDEBUGCODE(this->validate();)
702
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000703 this->injectMoveToIfNeeded();
704
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000705 SkPathRef::Editor ed(&fPathRef);
706 ed.growForVerb(kLine_Verb)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000707
reed@google.comb54455e2011-05-16 14:16:04 +0000708 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000709}
710
711void SkPath::rLineTo(SkScalar x, SkScalar y) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000712 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000713 SkPoint pt;
714 this->getLastPt(&pt);
715 this->lineTo(pt.fX + x, pt.fY + y);
716}
717
718void SkPath::quadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
719 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000720
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000721 this->injectMoveToIfNeeded();
722
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000723 SkPathRef::Editor ed(&fPathRef);
724 SkPoint* pts = ed.growForVerb(kQuad_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000725 pts[0].set(x1, y1);
726 pts[1].set(x2, y2);
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000727
reed@google.comb54455e2011-05-16 14:16:04 +0000728 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000729}
730
731void SkPath::rQuadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000732 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000733 SkPoint pt;
734 this->getLastPt(&pt);
735 this->quadTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2);
736}
737
reed@google.com277c3f82013-05-31 15:17:50 +0000738void SkPath::conicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
739 SkScalar w) {
740 // check for <= 0 or NaN with this test
741 if (!(w > 0)) {
742 this->lineTo(x2, y2);
743 } else if (!SkScalarIsFinite(w)) {
744 this->lineTo(x1, y1);
745 this->lineTo(x2, y2);
746 } else if (SK_Scalar1 == w) {
747 this->quadTo(x1, y1, x2, y2);
748 } else {
749 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000750
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000751 this->injectMoveToIfNeeded();
752
reed@google.com277c3f82013-05-31 15:17:50 +0000753 SkPathRef::Editor ed(&fPathRef);
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000754 SkPoint* pts = ed.growForVerb(kConic_Verb, w);
reed@google.com277c3f82013-05-31 15:17:50 +0000755 pts[0].set(x1, y1);
756 pts[1].set(x2, y2);
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000757
reed@google.com277c3f82013-05-31 15:17:50 +0000758 DIRTY_AFTER_EDIT;
759 }
760}
761
762void SkPath::rConicTo(SkScalar dx1, SkScalar dy1, SkScalar dx2, SkScalar dy2,
763 SkScalar w) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000764 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@google.com277c3f82013-05-31 15:17:50 +0000765 SkPoint pt;
766 this->getLastPt(&pt);
767 this->conicTo(pt.fX + dx1, pt.fY + dy1, pt.fX + dx2, pt.fY + dy2, w);
768}
769
reed@android.com8a1c16f2008-12-17 15:59:43 +0000770void SkPath::cubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
771 SkScalar x3, SkScalar y3) {
772 SkDEBUGCODE(this->validate();)
773
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000774 this->injectMoveToIfNeeded();
775
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000776 SkPathRef::Editor ed(&fPathRef);
777 SkPoint* pts = ed.growForVerb(kCubic_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000778 pts[0].set(x1, y1);
779 pts[1].set(x2, y2);
780 pts[2].set(x3, y3);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000781
reed@google.comb54455e2011-05-16 14:16:04 +0000782 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000783}
784
785void SkPath::rCubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
786 SkScalar x3, SkScalar y3) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000787 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000788 SkPoint pt;
789 this->getLastPt(&pt);
790 this->cubicTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2,
791 pt.fX + x3, pt.fY + y3);
792}
793
794void SkPath::close() {
795 SkDEBUGCODE(this->validate();)
796
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000797 int count = fPathRef->countVerbs();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000798 if (count > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000799 switch (fPathRef->atVerb(count - 1)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000800 case kLine_Verb:
801 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000802 case kConic_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +0000803 case kCubic_Verb:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000804 case kMove_Verb: {
805 SkPathRef::Editor ed(&fPathRef);
806 ed.growForVerb(kClose_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000807 break;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000808 }
reed@google.com277c3f82013-05-31 15:17:50 +0000809 case kClose_Verb:
reed@google.comfa2f2a42013-05-30 15:29:48 +0000810 // don't add a close if it's the first verb or a repeat
reed@google.com7950a9e2013-05-30 14:57:55 +0000811 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000812 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000813 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000814 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000815 }
816 }
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000817
818 // signal that we need a moveTo to follow us (unless we're done)
819#if 0
820 if (fLastMoveToIndex >= 0) {
821 fLastMoveToIndex = ~fLastMoveToIndex;
822 }
823#else
824 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
825#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000826}
827
828///////////////////////////////////////////////////////////////////////////////
reed@google.comabf15c12011-01-18 20:35:51 +0000829
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000830static void assert_known_direction(int dir) {
831 SkASSERT(SkPath::kCW_Direction == dir || SkPath::kCCW_Direction == dir);
832}
833
reed@android.com8a1c16f2008-12-17 15:59:43 +0000834void SkPath::addRect(const SkRect& rect, Direction dir) {
835 this->addRect(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, dir);
836}
837
838void SkPath::addRect(SkScalar left, SkScalar top, SkScalar right,
839 SkScalar bottom, Direction dir) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000840 assert_known_direction(dir);
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000841 fDirection = this->hasOnlyMoveTos() ? dir : kUnknown_Direction;
842 SkAutoDisableDirectionCheck addc(this);
843
reed@android.com8a1c16f2008-12-17 15:59:43 +0000844 SkAutoPathBoundsUpdate apbu(this, left, top, right, bottom);
845
846 this->incReserve(5);
847
848 this->moveTo(left, top);
849 if (dir == kCCW_Direction) {
850 this->lineTo(left, bottom);
851 this->lineTo(right, bottom);
852 this->lineTo(right, top);
853 } else {
854 this->lineTo(right, top);
855 this->lineTo(right, bottom);
856 this->lineTo(left, bottom);
857 }
858 this->close();
859}
860
reed@google.com744faba2012-05-29 19:54:52 +0000861void SkPath::addPoly(const SkPoint pts[], int count, bool close) {
862 SkDEBUGCODE(this->validate();)
863 if (count <= 0) {
864 return;
865 }
866
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000867 fLastMoveToIndex = fPathRef->countPoints();
868
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000869 // +close makes room for the extra kClose_Verb
870 SkPathRef::Editor ed(&fPathRef, count+close, count);
871
872 ed.growForVerb(kMove_Verb)->set(pts[0].fX, pts[0].fY);
reed@google.com744faba2012-05-29 19:54:52 +0000873 if (count > 1) {
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000874 SkPoint* p = ed.growForRepeatedVerb(kLine_Verb, count - 1);
875 memcpy(p, &pts[1], (count-1) * sizeof(SkPoint));
reed@google.com744faba2012-05-29 19:54:52 +0000876 }
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000877
reed@google.com744faba2012-05-29 19:54:52 +0000878 if (close) {
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000879 ed.growForVerb(kClose_Verb);
caryclark63c684a2015-02-25 09:04:04 -0800880 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
reed@google.com744faba2012-05-29 19:54:52 +0000881 }
882
reed@google.com744faba2012-05-29 19:54:52 +0000883 DIRTY_AFTER_EDIT;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000884 SkDEBUGCODE(this->validate();)
reed@google.com744faba2012-05-29 19:54:52 +0000885}
886
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000887#include "SkGeometry.h"
888
reedf90ea012015-01-29 12:03:58 -0800889static bool arc_is_lone_point(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
890 SkPoint* pt) {
891 if (0 == sweepAngle && (0 == startAngle || SkIntToScalar(360) == startAngle)) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000892 // Chrome uses this path to move into and out of ovals. If not
893 // treated as a special case the moves can distort the oval's
894 // bounding box (and break the circle special case).
reedf90ea012015-01-29 12:03:58 -0800895 pt->set(oval.fRight, oval.centerY());
896 return true;
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000897 } else if (0 == oval.width() && 0 == oval.height()) {
898 // Chrome will sometimes create 0 radius round rects. Having degenerate
899 // quad segments in the path prevents the path from being recognized as
900 // a rect.
901 // TODO: optimizing the case where only one of width or height is zero
902 // should also be considered. This case, however, doesn't seem to be
903 // as common as the single point case.
reedf90ea012015-01-29 12:03:58 -0800904 pt->set(oval.fRight, oval.fTop);
905 return true;
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000906 }
reedf90ea012015-01-29 12:03:58 -0800907 return false;
908}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000909
reedd5d27d92015-02-09 13:54:43 -0800910// Return the unit vectors pointing at the start/stop points for the given start/sweep angles
911//
912static void angles_to_unit_vectors(SkScalar startAngle, SkScalar sweepAngle,
913 SkVector* startV, SkVector* stopV, SkRotationDirection* dir) {
914 startV->fY = SkScalarSinCos(SkDegreesToRadians(startAngle), &startV->fX);
915 stopV->fY = SkScalarSinCos(SkDegreesToRadians(startAngle + sweepAngle), &stopV->fX);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000916
917 /* If the sweep angle is nearly (but less than) 360, then due to precision
reedd5d27d92015-02-09 13:54:43 -0800918 loss in radians-conversion and/or sin/cos, we may end up with coincident
919 vectors, which will fool SkBuildQuadArc into doing nothing (bad) instead
920 of drawing a nearly complete circle (good).
921 e.g. canvas.drawArc(0, 359.99, ...)
922 -vs- canvas.drawArc(0, 359.9, ...)
923 We try to detect this edge case, and tweak the stop vector
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000924 */
reedd5d27d92015-02-09 13:54:43 -0800925 if (*startV == *stopV) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000926 SkScalar sw = SkScalarAbs(sweepAngle);
927 if (sw < SkIntToScalar(360) && sw > SkIntToScalar(359)) {
928 SkScalar stopRad = SkDegreesToRadians(startAngle + sweepAngle);
929 // make a guess at a tiny angle (in radians) to tweak by
930 SkScalar deltaRad = SkScalarCopySign(SK_Scalar1/512, sweepAngle);
931 // not sure how much will be enough, so we use a loop
932 do {
933 stopRad -= deltaRad;
reedd5d27d92015-02-09 13:54:43 -0800934 stopV->fY = SkScalarSinCos(stopRad, &stopV->fX);
935 } while (*startV == *stopV);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000936 }
937 }
reedd5d27d92015-02-09 13:54:43 -0800938 *dir = sweepAngle > 0 ? kCW_SkRotationDirection : kCCW_SkRotationDirection;
939}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000940
reed9e779d42015-02-17 11:43:14 -0800941/**
942 * If this returns 0, then the caller should just line-to the singlePt, else it should
943 * ignore singlePt and append the specified number of conics.
944 */
reedd5d27d92015-02-09 13:54:43 -0800945static int build_arc_conics(const SkRect& oval, const SkVector& start, const SkVector& stop,
reed9e779d42015-02-17 11:43:14 -0800946 SkRotationDirection dir, SkConic conics[SkConic::kMaxConicsForArc],
947 SkPoint* singlePt) {
reedd5d27d92015-02-09 13:54:43 -0800948 SkMatrix matrix;
949
950 matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height()));
951 matrix.postTranslate(oval.centerX(), oval.centerY());
952
reed9e779d42015-02-17 11:43:14 -0800953 int count = SkConic::BuildUnitArc(start, stop, dir, &matrix, conics);
954 if (0 == count) {
955 matrix.mapXY(start.x(), start.y(), singlePt);
956 }
957 return count;
reedd5d27d92015-02-09 13:54:43 -0800958}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000959
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +0000960void SkPath::addRoundRect(const SkRect& rect, const SkScalar radii[],
reed@android.com8a1c16f2008-12-17 15:59:43 +0000961 Direction dir) {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +0000962 SkRRect rrect;
963 rrect.setRectRadii(rect, (const SkVector*) radii);
964 this->addRRect(rrect, dir);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000965}
966
reed@google.com4ed0fb72012-12-12 20:48:18 +0000967void SkPath::addRRect(const SkRRect& rrect, Direction dir) {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +0000968 assert_known_direction(dir);
969
970 if (rrect.isEmpty()) {
971 return;
972 }
973
reed@google.com4ed0fb72012-12-12 20:48:18 +0000974 const SkRect& bounds = rrect.getBounds();
975
976 if (rrect.isRect()) {
977 this->addRect(bounds, dir);
978 } else if (rrect.isOval()) {
979 this->addOval(bounds, dir);
reed@google.com4ed0fb72012-12-12 20:48:18 +0000980 } else {
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +0000981 fDirection = this->hasOnlyMoveTos() ? dir : kUnknown_Direction;
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +0000982
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +0000983 SkAutoPathBoundsUpdate apbu(this, bounds);
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +0000984 SkAutoDisableDirectionCheck addc(this);
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +0000985
reed1b28a3a2014-12-17 14:42:09 -0800986 const SkScalar L = bounds.fLeft;
987 const SkScalar T = bounds.fTop;
988 const SkScalar R = bounds.fRight;
989 const SkScalar B = bounds.fBottom;
990 const SkScalar W = SK_ScalarRoot2Over2;
991
992 this->incReserve(13);
993 if (kCW_Direction == dir) {
994 this->moveTo(L, B - rrect.fRadii[SkRRect::kLowerLeft_Corner].fY);
995
996 this->lineTo(L, T + rrect.fRadii[SkRRect::kUpperLeft_Corner].fY);
997 this->conicTo(L, T, L + rrect.fRadii[SkRRect::kUpperLeft_Corner].fX, T, W);
998
999 this->lineTo(R - rrect.fRadii[SkRRect::kUpperRight_Corner].fX, T);
1000 this->conicTo(R, T, R, T + rrect.fRadii[SkRRect::kUpperRight_Corner].fY, W);
1001
1002 this->lineTo(R, B - rrect.fRadii[SkRRect::kLowerRight_Corner].fY);
1003 this->conicTo(R, B, R - rrect.fRadii[SkRRect::kLowerRight_Corner].fX, B, W);
1004
1005 this->lineTo(L + rrect.fRadii[SkRRect::kLowerLeft_Corner].fX, B);
1006 this->conicTo(L, B, L, B - rrect.fRadii[SkRRect::kLowerLeft_Corner].fY, W);
1007 } else {
1008 this->moveTo(L, T + rrect.fRadii[SkRRect::kUpperLeft_Corner].fY);
1009
1010 this->lineTo(L, B - rrect.fRadii[SkRRect::kLowerLeft_Corner].fY);
1011 this->conicTo(L, B, L + rrect.fRadii[SkRRect::kLowerLeft_Corner].fX, B, W);
1012
1013 this->lineTo(R - rrect.fRadii[SkRRect::kLowerRight_Corner].fX, B);
1014 this->conicTo(R, B, R, B - rrect.fRadii[SkRRect::kLowerRight_Corner].fY, W);
1015
1016 this->lineTo(R, T + rrect.fRadii[SkRRect::kUpperRight_Corner].fY);
1017 this->conicTo(R, T, R - rrect.fRadii[SkRRect::kUpperRight_Corner].fX, T, W);
1018
1019 this->lineTo(L + rrect.fRadii[SkRRect::kUpperLeft_Corner].fX, T);
1020 this->conicTo(L, T, L, T + rrect.fRadii[SkRRect::kUpperLeft_Corner].fY, W);
1021 }
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001022 this->close();
reed@google.com4ed0fb72012-12-12 20:48:18 +00001023 }
reed5bcbe912014-12-15 12:28:33 -08001024 SkDEBUGCODE(fPathRef->validate();)
reed@google.com4ed0fb72012-12-12 20:48:18 +00001025}
1026
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001027bool SkPath::hasOnlyMoveTos() const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001028 int count = fPathRef->countVerbs();
1029 const uint8_t* verbs = const_cast<const SkPathRef*>(fPathRef.get())->verbsMemBegin();
1030 for (int i = 0; i < count; ++i) {
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001031 if (*verbs == kLine_Verb ||
1032 *verbs == kQuad_Verb ||
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001033 *verbs == kConic_Verb ||
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001034 *verbs == kCubic_Verb) {
1035 return false;
1036 }
1037 ++verbs;
1038 }
1039 return true;
1040}
1041
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001042void SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
1043 Direction dir) {
1044 assert_known_direction(dir);
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001045
humper@google.com75e3ca12013-04-08 21:44:11 +00001046 if (rx < 0 || ry < 0) {
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001047 SkErrorInternals::SetError( kInvalidArgument_SkError,
humper@google.com75e3ca12013-04-08 21:44:11 +00001048 "I got %f and %f as radii to SkPath::AddRoundRect, "
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001049 "but negative radii are not allowed.",
humper@google.com75e3ca12013-04-08 21:44:11 +00001050 SkScalarToDouble(rx), SkScalarToDouble(ry) );
1051 return;
1052 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001053
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001054 SkRRect rrect;
1055 rrect.setRectXY(rect, rx, ry);
1056 this->addRRect(rrect, dir);
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001057}
1058
reed@android.com8a1c16f2008-12-17 15:59:43 +00001059void SkPath::addOval(const SkRect& oval, Direction dir) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +00001060 assert_known_direction(dir);
1061
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001062 /* If addOval() is called after previous moveTo(),
1063 this path is still marked as an oval. This is used to
1064 fit into WebKit's calling sequences.
1065 We can't simply check isEmpty() in this case, as additional
1066 moveTo() would mark the path non empty.
1067 */
robertphillips@google.com466310d2013-12-03 16:43:54 +00001068 bool isOval = hasOnlyMoveTos();
1069 if (isOval) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001070 fDirection = dir;
1071 } else {
1072 fDirection = kUnknown_Direction;
1073 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001074
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001075 SkAutoDisableDirectionCheck addc(this);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001076
reed@android.com8a1c16f2008-12-17 15:59:43 +00001077 SkAutoPathBoundsUpdate apbu(this, oval);
1078
reed220f9262014-12-17 08:21:04 -08001079 const SkScalar L = oval.fLeft;
1080 const SkScalar T = oval.fTop;
1081 const SkScalar R = oval.fRight;
1082 const SkScalar B = oval.fBottom;
1083 const SkScalar cx = oval.centerX();
1084 const SkScalar cy = oval.centerY();
1085 const SkScalar weight = SK_ScalarRoot2Over2;
1086
1087 this->incReserve(9); // move + 4 conics
1088 this->moveTo(R, cy);
1089 if (dir == kCCW_Direction) {
1090 this->conicTo(R, T, cx, T, weight);
1091 this->conicTo(L, T, L, cy, weight);
1092 this->conicTo(L, B, cx, B, weight);
1093 this->conicTo(R, B, R, cy, weight);
1094 } else {
1095 this->conicTo(R, B, cx, B, weight);
1096 this->conicTo(L, B, L, cy, weight);
1097 this->conicTo(L, T, cx, T, weight);
1098 this->conicTo(R, T, R, cy, weight);
1099 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001100 this->close();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001101
robertphillips@google.com466310d2013-12-03 16:43:54 +00001102 SkPathRef::Editor ed(&fPathRef);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001103
robertphillips@google.com466310d2013-12-03 16:43:54 +00001104 ed.setIsOval(isOval);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001105}
1106
reed@android.com8a1c16f2008-12-17 15:59:43 +00001107void SkPath::addCircle(SkScalar x, SkScalar y, SkScalar r, Direction dir) {
1108 if (r > 0) {
1109 SkRect rect;
1110 rect.set(x - r, y - r, x + r, y + r);
1111 this->addOval(rect, dir);
1112 }
1113}
1114
reed@android.com8a1c16f2008-12-17 15:59:43 +00001115void SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
1116 bool forceMoveTo) {
1117 if (oval.width() < 0 || oval.height() < 0) {
1118 return;
1119 }
1120
reedf90ea012015-01-29 12:03:58 -08001121 if (fPathRef->countVerbs() == 0) {
1122 forceMoveTo = true;
1123 }
1124
1125 SkPoint lonePt;
1126 if (arc_is_lone_point(oval, startAngle, sweepAngle, &lonePt)) {
1127 forceMoveTo ? this->moveTo(lonePt) : this->lineTo(lonePt);
1128 return;
1129 }
1130
reedd5d27d92015-02-09 13:54:43 -08001131 SkVector startV, stopV;
1132 SkRotationDirection dir;
1133 angles_to_unit_vectors(startAngle, sweepAngle, &startV, &stopV, &dir);
1134
reed9e779d42015-02-17 11:43:14 -08001135 SkPoint singlePt;
reedd5d27d92015-02-09 13:54:43 -08001136 SkConic conics[SkConic::kMaxConicsForArc];
reed9e779d42015-02-17 11:43:14 -08001137 int count = build_arc_conics(oval, startV, stopV, dir, conics, &singlePt);
reedd5d27d92015-02-09 13:54:43 -08001138 if (count) {
1139 this->incReserve(count * 2 + 1);
1140 const SkPoint& pt = conics[0].fPts[0];
1141 forceMoveTo ? this->moveTo(pt) : this->lineTo(pt);
1142 for (int i = 0; i < count; ++i) {
1143 this->conicTo(conics[i].fPts[1], conics[i].fPts[2], conics[i].fW);
1144 }
reed9e779d42015-02-17 11:43:14 -08001145 } else {
1146 forceMoveTo ? this->moveTo(singlePt) : this->lineTo(singlePt);
reedd5d27d92015-02-09 13:54:43 -08001147 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001148}
1149
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001150void SkPath::addArc(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001151 if (oval.isEmpty() || 0 == sweepAngle) {
1152 return;
1153 }
1154
1155 const SkScalar kFullCircleAngle = SkIntToScalar(360);
1156
1157 if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
1158 this->addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction);
reedc7789042015-01-29 12:59:11 -08001159 } else {
1160 this->arcTo(oval, startAngle, sweepAngle, true);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001161 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001162}
1163
1164/*
1165 Need to handle the case when the angle is sharp, and our computed end-points
1166 for the arc go behind pt1 and/or p2...
1167*/
reedc7789042015-01-29 12:59:11 -08001168void SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2, SkScalar radius) {
reeda8b326c2014-12-09 11:50:32 -08001169 if (radius == 0) {
1170 this->lineTo(x1, y1);
1171 return;
1172 }
1173
1174 SkVector before, after;
reed@google.comabf15c12011-01-18 20:35:51 +00001175
reed@android.com8a1c16f2008-12-17 15:59:43 +00001176 // need to know our prev pt so we can construct tangent vectors
1177 {
1178 SkPoint start;
1179 this->getLastPt(&start);
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001180 // Handle degenerate cases by adding a line to the first point and
1181 // bailing out.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001182 before.setNormalize(x1 - start.fX, y1 - start.fY);
1183 after.setNormalize(x2 - x1, y2 - y1);
1184 }
reed@google.comabf15c12011-01-18 20:35:51 +00001185
reed@android.com8a1c16f2008-12-17 15:59:43 +00001186 SkScalar cosh = SkPoint::DotProduct(before, after);
1187 SkScalar sinh = SkPoint::CrossProduct(before, after);
1188
1189 if (SkScalarNearlyZero(sinh)) { // angle is too tight
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001190 this->lineTo(x1, y1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001191 return;
1192 }
reed@google.comabf15c12011-01-18 20:35:51 +00001193
reed@android.com8a1c16f2008-12-17 15:59:43 +00001194 SkScalar dist = SkScalarMulDiv(radius, SK_Scalar1 - cosh, sinh);
1195 if (dist < 0) {
1196 dist = -dist;
1197 }
1198
1199 SkScalar xx = x1 - SkScalarMul(dist, before.fX);
1200 SkScalar yy = y1 - SkScalarMul(dist, before.fY);
1201 SkRotationDirection arcDir;
1202
1203 // now turn before/after into normals
1204 if (sinh > 0) {
1205 before.rotateCCW();
1206 after.rotateCCW();
1207 arcDir = kCW_SkRotationDirection;
1208 } else {
1209 before.rotateCW();
1210 after.rotateCW();
1211 arcDir = kCCW_SkRotationDirection;
1212 }
1213
1214 SkMatrix matrix;
1215 SkPoint pts[kSkBuildQuadArcStorage];
reed@google.comabf15c12011-01-18 20:35:51 +00001216
reed@android.com8a1c16f2008-12-17 15:59:43 +00001217 matrix.setScale(radius, radius);
1218 matrix.postTranslate(xx - SkScalarMul(radius, before.fX),
1219 yy - SkScalarMul(radius, before.fY));
reed@google.comabf15c12011-01-18 20:35:51 +00001220
reed@android.com8a1c16f2008-12-17 15:59:43 +00001221 int count = SkBuildQuadArc(before, after, arcDir, &matrix, pts);
reed@google.comabf15c12011-01-18 20:35:51 +00001222
reed@android.com8a1c16f2008-12-17 15:59:43 +00001223 this->incReserve(count);
1224 // [xx,yy] == pts[0]
1225 this->lineTo(xx, yy);
1226 for (int i = 1; i < count; i += 2) {
1227 this->quadTo(pts[i], pts[i+1]);
1228 }
1229}
1230
1231///////////////////////////////////////////////////////////////////////////////
1232
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001233void SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy, AddPathMode mode) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001234 SkMatrix matrix;
1235
1236 matrix.setTranslate(dx, dy);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001237 this->addPath(path, matrix, mode);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001238}
1239
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001240void SkPath::addPath(const SkPath& path, const SkMatrix& matrix, AddPathMode mode) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001241 SkPathRef::Editor(&fPathRef, path.countVerbs(), path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001242
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001243 RawIter iter(path);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001244 SkPoint pts[4];
1245 Verb verb;
1246
1247 SkMatrix::MapPtsProc proc = matrix.getMapPtsProc();
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001248 bool firstVerb = true;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001249 while ((verb = iter.next(pts)) != kDone_Verb) {
1250 switch (verb) {
1251 case kMove_Verb:
1252 proc(matrix, &pts[0], &pts[0], 1);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001253 if (firstVerb && mode == kExtend_AddPathMode && !isEmpty()) {
1254 injectMoveToIfNeeded(); // In case last contour is closed
1255 this->lineTo(pts[0]);
1256 } else {
1257 this->moveTo(pts[0]);
1258 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001259 break;
1260 case kLine_Verb:
1261 proc(matrix, &pts[1], &pts[1], 1);
1262 this->lineTo(pts[1]);
1263 break;
1264 case kQuad_Verb:
1265 proc(matrix, &pts[1], &pts[1], 2);
1266 this->quadTo(pts[1], pts[2]);
1267 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001268 case kConic_Verb:
1269 proc(matrix, &pts[1], &pts[1], 2);
1270 this->conicTo(pts[1], pts[2], iter.conicWeight());
1271 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001272 case kCubic_Verb:
1273 proc(matrix, &pts[1], &pts[1], 3);
1274 this->cubicTo(pts[1], pts[2], pts[3]);
1275 break;
1276 case kClose_Verb:
1277 this->close();
1278 break;
1279 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001280 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001281 }
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001282 firstVerb = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001283 }
1284}
1285
1286///////////////////////////////////////////////////////////////////////////////
1287
reed@google.com277c3f82013-05-31 15:17:50 +00001288static int pts_in_verb(unsigned verb) {
1289 static const uint8_t gPtsInVerb[] = {
1290 1, // kMove
1291 1, // kLine
1292 2, // kQuad
1293 2, // kConic
1294 3, // kCubic
1295 0, // kClose
1296 0 // kDone
1297 };
1298
1299 SkASSERT(verb < SK_ARRAY_COUNT(gPtsInVerb));
1300 return gPtsInVerb[verb];
1301}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001302
reed@android.com8a1c16f2008-12-17 15:59:43 +00001303// ignore the last point of the 1st contour
1304void SkPath::reversePathTo(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001305 int i, vcount = path.fPathRef->countVerbs();
1306 // exit early if the path is empty, or just has a moveTo.
1307 if (vcount < 2) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001308 return;
1309 }
1310
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001311 SkPathRef::Editor(&fPathRef, vcount, path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001312
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001313 const uint8_t* verbs = path.fPathRef->verbs();
1314 const SkPoint* pts = path.fPathRef->points();
reed@google.com277c3f82013-05-31 15:17:50 +00001315 const SkScalar* conicWeights = path.fPathRef->conicWeights();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001316
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001317 SkASSERT(verbs[~0] == kMove_Verb);
1318 for (i = 1; i < vcount; ++i) {
reed@google.com277c3f82013-05-31 15:17:50 +00001319 unsigned v = verbs[~i];
1320 int n = pts_in_verb(v);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001321 if (n == 0) {
1322 break;
1323 }
1324 pts += n;
reed@google.com277c3f82013-05-31 15:17:50 +00001325 conicWeights += (SkPath::kConic_Verb == v);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001326 }
1327
1328 while (--i > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001329 switch (verbs[~i]) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001330 case kLine_Verb:
1331 this->lineTo(pts[-1].fX, pts[-1].fY);
1332 break;
1333 case kQuad_Verb:
1334 this->quadTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY);
1335 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001336 case kConic_Verb:
1337 this->conicTo(pts[-1], pts[-2], *--conicWeights);
1338 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001339 case kCubic_Verb:
1340 this->cubicTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY,
1341 pts[-3].fX, pts[-3].fY);
1342 break;
1343 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001344 SkDEBUGFAIL("bad verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001345 break;
1346 }
reed@google.com277c3f82013-05-31 15:17:50 +00001347 pts -= pts_in_verb(verbs[~i]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001348 }
1349}
1350
reed@google.com63d73742012-01-10 15:33:12 +00001351void SkPath::reverseAddPath(const SkPath& src) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001352 SkPathRef::Editor ed(&fPathRef, src.fPathRef->countPoints(), src.fPathRef->countVerbs());
reed@google.com63d73742012-01-10 15:33:12 +00001353
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001354 const SkPoint* pts = src.fPathRef->pointsEnd();
1355 // we will iterator through src's verbs backwards
1356 const uint8_t* verbs = src.fPathRef->verbsMemBegin(); // points at the last verb
1357 const uint8_t* verbsEnd = src.fPathRef->verbs(); // points just past the first verb
reed@google.com277c3f82013-05-31 15:17:50 +00001358 const SkScalar* conicWeights = src.fPathRef->conicWeightsEnd();
reed@google.com63d73742012-01-10 15:33:12 +00001359
1360 bool needMove = true;
1361 bool needClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001362 while (verbs < verbsEnd) {
1363 uint8_t v = *(verbs++);
reed@google.com277c3f82013-05-31 15:17:50 +00001364 int n = pts_in_verb(v);
reed@google.com63d73742012-01-10 15:33:12 +00001365
1366 if (needMove) {
1367 --pts;
1368 this->moveTo(pts->fX, pts->fY);
1369 needMove = false;
1370 }
1371 pts -= n;
1372 switch (v) {
1373 case kMove_Verb:
1374 if (needClose) {
1375 this->close();
1376 needClose = false;
1377 }
1378 needMove = true;
1379 pts += 1; // so we see the point in "if (needMove)" above
1380 break;
1381 case kLine_Verb:
1382 this->lineTo(pts[0]);
1383 break;
1384 case kQuad_Verb:
1385 this->quadTo(pts[1], pts[0]);
1386 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001387 case kConic_Verb:
1388 this->conicTo(pts[1], pts[0], *--conicWeights);
1389 break;
reed@google.com63d73742012-01-10 15:33:12 +00001390 case kCubic_Verb:
1391 this->cubicTo(pts[2], pts[1], pts[0]);
1392 break;
1393 case kClose_Verb:
1394 needClose = true;
1395 break;
1396 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00001397 SkDEBUGFAIL("unexpected verb");
reed@google.com63d73742012-01-10 15:33:12 +00001398 }
1399 }
1400}
1401
reed@android.com8a1c16f2008-12-17 15:59:43 +00001402///////////////////////////////////////////////////////////////////////////////
1403
1404void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
1405 SkMatrix matrix;
1406
1407 matrix.setTranslate(dx, dy);
1408 this->transform(matrix, dst);
1409}
1410
reed@android.com8a1c16f2008-12-17 15:59:43 +00001411static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
1412 int level = 2) {
1413 if (--level >= 0) {
1414 SkPoint tmp[7];
1415
1416 SkChopCubicAtHalf(pts, tmp);
1417 subdivide_cubic_to(path, &tmp[0], level);
1418 subdivide_cubic_to(path, &tmp[3], level);
1419 } else {
1420 path->cubicTo(pts[1], pts[2], pts[3]);
1421 }
1422}
1423
1424void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
1425 SkDEBUGCODE(this->validate();)
1426 if (dst == NULL) {
1427 dst = (SkPath*)this;
1428 }
1429
tomhudson@google.com8d430182011-06-06 19:11:19 +00001430 if (matrix.hasPerspective()) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001431 SkPath tmp;
1432 tmp.fFillType = fFillType;
1433
1434 SkPath::Iter iter(*this, false);
1435 SkPoint pts[4];
1436 SkPath::Verb verb;
1437
reed@google.com4a3b7142012-05-16 17:16:46 +00001438 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001439 switch (verb) {
1440 case kMove_Verb:
1441 tmp.moveTo(pts[0]);
1442 break;
1443 case kLine_Verb:
1444 tmp.lineTo(pts[1]);
1445 break;
1446 case kQuad_Verb:
reed220f9262014-12-17 08:21:04 -08001447 // promote the quad to a conic
1448 tmp.conicTo(pts[1], pts[2],
1449 SkConic::TransformW(pts, SK_Scalar1, matrix));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001450 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001451 case kConic_Verb:
reed220f9262014-12-17 08:21:04 -08001452 tmp.conicTo(pts[1], pts[2],
1453 SkConic::TransformW(pts, iter.conicWeight(), matrix));
reed@google.com277c3f82013-05-31 15:17:50 +00001454 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001455 case kCubic_Verb:
1456 subdivide_cubic_to(&tmp, pts);
1457 break;
1458 case kClose_Verb:
1459 tmp.close();
1460 break;
1461 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001462 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001463 break;
1464 }
1465 }
1466
1467 dst->swap(tmp);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001468 SkPathRef::Editor ed(&dst->fPathRef);
1469 matrix.mapPoints(ed.points(), ed.pathRef()->countPoints());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001470 dst->fDirection = kUnknown_Direction;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001471 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001472 SkPathRef::CreateTransformedCopy(&dst->fPathRef, *fPathRef.get(), matrix);
1473
reed@android.com8a1c16f2008-12-17 15:59:43 +00001474 if (this != dst) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001475 dst->fFillType = fFillType;
reed@google.com2a6f8ab2011-10-25 18:41:23 +00001476 dst->fConvexity = fConvexity;
jvanverthb3eb6872014-10-24 07:12:51 -07001477 dst->fIsVolatile = fIsVolatile;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001478 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001479
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001480 if (kUnknown_Direction == fDirection) {
1481 dst->fDirection = kUnknown_Direction;
1482 } else {
1483 SkScalar det2x2 =
1484 SkScalarMul(matrix.get(SkMatrix::kMScaleX), matrix.get(SkMatrix::kMScaleY)) -
1485 SkScalarMul(matrix.get(SkMatrix::kMSkewX), matrix.get(SkMatrix::kMSkewY));
1486 if (det2x2 < 0) {
1487 dst->fDirection = SkPath::OppositeDirection(static_cast<Direction>(fDirection));
1488 } else if (det2x2 > 0) {
1489 dst->fDirection = fDirection;
1490 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001491 dst->fConvexity = kUnknown_Convexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001492 dst->fDirection = kUnknown_Direction;
1493 }
1494 }
1495
reed@android.com8a1c16f2008-12-17 15:59:43 +00001496 SkDEBUGCODE(dst->validate();)
1497 }
1498}
1499
reed@android.com8a1c16f2008-12-17 15:59:43 +00001500///////////////////////////////////////////////////////////////////////////////
1501///////////////////////////////////////////////////////////////////////////////
1502
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001503enum SegmentState {
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001504 kEmptyContour_SegmentState, // The current contour is empty. We may be
1505 // starting processing or we may have just
1506 // closed a contour.
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001507 kAfterMove_SegmentState, // We have seen a move, but nothing else.
1508 kAfterPrimitive_SegmentState // We have seen a primitive but not yet
1509 // closed the path. Also the initial state.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001510};
1511
1512SkPath::Iter::Iter() {
1513#ifdef SK_DEBUG
1514 fPts = NULL;
reed@google.com277c3f82013-05-31 15:17:50 +00001515 fConicWeights = NULL;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001516 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001517 fForceClose = fCloseLine = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001518 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001519#endif
1520 // need to init enough to make next() harmlessly return kDone_Verb
1521 fVerbs = NULL;
1522 fVerbStop = NULL;
1523 fNeedClose = false;
1524}
1525
1526SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
1527 this->setPath(path, forceClose);
1528}
1529
1530void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001531 fPts = path.fPathRef->points();
1532 fVerbs = path.fPathRef->verbs();
1533 fVerbStop = path.fPathRef->verbsMemBegin();
reed@google.com277c3f82013-05-31 15:17:50 +00001534 fConicWeights = path.fPathRef->conicWeights() - 1; // begin one behind
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001535 fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org72785c42011-12-29 21:03:28 +00001536 fMoveTo.fX = fMoveTo.fY = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001537 fForceClose = SkToU8(forceClose);
1538 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001539 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001540}
1541
1542bool SkPath::Iter::isClosedContour() const {
1543 if (fVerbs == NULL || fVerbs == fVerbStop) {
1544 return false;
1545 }
1546 if (fForceClose) {
1547 return true;
1548 }
1549
1550 const uint8_t* verbs = fVerbs;
1551 const uint8_t* stop = fVerbStop;
reed@google.comabf15c12011-01-18 20:35:51 +00001552
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001553 if (kMove_Verb == *(verbs - 1)) {
1554 verbs -= 1; // skip the initial moveto
reed@android.com8a1c16f2008-12-17 15:59:43 +00001555 }
1556
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001557 while (verbs > stop) {
1558 // verbs points one beyond the current verb, decrement first.
1559 unsigned v = *(--verbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001560 if (kMove_Verb == v) {
1561 break;
1562 }
1563 if (kClose_Verb == v) {
1564 return true;
1565 }
1566 }
1567 return false;
1568}
1569
1570SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001571 SkASSERT(pts);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001572 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001573 // A special case: if both points are NaN, SkPoint::operation== returns
1574 // false, but the iterator expects that they are treated as the same.
1575 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001576 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1577 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001578 return kClose_Verb;
1579 }
1580
reed@google.com9e25dbf2012-05-15 17:05:38 +00001581 pts[0] = fLastPt;
1582 pts[1] = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001583 fLastPt = fMoveTo;
1584 fCloseLine = true;
1585 return kLine_Verb;
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +00001586 } else {
1587 pts[0] = fMoveTo;
1588 return kClose_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001589 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001590}
1591
reed@google.com9e25dbf2012-05-15 17:05:38 +00001592const SkPoint& SkPath::Iter::cons_moveTo() {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001593 if (fSegmentState == kAfterMove_SegmentState) {
1594 // Set the first return pt to the move pt
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001595 fSegmentState = kAfterPrimitive_SegmentState;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001596 return fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001597 } else {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001598 SkASSERT(fSegmentState == kAfterPrimitive_SegmentState);
1599 // Set the first return pt to the last pt of the previous primitive.
reed@google.com9e25dbf2012-05-15 17:05:38 +00001600 return fPts[-1];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001601 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001602}
1603
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001604void SkPath::Iter::consumeDegenerateSegments() {
1605 // We need to step over anything that will not move the current draw point
1606 // forward before the next move is seen
1607 const uint8_t* lastMoveVerb = 0;
1608 const SkPoint* lastMovePt = 0;
robertphillipsb8de1f42015-02-23 11:17:01 -08001609 const SkScalar* lastMoveWeight = NULL;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001610 SkPoint lastPt = fLastPt;
1611 while (fVerbs != fVerbStop) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001612 unsigned verb = *(fVerbs - 1); // fVerbs is one beyond the current verb
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001613 switch (verb) {
1614 case kMove_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001615 // Keep a record of this most recent move
1616 lastMoveVerb = fVerbs;
1617 lastMovePt = fPts;
robertphillipsb8de1f42015-02-23 11:17:01 -08001618 lastMoveWeight = fConicWeights;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001619 lastPt = fPts[0];
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001620 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001621 fPts++;
1622 break;
1623
1624 case kClose_Verb:
schenney@chromium.org7e963602012-06-13 17:05:43 +00001625 // A close when we are in a segment is always valid except when it
1626 // follows a move which follows a segment.
1627 if (fSegmentState == kAfterPrimitive_SegmentState && !lastMoveVerb) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001628 return;
1629 }
1630 // A close at any other time must be ignored
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001631 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001632 break;
1633
1634 case kLine_Verb:
1635 if (!IsLineDegenerate(lastPt, fPts[0])) {
1636 if (lastMoveVerb) {
1637 fVerbs = lastMoveVerb;
1638 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001639 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001640 return;
1641 }
1642 return;
1643 }
1644 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001645 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001646 fPts++;
1647 break;
1648
reed@google.com277c3f82013-05-31 15:17:50 +00001649 case kConic_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001650 case kQuad_Verb:
1651 if (!IsQuadDegenerate(lastPt, fPts[0], fPts[1])) {
1652 if (lastMoveVerb) {
1653 fVerbs = lastMoveVerb;
1654 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001655 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001656 return;
1657 }
1658 return;
1659 }
1660 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001661 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001662 fPts += 2;
reed@google.com277c3f82013-05-31 15:17:50 +00001663 fConicWeights += (kConic_Verb == verb);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001664 break;
1665
1666 case kCubic_Verb:
1667 if (!IsCubicDegenerate(lastPt, fPts[0], fPts[1], fPts[2])) {
1668 if (lastMoveVerb) {
1669 fVerbs = lastMoveVerb;
1670 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001671 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001672 return;
1673 }
1674 return;
1675 }
1676 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001677 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001678 fPts += 3;
1679 break;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001680
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001681 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001682 SkDEBUGFAIL("Should never see kDone_Verb");
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001683 }
1684 }
1685}
1686
reed@google.com4a3b7142012-05-16 17:16:46 +00001687SkPath::Verb SkPath::Iter::doNext(SkPoint ptsParam[4]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001688 SkASSERT(ptsParam);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001689
reed@android.com8a1c16f2008-12-17 15:59:43 +00001690 if (fVerbs == fVerbStop) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001691 // Close the curve if requested and if there is some curve to close
1692 if (fNeedClose && fSegmentState == kAfterPrimitive_SegmentState) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001693 if (kLine_Verb == this->autoClose(ptsParam)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001694 return kLine_Verb;
1695 }
1696 fNeedClose = false;
1697 return kClose_Verb;
1698 }
1699 return kDone_Verb;
1700 }
1701
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001702 // fVerbs is one beyond the current verb, decrement first
1703 unsigned verb = *(--fVerbs);
reed@google.com9e25dbf2012-05-15 17:05:38 +00001704 const SkPoint* SK_RESTRICT srcPts = fPts;
1705 SkPoint* SK_RESTRICT pts = ptsParam;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001706
1707 switch (verb) {
1708 case kMove_Verb:
1709 if (fNeedClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001710 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001711 verb = this->autoClose(pts);
1712 if (verb == kClose_Verb) {
1713 fNeedClose = false;
1714 }
1715 return (Verb)verb;
1716 }
1717 if (fVerbs == fVerbStop) { // might be a trailing moveto
1718 return kDone_Verb;
1719 }
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001720 fMoveTo = *srcPts;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001721 pts[0] = *srcPts;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001722 srcPts += 1;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001723 fSegmentState = kAfterMove_SegmentState;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001724 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001725 fNeedClose = fForceClose;
1726 break;
1727 case kLine_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001728 pts[0] = this->cons_moveTo();
1729 pts[1] = srcPts[0];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001730 fLastPt = srcPts[0];
1731 fCloseLine = false;
1732 srcPts += 1;
1733 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001734 case kConic_Verb:
1735 fConicWeights += 1;
1736 // fall-through
reed@android.com8a1c16f2008-12-17 15:59:43 +00001737 case kQuad_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001738 pts[0] = this->cons_moveTo();
1739 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001740 fLastPt = srcPts[1];
1741 srcPts += 2;
1742 break;
1743 case kCubic_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001744 pts[0] = this->cons_moveTo();
1745 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001746 fLastPt = srcPts[2];
1747 srcPts += 3;
1748 break;
1749 case kClose_Verb:
1750 verb = this->autoClose(pts);
1751 if (verb == kLine_Verb) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001752 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001753 } else {
1754 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001755 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001756 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001757 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001758 break;
1759 }
1760 fPts = srcPts;
1761 return (Verb)verb;
1762}
1763
1764///////////////////////////////////////////////////////////////////////////////
1765
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001766SkPath::RawIter::RawIter() {
1767#ifdef SK_DEBUG
1768 fPts = NULL;
reed@google.com277c3f82013-05-31 15:17:50 +00001769 fConicWeights = NULL;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001770 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
1771#endif
1772 // need to init enough to make next() harmlessly return kDone_Verb
1773 fVerbs = NULL;
1774 fVerbStop = NULL;
1775}
1776
1777SkPath::RawIter::RawIter(const SkPath& path) {
1778 this->setPath(path);
1779}
1780
1781void SkPath::RawIter::setPath(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001782 fPts = path.fPathRef->points();
1783 fVerbs = path.fPathRef->verbs();
1784 fVerbStop = path.fPathRef->verbsMemBegin();
reed@google.com277c3f82013-05-31 15:17:50 +00001785 fConicWeights = path.fPathRef->conicWeights() - 1; // begin one behind
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001786 fMoveTo.fX = fMoveTo.fY = 0;
1787 fLastPt.fX = fLastPt.fY = 0;
1788}
1789
1790SkPath::Verb SkPath::RawIter::next(SkPoint pts[4]) {
bsalomon49f085d2014-09-05 13:34:00 -07001791 SkASSERT(pts);
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001792 if (fVerbs == fVerbStop) {
1793 return kDone_Verb;
1794 }
1795
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001796 // fVerbs points one beyond next verb so decrement first.
1797 unsigned verb = *(--fVerbs);
1798 const SkPoint* srcPts = fPts;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001799
1800 switch (verb) {
1801 case kMove_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001802 pts[0] = *srcPts;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001803 fMoveTo = srcPts[0];
1804 fLastPt = fMoveTo;
1805 srcPts += 1;
1806 break;
1807 case kLine_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001808 pts[0] = fLastPt;
1809 pts[1] = srcPts[0];
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001810 fLastPt = srcPts[0];
1811 srcPts += 1;
1812 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001813 case kConic_Verb:
1814 fConicWeights += 1;
1815 // fall-through
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001816 case kQuad_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001817 pts[0] = fLastPt;
1818 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001819 fLastPt = srcPts[1];
1820 srcPts += 2;
1821 break;
1822 case kCubic_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001823 pts[0] = fLastPt;
1824 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001825 fLastPt = srcPts[2];
1826 srcPts += 3;
1827 break;
1828 case kClose_Verb:
1829 fLastPt = fMoveTo;
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001830 pts[0] = fMoveTo;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001831 break;
1832 }
1833 fPts = srcPts;
1834 return (Verb)verb;
1835}
1836
1837///////////////////////////////////////////////////////////////////////////////
1838
reed@android.com8a1c16f2008-12-17 15:59:43 +00001839/*
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001840 Format in compressed buffer: [ptCount, verbCount, pts[], verbs[]]
reed@android.com8a1c16f2008-12-17 15:59:43 +00001841*/
1842
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00001843size_t SkPath::writeToMemory(void* storage) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001844 SkDEBUGCODE(this->validate();)
1845
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001846 if (NULL == storage) {
robertphillips@google.comca0c8382013-09-26 12:18:23 +00001847 const int byteCount = sizeof(int32_t) + fPathRef->writeSize();
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001848 return SkAlign4(byteCount);
1849 }
1850
1851 SkWBuffer buffer(storage);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001852
robertphillips@google.com466310d2013-12-03 16:43:54 +00001853 int32_t packed = (fConvexity << kConvexity_SerializationShift) |
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001854 (fFillType << kFillType_SerializationShift) |
jvanverthb3eb6872014-10-24 07:12:51 -07001855 (fDirection << kDirection_SerializationShift) |
1856 (fIsVolatile << kIsVolatile_SerializationShift);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001857
robertphillips@google.com2972bb52012-08-07 17:32:51 +00001858 buffer.write32(packed);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001859
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001860 fPathRef->writeToBuffer(&buffer);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001861
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001862 buffer.padToAlign4();
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00001863 return buffer.pos();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001864}
1865
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00001866size_t SkPath::readFromMemory(const void* storage, size_t length) {
1867 SkRBufferWithSizeCheck buffer(storage, length);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001868
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00001869 int32_t packed;
1870 if (!buffer.readS32(&packed)) {
1871 return 0;
1872 }
1873
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001874 fConvexity = (packed >> kConvexity_SerializationShift) & 0xFF;
1875 fFillType = (packed >> kFillType_SerializationShift) & 0xFF;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001876 fDirection = (packed >> kDirection_SerializationShift) & 0x3;
jvanverthb3eb6872014-10-24 07:12:51 -07001877 fIsVolatile = (packed >> kIsVolatile_SerializationShift) & 0x1;
commit-bot@chromium.orgfed2ab62014-01-23 15:16:05 +00001878 SkPathRef* pathRef = SkPathRef::CreateFromBuffer(&buffer);
reed@google.comabf15c12011-01-18 20:35:51 +00001879
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00001880 size_t sizeRead = 0;
1881 if (buffer.isValid()) {
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00001882 fPathRef.reset(pathRef);
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00001883 SkDEBUGCODE(this->validate();)
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00001884 buffer.skipToAlign4();
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00001885 sizeRead = buffer.pos();
bsalomon49f085d2014-09-05 13:34:00 -07001886 } else if (pathRef) {
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00001887 // If the buffer is not valid, pathRef should be NULL
1888 sk_throw();
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00001889 }
1890 return sizeRead;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001891}
1892
1893///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00001894
reede05fed02014-12-15 07:59:53 -08001895#include "SkStringUtils.h"
caryclark66a5d8b2014-06-24 08:30:15 -07001896#include "SkStream.h"
reed@google.com51bbe752013-01-17 22:07:50 +00001897
reed@google.com51bbe752013-01-17 22:07:50 +00001898static void append_params(SkString* str, const char label[], const SkPoint pts[],
reede05fed02014-12-15 07:59:53 -08001899 int count, SkScalarAsStringType strType, SkScalar conicWeight = -1) {
reed@google.com51bbe752013-01-17 22:07:50 +00001900 str->append(label);
1901 str->append("(");
skia.committer@gmail.com15dd3002013-01-18 07:07:28 +00001902
reed@google.com51bbe752013-01-17 22:07:50 +00001903 const SkScalar* values = &pts[0].fX;
1904 count *= 2;
1905
1906 for (int i = 0; i < count; ++i) {
reede05fed02014-12-15 07:59:53 -08001907 SkAppendScalar(str, values[i], strType);
reed@google.com51bbe752013-01-17 22:07:50 +00001908 if (i < count - 1) {
1909 str->append(", ");
1910 }
1911 }
reed@google.com277c3f82013-05-31 15:17:50 +00001912 if (conicWeight >= 0) {
1913 str->append(", ");
reede05fed02014-12-15 07:59:53 -08001914 SkAppendScalar(str, conicWeight, strType);
reed@google.com277c3f82013-05-31 15:17:50 +00001915 }
caryclark08fa28c2014-10-23 13:08:56 -07001916 str->append(");");
reede05fed02014-12-15 07:59:53 -08001917 if (kHex_SkScalarAsStringType == strType) {
caryclark08fa28c2014-10-23 13:08:56 -07001918 str->append(" // ");
1919 for (int i = 0; i < count; ++i) {
reede05fed02014-12-15 07:59:53 -08001920 SkAppendScalarDec(str, values[i]);
caryclark08fa28c2014-10-23 13:08:56 -07001921 if (i < count - 1) {
1922 str->append(", ");
1923 }
1924 }
1925 if (conicWeight >= 0) {
1926 str->append(", ");
reede05fed02014-12-15 07:59:53 -08001927 SkAppendScalarDec(str, conicWeight);
caryclark08fa28c2014-10-23 13:08:56 -07001928 }
1929 }
1930 str->append("\n");
reed@google.com51bbe752013-01-17 22:07:50 +00001931}
1932
caryclarke9562592014-09-15 09:26:09 -07001933void SkPath::dump(SkWStream* wStream, bool forceClose, bool dumpAsHex) const {
reede05fed02014-12-15 07:59:53 -08001934 SkScalarAsStringType asType = dumpAsHex ? kHex_SkScalarAsStringType : kDec_SkScalarAsStringType;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001935 Iter iter(*this, forceClose);
1936 SkPoint pts[4];
1937 Verb verb;
1938
caryclark66a5d8b2014-06-24 08:30:15 -07001939 if (!wStream) {
1940 SkDebugf("path: forceClose=%s\n", forceClose ? "true" : "false");
1941 }
reed@google.com51bbe752013-01-17 22:07:50 +00001942 SkString builder;
1943
reed@google.com4a3b7142012-05-16 17:16:46 +00001944 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001945 switch (verb) {
1946 case kMove_Verb:
reede05fed02014-12-15 07:59:53 -08001947 append_params(&builder, "path.moveTo", &pts[0], 1, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001948 break;
1949 case kLine_Verb:
reede05fed02014-12-15 07:59:53 -08001950 append_params(&builder, "path.lineTo", &pts[1], 1, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001951 break;
1952 case kQuad_Verb:
reede05fed02014-12-15 07:59:53 -08001953 append_params(&builder, "path.quadTo", &pts[1], 2, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001954 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001955 case kConic_Verb:
reede05fed02014-12-15 07:59:53 -08001956 append_params(&builder, "path.conicTo", &pts[1], 2, asType, iter.conicWeight());
reed@google.com277c3f82013-05-31 15:17:50 +00001957 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001958 case kCubic_Verb:
reede05fed02014-12-15 07:59:53 -08001959 append_params(&builder, "path.cubicTo", &pts[1], 3, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001960 break;
1961 case kClose_Verb:
caryclark66a5d8b2014-06-24 08:30:15 -07001962 builder.append("path.close();\n");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001963 break;
1964 default:
1965 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
1966 verb = kDone_Verb; // stop the loop
1967 break;
1968 }
1969 }
caryclark66a5d8b2014-06-24 08:30:15 -07001970 if (wStream) {
1971 wStream->writeText(builder.c_str());
1972 } else {
1973 SkDebugf("%s", builder.c_str());
1974 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001975}
1976
reed@android.come522ca52009-11-23 20:10:41 +00001977void SkPath::dump() const {
caryclarke9562592014-09-15 09:26:09 -07001978 this->dump(NULL, false, false);
1979}
1980
1981void SkPath::dumpHex() const {
1982 this->dump(NULL, false, true);
reed@android.come522ca52009-11-23 20:10:41 +00001983}
1984
1985#ifdef SK_DEBUG
1986void SkPath::validate() const {
1987 SkASSERT(this != NULL);
1988 SkASSERT((fFillType & ~3) == 0);
reed@google.comabf15c12011-01-18 20:35:51 +00001989
djsollen@google.com077348c2012-10-22 20:23:32 +00001990#ifdef SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00001991 if (!fBoundsIsDirty) {
1992 SkRect bounds;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00001993
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001994 bool isFinite = compute_pt_bounds(&bounds, *fPathRef.get());
robertphillips@google.com5d8d1862012-08-15 14:36:41 +00001995 SkASSERT(SkToBool(fIsFinite) == isFinite);
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00001996
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001997 if (fPathRef->countPoints() <= 1) {
reed@android.come522ca52009-11-23 20:10:41 +00001998 // if we're empty, fBounds may be empty but translated, so we can't
1999 // necessarily compare to bounds directly
2000 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
2001 // be [2, 2, 2, 2]
2002 SkASSERT(bounds.isEmpty());
2003 SkASSERT(fBounds.isEmpty());
2004 } else {
reed@google.comeac52bd2011-11-14 18:13:59 +00002005 if (bounds.isEmpty()) {
2006 SkASSERT(fBounds.isEmpty());
2007 } else {
reed@google.com3563c9e2011-11-14 19:34:57 +00002008 if (!fBounds.isEmpty()) {
2009 SkASSERT(fBounds.contains(bounds));
2010 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002011 }
reed@android.come522ca52009-11-23 20:10:41 +00002012 }
2013 }
djsollen@google.com077348c2012-10-22 20:23:32 +00002014#endif // SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002015}
djsollen@google.com077348c2012-10-22 20:23:32 +00002016#endif // SK_DEBUG
reed@android.come522ca52009-11-23 20:10:41 +00002017
reed@google.com04863fa2011-05-15 04:08:24 +00002018///////////////////////////////////////////////////////////////////////////////
2019
reed@google.com0b7b9822011-05-16 12:29:27 +00002020static int sign(SkScalar x) { return x < 0; }
2021#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00002022
robertphillipsc506e302014-09-16 09:43:31 -07002023enum DirChange {
2024 kLeft_DirChange,
2025 kRight_DirChange,
2026 kStraight_DirChange,
2027 kBackwards_DirChange,
2028
2029 kInvalid_DirChange
2030};
2031
2032
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002033static bool almost_equal(SkScalar compA, SkScalar compB) {
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002034 // The error epsilon was empirically derived; worse case round rects
2035 // with a mid point outset by 2x float epsilon in tests had an error
2036 // of 12.
2037 const int epsilon = 16;
2038 if (!SkScalarIsFinite(compA) || !SkScalarIsFinite(compB)) {
2039 return false;
2040 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002041 // no need to check for small numbers because SkPath::Iter has removed degenerate values
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002042 int aBits = SkFloatAs2sCompliment(compA);
2043 int bBits = SkFloatAs2sCompliment(compB);
2044 return aBits < bBits + epsilon && bBits < aBits + epsilon;
reed@google.com04863fa2011-05-15 04:08:24 +00002045}
2046
caryclarkb4216502015-03-02 13:02:34 -08002047static bool approximately_zero_when_compared_to(double x, double y) {
2048 return x == 0 || fabs(x) < fabs(y * FLT_EPSILON);
robertphillipsc506e302014-09-16 09:43:31 -07002049}
2050
caryclarkb4216502015-03-02 13:02:34 -08002051
reed@google.com04863fa2011-05-15 04:08:24 +00002052// only valid for a single contour
2053struct Convexicator {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002054 Convexicator()
2055 : fPtCount(0)
2056 , fConvexity(SkPath::kConvex_Convexity)
caryclarkd3d1a982014-12-08 04:57:38 -08002057 , fDirection(SkPath::kUnknown_Direction)
caryclark5ccef572015-03-02 10:07:56 -08002058 , fIsFinite(true)
2059 , fIsCurve(false) {
robertphillipsc506e302014-09-16 09:43:31 -07002060 fExpectedDir = kInvalid_DirChange;
reed@google.com04863fa2011-05-15 04:08:24 +00002061 // warnings
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002062 fLastPt.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002063 fCurrPt.set(0, 0);
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002064 fLastVec.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002065 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00002066
2067 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00002068 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00002069 }
2070
2071 SkPath::Convexity getConvexity() const { return fConvexity; }
2072
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002073 /** The direction returned is only valid if the path is determined convex */
2074 SkPath::Direction getDirection() const { return fDirection; }
2075
reed@google.com04863fa2011-05-15 04:08:24 +00002076 void addPt(const SkPoint& pt) {
caryclarkd3d1a982014-12-08 04:57:38 -08002077 if (SkPath::kConcave_Convexity == fConvexity || !fIsFinite) {
reed@google.com04863fa2011-05-15 04:08:24 +00002078 return;
2079 }
2080
2081 if (0 == fPtCount) {
2082 fCurrPt = pt;
2083 ++fPtCount;
2084 } else {
2085 SkVector vec = pt - fCurrPt;
caryclarkd3d1a982014-12-08 04:57:38 -08002086 SkScalar lengthSqd = vec.lengthSqd();
2087 if (!SkScalarIsFinite(lengthSqd)) {
2088 fIsFinite = false;
2089 } else if (!SkScalarNearlyZero(lengthSqd, SK_ScalarNearlyZero*SK_ScalarNearlyZero)) {
caryclarkb4216502015-03-02 13:02:34 -08002090 fPriorPt = fLastPt;
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002091 fLastPt = fCurrPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002092 fCurrPt = pt;
2093 if (++fPtCount == 2) {
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002094 fFirstVec = fLastVec = vec;
reed@google.com04863fa2011-05-15 04:08:24 +00002095 } else {
2096 SkASSERT(fPtCount > 2);
2097 this->addVec(vec);
2098 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002099
reed@google.com85b6e392011-05-15 20:25:17 +00002100 int sx = sign(vec.fX);
2101 int sy = sign(vec.fY);
2102 fDx += (sx != fSx);
2103 fDy += (sy != fSy);
2104 fSx = sx;
2105 fSy = sy;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002106
reed@google.com85b6e392011-05-15 20:25:17 +00002107 if (fDx > 3 || fDy > 3) {
2108 fConvexity = SkPath::kConcave_Convexity;
2109 }
reed@google.com04863fa2011-05-15 04:08:24 +00002110 }
2111 }
2112 }
2113
2114 void close() {
2115 if (fPtCount > 2) {
2116 this->addVec(fFirstVec);
2117 }
2118 }
2119
caryclarkb4216502015-03-02 13:02:34 -08002120 DirChange directionChange(const SkVector& curVec) {
2121 SkScalar cross = SkPoint::CrossProduct(fLastVec, curVec);
2122
2123 SkScalar smallest = SkTMin(fCurrPt.fX, SkTMin(fCurrPt.fY, SkTMin(fLastPt.fX, fLastPt.fY)));
2124 SkScalar largest = SkTMax(fCurrPt.fX, SkTMax(fCurrPt.fY, SkTMax(fLastPt.fX, fLastPt.fY)));
2125 largest = SkTMax(largest, -smallest);
2126
2127 if (!almost_equal(largest, largest + cross)) {
2128 int sign = SkScalarSignAsInt(cross);
2129 if (sign) {
2130 return (1 == sign) ? kRight_DirChange : kLeft_DirChange;
2131 }
2132 }
2133
2134 if (cross) {
2135 double dLastVecX = SkScalarToDouble(fLastPt.fX) - SkScalarToDouble(fPriorPt.fX);
2136 double dLastVecY = SkScalarToDouble(fLastPt.fY) - SkScalarToDouble(fPriorPt.fY);
2137 double dCurrVecX = SkScalarToDouble(fCurrPt.fX) - SkScalarToDouble(fLastPt.fX);
2138 double dCurrVecY = SkScalarToDouble(fCurrPt.fY) - SkScalarToDouble(fLastPt.fY);
2139 double dCross = dLastVecX * dCurrVecY - dLastVecY * dCurrVecX;
2140 if (!approximately_zero_when_compared_to(dCross, SkScalarToDouble(largest))) {
2141 int sign = SkScalarSignAsInt(SkDoubleToScalar(dCross));
2142 if (sign) {
2143 return (1 == sign) ? kRight_DirChange : kLeft_DirChange;
2144 }
2145 }
2146 }
2147
2148 if (!SkScalarNearlyZero(fLastVec.lengthSqd(), SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
2149 !SkScalarNearlyZero(curVec.lengthSqd(), SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
2150 fLastVec.dot(curVec) < 0.0f) {
2151 return kBackwards_DirChange;
2152 }
2153
2154 return kStraight_DirChange;
2155 }
2156
2157
caryclarkd3d1a982014-12-08 04:57:38 -08002158 bool isFinite() const {
2159 return fIsFinite;
2160 }
2161
caryclark5ccef572015-03-02 10:07:56 -08002162 void setCurve(bool isCurve) {
2163 fIsCurve = isCurve;
2164 }
2165
reed@google.com04863fa2011-05-15 04:08:24 +00002166private:
2167 void addVec(const SkVector& vec) {
2168 SkASSERT(vec.fX || vec.fY);
caryclarkb4216502015-03-02 13:02:34 -08002169 DirChange dir = this->directionChange(vec);
robertphillipsc506e302014-09-16 09:43:31 -07002170 switch (dir) {
2171 case kLeft_DirChange: // fall through
2172 case kRight_DirChange:
2173 if (kInvalid_DirChange == fExpectedDir) {
2174 fExpectedDir = dir;
2175 fDirection = (kRight_DirChange == dir) ? SkPath::kCW_Direction
2176 : SkPath::kCCW_Direction;
2177 } else if (dir != fExpectedDir) {
2178 fConvexity = SkPath::kConcave_Convexity;
2179 fDirection = SkPath::kUnknown_Direction;
2180 }
2181 fLastVec = vec;
2182 break;
2183 case kStraight_DirChange:
2184 break;
2185 case kBackwards_DirChange:
caryclark5ccef572015-03-02 10:07:56 -08002186 if (fIsCurve) {
2187 fConvexity = SkPath::kConcave_Convexity;
2188 fDirection = SkPath::kUnknown_Direction;
2189 }
robertphillipsc506e302014-09-16 09:43:31 -07002190 fLastVec = vec;
2191 break;
2192 case kInvalid_DirChange:
2193 SkFAIL("Use of invalid direction change flag");
2194 break;
reed@google.com04863fa2011-05-15 04:08:24 +00002195 }
2196 }
2197
caryclarkb4216502015-03-02 13:02:34 -08002198 SkPoint fPriorPt;
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002199 SkPoint fLastPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002200 SkPoint fCurrPt;
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002201 // fLastVec does not necessarily start at fLastPt. We only advance it when the cross product
2202 // value with the current vec is deemed to be of a significant value.
2203 SkVector fLastVec, fFirstVec;
reed@google.com04863fa2011-05-15 04:08:24 +00002204 int fPtCount; // non-degenerate points
robertphillipsc506e302014-09-16 09:43:31 -07002205 DirChange fExpectedDir;
reed@google.com04863fa2011-05-15 04:08:24 +00002206 SkPath::Convexity fConvexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002207 SkPath::Direction fDirection;
reed@google.com0b7b9822011-05-16 12:29:27 +00002208 int fDx, fDy, fSx, fSy;
caryclarkd3d1a982014-12-08 04:57:38 -08002209 bool fIsFinite;
caryclark5ccef572015-03-02 10:07:56 -08002210 bool fIsCurve;
reed@google.com04863fa2011-05-15 04:08:24 +00002211};
2212
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002213SkPath::Convexity SkPath::internalGetConvexity() const {
2214 SkASSERT(kUnknown_Convexity == fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002215 SkPoint pts[4];
2216 SkPath::Verb verb;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002217 SkPath::Iter iter(*this, true);
reed@google.com04863fa2011-05-15 04:08:24 +00002218
2219 int contourCount = 0;
2220 int count;
2221 Convexicator state;
2222
caryclarkd3d1a982014-12-08 04:57:38 -08002223 if (!isFinite()) {
2224 return kUnknown_Convexity;
2225 }
reed@google.com04863fa2011-05-15 04:08:24 +00002226 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
2227 switch (verb) {
2228 case kMove_Verb:
2229 if (++contourCount > 1) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002230 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002231 return kConcave_Convexity;
2232 }
2233 pts[1] = pts[0];
caryclark5ccef572015-03-02 10:07:56 -08002234 // fall through
2235 case kLine_Verb:
reed@google.com04863fa2011-05-15 04:08:24 +00002236 count = 1;
caryclark5ccef572015-03-02 10:07:56 -08002237 state.setCurve(false);
reed@google.com04863fa2011-05-15 04:08:24 +00002238 break;
caryclark5ccef572015-03-02 10:07:56 -08002239 case kQuad_Verb:
2240 // fall through
2241 case kConic_Verb:
2242 // fall through
2243 case kCubic_Verb:
2244 count = 2 + (kCubic_Verb == verb);
2245 // As an additional enhancement, this could set curve true only
2246 // if the curve is nonlinear
2247 state.setCurve(true);
2248 break;
reed@google.com04863fa2011-05-15 04:08:24 +00002249 case kClose_Verb:
caryclark5ccef572015-03-02 10:07:56 -08002250 state.setCurve(false);
reed@google.com04863fa2011-05-15 04:08:24 +00002251 state.close();
2252 count = 0;
2253 break;
2254 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00002255 SkDEBUGFAIL("bad verb");
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002256 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002257 return kConcave_Convexity;
2258 }
2259
2260 for (int i = 1; i <= count; i++) {
2261 state.addPt(pts[i]);
2262 }
2263 // early exit
caryclarkd3d1a982014-12-08 04:57:38 -08002264 if (!state.isFinite()) {
2265 return kUnknown_Convexity;
2266 }
reed@google.com04863fa2011-05-15 04:08:24 +00002267 if (kConcave_Convexity == state.getConvexity()) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002268 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002269 return kConcave_Convexity;
2270 }
2271 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002272 fConvexity = state.getConvexity();
2273 if (kConvex_Convexity == fConvexity && kUnknown_Direction == fDirection) {
2274 fDirection = state.getDirection();
2275 }
2276 return static_cast<Convexity>(fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002277}
reed@google.com69a99432012-01-10 18:00:10 +00002278
2279///////////////////////////////////////////////////////////////////////////////
2280
2281class ContourIter {
2282public:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002283 ContourIter(const SkPathRef& pathRef);
reed@google.com69a99432012-01-10 18:00:10 +00002284
2285 bool done() const { return fDone; }
2286 // if !done() then these may be called
2287 int count() const { return fCurrPtCount; }
2288 const SkPoint* pts() const { return fCurrPt; }
2289 void next();
2290
2291private:
2292 int fCurrPtCount;
2293 const SkPoint* fCurrPt;
2294 const uint8_t* fCurrVerb;
2295 const uint8_t* fStopVerbs;
reed@google.com277c3f82013-05-31 15:17:50 +00002296 const SkScalar* fCurrConicWeight;
reed@google.com69a99432012-01-10 18:00:10 +00002297 bool fDone;
reed@google.comd1ab9322012-01-10 18:40:03 +00002298 SkDEBUGCODE(int fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002299};
2300
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002301ContourIter::ContourIter(const SkPathRef& pathRef) {
2302 fStopVerbs = pathRef.verbsMemBegin();
reed@google.com69a99432012-01-10 18:00:10 +00002303 fDone = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002304 fCurrPt = pathRef.points();
2305 fCurrVerb = pathRef.verbs();
reed@google.com277c3f82013-05-31 15:17:50 +00002306 fCurrConicWeight = pathRef.conicWeights();
reed@google.com69a99432012-01-10 18:00:10 +00002307 fCurrPtCount = 0;
reed@google.comd1ab9322012-01-10 18:40:03 +00002308 SkDEBUGCODE(fContourCounter = 0;)
reed@google.com69a99432012-01-10 18:00:10 +00002309 this->next();
2310}
2311
2312void ContourIter::next() {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002313 if (fCurrVerb <= fStopVerbs) {
reed@google.com69a99432012-01-10 18:00:10 +00002314 fDone = true;
2315 }
2316 if (fDone) {
2317 return;
2318 }
2319
2320 // skip pts of prev contour
2321 fCurrPt += fCurrPtCount;
2322
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002323 SkASSERT(SkPath::kMove_Verb == fCurrVerb[~0]);
reed@google.com69a99432012-01-10 18:00:10 +00002324 int ptCount = 1; // moveTo
2325 const uint8_t* verbs = fCurrVerb;
2326
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002327 for (--verbs; verbs > fStopVerbs; --verbs) {
2328 switch (verbs[~0]) {
reed@google.com69a99432012-01-10 18:00:10 +00002329 case SkPath::kMove_Verb:
reed@google.com69a99432012-01-10 18:00:10 +00002330 goto CONTOUR_END;
2331 case SkPath::kLine_Verb:
2332 ptCount += 1;
2333 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002334 case SkPath::kConic_Verb:
2335 fCurrConicWeight += 1;
2336 // fall-through
reed@google.com69a99432012-01-10 18:00:10 +00002337 case SkPath::kQuad_Verb:
2338 ptCount += 2;
2339 break;
2340 case SkPath::kCubic_Verb:
2341 ptCount += 3;
2342 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002343 case SkPath::kClose_Verb:
2344 break;
2345 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00002346 SkDEBUGFAIL("unexpected verb");
reed@google.com69a99432012-01-10 18:00:10 +00002347 break;
2348 }
2349 }
2350CONTOUR_END:
2351 fCurrPtCount = ptCount;
2352 fCurrVerb = verbs;
reed@google.comd1ab9322012-01-10 18:40:03 +00002353 SkDEBUGCODE(++fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002354}
2355
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002356// returns cross product of (p1 - p0) and (p2 - p0)
reed@google.com69a99432012-01-10 18:00:10 +00002357static SkScalar cross_prod(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002358 SkScalar cross = SkPoint::CrossProduct(p1 - p0, p2 - p0);
2359 // We may get 0 when the above subtracts underflow. We expect this to be
2360 // very rare and lazily promote to double.
2361 if (0 == cross) {
2362 double p0x = SkScalarToDouble(p0.fX);
2363 double p0y = SkScalarToDouble(p0.fY);
2364
2365 double p1x = SkScalarToDouble(p1.fX);
2366 double p1y = SkScalarToDouble(p1.fY);
2367
2368 double p2x = SkScalarToDouble(p2.fX);
2369 double p2y = SkScalarToDouble(p2.fY);
2370
2371 cross = SkDoubleToScalar((p1x - p0x) * (p2y - p0y) -
2372 (p1y - p0y) * (p2x - p0x));
2373
2374 }
2375 return cross;
reed@google.com69a99432012-01-10 18:00:10 +00002376}
2377
reed@google.comc1ea60a2012-01-31 15:15:36 +00002378// Returns the first pt with the maximum Y coordinate
reed@google.com69a99432012-01-10 18:00:10 +00002379static int find_max_y(const SkPoint pts[], int count) {
2380 SkASSERT(count > 0);
2381 SkScalar max = pts[0].fY;
reed@google.comc1ea60a2012-01-31 15:15:36 +00002382 int firstIndex = 0;
reed@google.com69a99432012-01-10 18:00:10 +00002383 for (int i = 1; i < count; ++i) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002384 SkScalar y = pts[i].fY;
2385 if (y > max) {
2386 max = y;
2387 firstIndex = i;
reed@google.com69a99432012-01-10 18:00:10 +00002388 }
2389 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002390 return firstIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002391}
2392
reed@google.comcabaf1d2012-01-11 21:03:05 +00002393static int find_diff_pt(const SkPoint pts[], int index, int n, int inc) {
2394 int i = index;
2395 for (;;) {
2396 i = (i + inc) % n;
2397 if (i == index) { // we wrapped around, so abort
2398 break;
2399 }
2400 if (pts[index] != pts[i]) { // found a different point, success!
2401 break;
2402 }
2403 }
2404 return i;
2405}
2406
reed@google.comc1ea60a2012-01-31 15:15:36 +00002407/**
2408 * Starting at index, and moving forward (incrementing), find the xmin and
2409 * xmax of the contiguous points that have the same Y.
2410 */
2411static int find_min_max_x_at_y(const SkPoint pts[], int index, int n,
2412 int* maxIndexPtr) {
2413 const SkScalar y = pts[index].fY;
2414 SkScalar min = pts[index].fX;
2415 SkScalar max = min;
2416 int minIndex = index;
2417 int maxIndex = index;
2418 for (int i = index + 1; i < n; ++i) {
2419 if (pts[i].fY != y) {
2420 break;
2421 }
2422 SkScalar x = pts[i].fX;
2423 if (x < min) {
2424 min = x;
2425 minIndex = i;
2426 } else if (x > max) {
2427 max = x;
2428 maxIndex = i;
2429 }
2430 }
2431 *maxIndexPtr = maxIndex;
2432 return minIndex;
2433}
2434
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002435static void crossToDir(SkScalar cross, SkPath::Direction* dir) {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002436 *dir = cross > 0 ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002437}
2438
reed@google.comac8543f2012-01-30 20:51:25 +00002439/*
2440 * We loop through all contours, and keep the computed cross-product of the
2441 * contour that contained the global y-max. If we just look at the first
2442 * contour, we may find one that is wound the opposite way (correctly) since
2443 * it is the interior of a hole (e.g. 'o'). Thus we must find the contour
2444 * that is outer most (or at least has the global y-max) before we can consider
2445 * its cross product.
2446 */
reed@google.com69a99432012-01-10 18:00:10 +00002447bool SkPath::cheapComputeDirection(Direction* dir) const {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002448 if (kUnknown_Direction != fDirection) {
2449 *dir = static_cast<Direction>(fDirection);
2450 return true;
2451 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002452
2453 // don't want to pay the cost for computing this if it
2454 // is unknown, so we don't call isConvex()
2455 if (kConvex_Convexity == this->getConvexityOrUnknown()) {
2456 SkASSERT(kUnknown_Direction == fDirection);
2457 *dir = static_cast<Direction>(fDirection);
2458 return false;
2459 }
reed@google.com69a99432012-01-10 18:00:10 +00002460
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002461 ContourIter iter(*fPathRef.get());
reed@google.com69a99432012-01-10 18:00:10 +00002462
reed@google.comac8543f2012-01-30 20:51:25 +00002463 // initialize with our logical y-min
2464 SkScalar ymax = this->getBounds().fTop;
2465 SkScalar ymaxCross = 0;
2466
reed@google.com69a99432012-01-10 18:00:10 +00002467 for (; !iter.done(); iter.next()) {
2468 int n = iter.count();
reed@google.comcabaf1d2012-01-11 21:03:05 +00002469 if (n < 3) {
2470 continue;
2471 }
djsollen@google.come63793a2012-03-21 15:39:03 +00002472
reed@google.comcabaf1d2012-01-11 21:03:05 +00002473 const SkPoint* pts = iter.pts();
reed@google.com69a99432012-01-10 18:00:10 +00002474 SkScalar cross = 0;
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002475 int index = find_max_y(pts, n);
2476 if (pts[index].fY < ymax) {
2477 continue;
2478 }
2479
2480 // If there is more than 1 distinct point at the y-max, we take the
2481 // x-min and x-max of them and just subtract to compute the dir.
2482 if (pts[(index + 1) % n].fY == pts[index].fY) {
2483 int maxIndex;
2484 int minIndex = find_min_max_x_at_y(pts, index, n, &maxIndex);
2485 if (minIndex == maxIndex) {
2486 goto TRY_CROSSPROD;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002487 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002488 SkASSERT(pts[minIndex].fY == pts[index].fY);
2489 SkASSERT(pts[maxIndex].fY == pts[index].fY);
2490 SkASSERT(pts[minIndex].fX <= pts[maxIndex].fX);
2491 // we just subtract the indices, and let that auto-convert to
2492 // SkScalar, since we just want - or + to signal the direction.
2493 cross = minIndex - maxIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002494 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002495 TRY_CROSSPROD:
2496 // Find a next and prev index to use for the cross-product test,
2497 // but we try to find pts that form non-zero vectors from pts[index]
2498 //
2499 // Its possible that we can't find two non-degenerate vectors, so
2500 // we have to guard our search (e.g. all the pts could be in the
2501 // same place).
2502
2503 // we pass n - 1 instead of -1 so we don't foul up % operator by
2504 // passing it a negative LH argument.
2505 int prev = find_diff_pt(pts, index, n, n - 1);
2506 if (prev == index) {
2507 // completely degenerate, skip to next contour
reed@google.comac8543f2012-01-30 20:51:25 +00002508 continue;
2509 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002510 int next = find_diff_pt(pts, index, n, 1);
2511 SkASSERT(next != index);
2512 cross = cross_prod(pts[prev], pts[index], pts[next]);
2513 // if we get a zero and the points are horizontal, then we look at the spread in
2514 // x-direction. We really should continue to walk away from the degeneracy until
2515 // there is a divergence.
2516 if (0 == cross && pts[prev].fY == pts[index].fY && pts[next].fY == pts[index].fY) {
2517 // construct the subtract so we get the correct Direction below
2518 cross = pts[index].fX - pts[next].fX;
reed@google.com188bfcf2012-01-17 18:26:38 +00002519 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002520 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002521
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002522 if (cross) {
2523 // record our best guess so far
2524 ymax = pts[index].fY;
2525 ymaxCross = cross;
reed@google.com69a99432012-01-10 18:00:10 +00002526 }
2527 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002528 if (ymaxCross) {
2529 crossToDir(ymaxCross, dir);
2530 fDirection = *dir;
2531 return true;
2532 } else {
2533 return false;
2534 }
reed@google.comac8543f2012-01-30 20:51:25 +00002535}
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002536
2537///////////////////////////////////////////////////////////////////////////////
2538
2539static SkScalar eval_cubic_coeff(SkScalar A, SkScalar B, SkScalar C,
2540 SkScalar D, SkScalar t) {
2541 return SkScalarMulAdd(SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C), t, D);
2542}
2543
2544static SkScalar eval_cubic_pts(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2545 SkScalar t) {
2546 SkScalar A = c3 + 3*(c1 - c2) - c0;
2547 SkScalar B = 3*(c2 - c1 - c1 + c0);
2548 SkScalar C = 3*(c1 - c0);
2549 SkScalar D = c0;
2550 return eval_cubic_coeff(A, B, C, D, t);
2551}
2552
2553/* Given 4 cubic points (either Xs or Ys), and a target X or Y, compute the
2554 t value such that cubic(t) = target
2555 */
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002556static void chopMonoCubicAt(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002557 SkScalar target, SkScalar* t) {
2558 // SkASSERT(c0 <= c1 && c1 <= c2 && c2 <= c3);
2559 SkASSERT(c0 < target && target < c3);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002560
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002561 SkScalar D = c0 - target;
2562 SkScalar A = c3 + 3*(c1 - c2) - c0;
2563 SkScalar B = 3*(c2 - c1 - c1 + c0);
2564 SkScalar C = 3*(c1 - c0);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002565
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002566 const SkScalar TOLERANCE = SK_Scalar1 / 4096;
2567 SkScalar minT = 0;
2568 SkScalar maxT = SK_Scalar1;
2569 SkScalar mid;
2570 int i;
2571 for (i = 0; i < 16; i++) {
2572 mid = SkScalarAve(minT, maxT);
2573 SkScalar delta = eval_cubic_coeff(A, B, C, D, mid);
2574 if (delta < 0) {
2575 minT = mid;
2576 delta = -delta;
2577 } else {
2578 maxT = mid;
2579 }
2580 if (delta < TOLERANCE) {
2581 break;
2582 }
2583 }
2584 *t = mid;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002585}
2586
2587template <size_t N> static void find_minmax(const SkPoint pts[],
2588 SkScalar* minPtr, SkScalar* maxPtr) {
2589 SkScalar min, max;
2590 min = max = pts[0].fX;
2591 for (size_t i = 1; i < N; ++i) {
2592 min = SkMinScalar(min, pts[i].fX);
2593 max = SkMaxScalar(max, pts[i].fX);
2594 }
2595 *minPtr = min;
2596 *maxPtr = max;
2597}
2598
2599static int winding_mono_cubic(const SkPoint pts[], SkScalar x, SkScalar y) {
2600 SkPoint storage[4];
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002601
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002602 int dir = 1;
2603 if (pts[0].fY > pts[3].fY) {
2604 storage[0] = pts[3];
2605 storage[1] = pts[2];
2606 storage[2] = pts[1];
2607 storage[3] = pts[0];
2608 pts = storage;
2609 dir = -1;
2610 }
2611 if (y < pts[0].fY || y >= pts[3].fY) {
2612 return 0;
2613 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002614
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002615 // quickreject or quickaccept
2616 SkScalar min, max;
2617 find_minmax<4>(pts, &min, &max);
2618 if (x < min) {
2619 return 0;
2620 }
2621 if (x > max) {
2622 return dir;
2623 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002624
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002625 // compute the actual x(t) value
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002626 SkScalar t;
2627 chopMonoCubicAt(pts[0].fY, pts[1].fY, pts[2].fY, pts[3].fY, y, &t);
2628 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 +00002629 return xt < x ? dir : 0;
2630}
2631
2632static int winding_cubic(const SkPoint pts[], SkScalar x, SkScalar y) {
2633 SkPoint dst[10];
2634 int n = SkChopCubicAtYExtrema(pts, dst);
2635 int w = 0;
2636 for (int i = 0; i <= n; ++i) {
2637 w += winding_mono_cubic(&dst[i * 3], x, y);
2638 }
2639 return w;
2640}
2641
2642static int winding_mono_quad(const SkPoint pts[], SkScalar x, SkScalar y) {
2643 SkScalar y0 = pts[0].fY;
2644 SkScalar y2 = pts[2].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002645
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002646 int dir = 1;
2647 if (y0 > y2) {
2648 SkTSwap(y0, y2);
2649 dir = -1;
2650 }
2651 if (y < y0 || y >= y2) {
2652 return 0;
2653 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002654
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002655 // bounds check on X (not required. is it faster?)
2656#if 0
2657 if (pts[0].fX > x && pts[1].fX > x && pts[2].fX > x) {
2658 return 0;
2659 }
2660#endif
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002661
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002662 SkScalar roots[2];
2663 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
2664 2 * (pts[1].fY - pts[0].fY),
2665 pts[0].fY - y,
2666 roots);
2667 SkASSERT(n <= 1);
2668 SkScalar xt;
2669 if (0 == n) {
2670 SkScalar mid = SkScalarAve(y0, y2);
2671 // Need [0] and [2] if dir == 1
2672 // and [2] and [0] if dir == -1
2673 xt = y < mid ? pts[1 - dir].fX : pts[dir - 1].fX;
2674 } else {
2675 SkScalar t = roots[0];
2676 SkScalar C = pts[0].fX;
2677 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
2678 SkScalar B = 2 * (pts[1].fX - C);
2679 xt = SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C);
2680 }
2681 return xt < x ? dir : 0;
2682}
2683
2684static bool is_mono_quad(SkScalar y0, SkScalar y1, SkScalar y2) {
2685 // return SkScalarSignAsInt(y0 - y1) + SkScalarSignAsInt(y1 - y2) != 0;
2686 if (y0 == y1) {
2687 return true;
2688 }
2689 if (y0 < y1) {
2690 return y1 <= y2;
2691 } else {
2692 return y1 >= y2;
2693 }
2694}
2695
2696static int winding_quad(const SkPoint pts[], SkScalar x, SkScalar y) {
2697 SkPoint dst[5];
2698 int n = 0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002699
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002700 if (!is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY)) {
2701 n = SkChopQuadAtYExtrema(pts, dst);
2702 pts = dst;
2703 }
2704 int w = winding_mono_quad(pts, x, y);
2705 if (n > 0) {
2706 w += winding_mono_quad(&pts[2], x, y);
2707 }
2708 return w;
2709}
2710
2711static int winding_line(const SkPoint pts[], SkScalar x, SkScalar y) {
2712 SkScalar x0 = pts[0].fX;
2713 SkScalar y0 = pts[0].fY;
2714 SkScalar x1 = pts[1].fX;
2715 SkScalar y1 = pts[1].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002716
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002717 SkScalar dy = y1 - y0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002718
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002719 int dir = 1;
2720 if (y0 > y1) {
2721 SkTSwap(y0, y1);
2722 dir = -1;
2723 }
2724 if (y < y0 || y >= y1) {
2725 return 0;
2726 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002727
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002728 SkScalar cross = SkScalarMul(x1 - x0, y - pts[0].fY) -
2729 SkScalarMul(dy, x - pts[0].fX);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002730
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002731 if (SkScalarSignAsInt(cross) == dir) {
2732 dir = 0;
2733 }
2734 return dir;
2735}
2736
reed@google.com4db592c2013-10-30 17:39:43 +00002737static bool contains_inclusive(const SkRect& r, SkScalar x, SkScalar y) {
2738 return r.fLeft <= x && x <= r.fRight && r.fTop <= y && y <= r.fBottom;
2739}
2740
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002741bool SkPath::contains(SkScalar x, SkScalar y) const {
2742 bool isInverse = this->isInverseFillType();
2743 if (this->isEmpty()) {
2744 return isInverse;
2745 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002746
reed@google.com4db592c2013-10-30 17:39:43 +00002747 if (!contains_inclusive(this->getBounds(), x, y)) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002748 return isInverse;
2749 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002750
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002751 SkPath::Iter iter(*this, true);
2752 bool done = false;
2753 int w = 0;
2754 do {
2755 SkPoint pts[4];
2756 switch (iter.next(pts, false)) {
2757 case SkPath::kMove_Verb:
2758 case SkPath::kClose_Verb:
2759 break;
2760 case SkPath::kLine_Verb:
2761 w += winding_line(pts, x, y);
2762 break;
2763 case SkPath::kQuad_Verb:
2764 w += winding_quad(pts, x, y);
2765 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002766 case SkPath::kConic_Verb:
2767 SkASSERT(0);
2768 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002769 case SkPath::kCubic_Verb:
2770 w += winding_cubic(pts, x, y);
2771 break;
2772 case SkPath::kDone_Verb:
2773 done = true;
2774 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002775 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002776 } while (!done);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002777
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002778 switch (this->getFillType()) {
2779 case SkPath::kEvenOdd_FillType:
2780 case SkPath::kInverseEvenOdd_FillType:
2781 w &= 1;
2782 break;
reed@google.come9bb6232012-07-11 18:56:10 +00002783 default:
2784 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002785 }
2786 return SkToBool(w);
2787}