blob: ab8d7359d2271b5a15b531859f44379522a4b378 [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"
caryclark9aacd902015-12-14 08:38:09 -08009#include "SkCubicClipper.h"
humper@google.com75e3ca12013-04-08 21:44:11 +000010#include "SkErrorInternals.h"
reed220f9262014-12-17 08:21:04 -080011#include "SkGeometry.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000012#include "SkMath.h"
reed026beb52015-06-10 14:23:15 -070013#include "SkPathPriv.h"
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +000014#include "SkPathRef.h"
reed@google.com4ed0fb72012-12-12 20:48:18 +000015#include "SkRRect.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) {
herb9f4dbca2015-09-28 11:05:47 -070040 fSaved = static_cast<SkPathPriv::FirstDirection>(fPath->fFirstDirection.load());
bsalomon@google.com30c174b2012-11-13 14:36:42 +000041 }
42
43 ~SkAutoDisableDirectionCheck() {
reed026beb52015-06-10 14:23:15 -070044 fPath->fFirstDirection = fSaved;
bsalomon@google.com30c174b2012-11-13 14:36:42 +000045 }
46
47private:
reed026beb52015-06-10 14:23:15 -070048 SkPath* fPath;
49 SkPathPriv::FirstDirection fSaved;
bsalomon@google.com30c174b2012-11-13 14:36:42 +000050};
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;
reed026beb52015-06-10 14:23:15 -0700138 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
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
halcanary96fcdcc2015-08-27 07:41:13 -0700141 // don't want to muck with it if it's been set to something non-nullptr.
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;
herb9f4dbca2015-09-28 11:05:47 -0700170 // Simulate fFirstDirection = that.fFirstDirection;
171 fFirstDirection.store(that.fFirstDirection.load());
jvanverthb3eb6872014-10-24 07:12:51 -0700172 fIsVolatile = that.fIsVolatile;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000173}
174
djsollen@google.com9c1a9672013-08-09 13:49:13 +0000175bool operator==(const SkPath& a, const SkPath& b) {
reed@android.com6b82d1a2009-06-03 02:35:01 +0000176 // note: don't need to look at isConvex or bounds, since just comparing the
177 // raw data is sufficient.
reed@android.com3abec1d2009-03-02 05:36:20 +0000178 return &a == &b ||
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000179 (a.fFillType == b.fFillType && *a.fPathRef.get() == *b.fPathRef.get());
reed@android.com3abec1d2009-03-02 05:36:20 +0000180}
181
bungeman@google.coma5809a32013-06-21 15:13:34 +0000182void SkPath::swap(SkPath& that) {
bungeman@google.coma5809a32013-06-21 15:13:34 +0000183 if (this != &that) {
bungeman77a53de2015-10-01 12:28:49 -0700184 fPathRef.swap(that.fPathRef);
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000185 SkTSwap<int>(fLastMoveToIndex, that.fLastMoveToIndex);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000186 SkTSwap<uint8_t>(fFillType, that.fFillType);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000187 SkTSwap<uint8_t>(fConvexity, that.fConvexity);
herb9f4dbca2015-09-28 11:05:47 -0700188 // Simulate SkTSwap<uint8_t>(fFirstDirection, that.fFirstDirection);
189 uint8_t temp = fFirstDirection;
190 fFirstDirection.store(that.fFirstDirection.load());
191 that.fFirstDirection.store(temp);
jvanverthb3eb6872014-10-24 07:12:51 -0700192 SkTSwap<SkBool8>(fIsVolatile, that.fIsVolatile);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000193 }
194}
195
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000196static inline bool check_edge_against_rect(const SkPoint& p0,
197 const SkPoint& p1,
198 const SkRect& rect,
reed026beb52015-06-10 14:23:15 -0700199 SkPathPriv::FirstDirection dir) {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000200 const SkPoint* edgeBegin;
201 SkVector v;
reed026beb52015-06-10 14:23:15 -0700202 if (SkPathPriv::kCW_FirstDirection == dir) {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000203 v = p1 - p0;
204 edgeBegin = &p0;
205 } else {
206 v = p0 - p1;
207 edgeBegin = &p1;
208 }
209 if (v.fX || v.fY) {
210 // check the cross product of v with the vec from edgeBegin to each rect corner
211 SkScalar yL = SkScalarMul(v.fY, rect.fLeft - edgeBegin->fX);
212 SkScalar xT = SkScalarMul(v.fX, rect.fTop - edgeBegin->fY);
213 SkScalar yR = SkScalarMul(v.fY, rect.fRight - edgeBegin->fX);
214 SkScalar xB = SkScalarMul(v.fX, rect.fBottom - edgeBegin->fY);
215 if ((xT < yL) || (xT < yR) || (xB < yL) || (xB < yR)) {
216 return false;
217 }
218 }
219 return true;
220}
221
222bool SkPath::conservativelyContainsRect(const SkRect& rect) const {
223 // This only handles non-degenerate convex paths currently.
224 if (kConvex_Convexity != this->getConvexity()) {
225 return false;
226 }
skia.committer@gmail.comcec8de62012-11-14 02:01:22 +0000227
reed026beb52015-06-10 14:23:15 -0700228 SkPathPriv::FirstDirection direction;
229 if (!SkPathPriv::CheapComputeFirstDirection(*this, &direction)) {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000230 return false;
231 }
232
233 SkPoint firstPt;
234 SkPoint prevPt;
235 RawIter iter(*this);
236 SkPath::Verb verb;
237 SkPoint pts[4];
238 SkDEBUGCODE(int moveCnt = 0;)
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000239 SkDEBUGCODE(int segmentCount = 0;)
240 SkDEBUGCODE(int closeCount = 0;)
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000241
242 while ((verb = iter.next(pts)) != kDone_Verb) {
243 int nextPt = -1;
244 switch (verb) {
245 case kMove_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000246 SkASSERT(!segmentCount && !closeCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000247 SkDEBUGCODE(++moveCnt);
248 firstPt = prevPt = pts[0];
249 break;
250 case kLine_Verb:
251 nextPt = 1;
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000252 SkASSERT(moveCnt && !closeCount);
253 SkDEBUGCODE(++segmentCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000254 break;
255 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000256 case kConic_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000257 SkASSERT(moveCnt && !closeCount);
258 SkDEBUGCODE(++segmentCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000259 nextPt = 2;
260 break;
261 case kCubic_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000262 SkASSERT(moveCnt && !closeCount);
263 SkDEBUGCODE(++segmentCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000264 nextPt = 3;
265 break;
266 case kClose_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000267 SkDEBUGCODE(++closeCount;)
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000268 break;
269 default:
270 SkDEBUGFAIL("unknown verb");
271 }
272 if (-1 != nextPt) {
reed220f9262014-12-17 08:21:04 -0800273 if (SkPath::kConic_Verb == verb) {
274 SkConic orig;
275 orig.set(pts, iter.conicWeight());
276 SkPoint quadPts[5];
277 int count = orig.chopIntoQuadsPOW2(quadPts, 1);
278 SK_ALWAYSBREAK(2 == count);
279
280 if (!check_edge_against_rect(quadPts[0], quadPts[2], rect, direction)) {
281 return false;
282 }
283 if (!check_edge_against_rect(quadPts[2], quadPts[4], rect, direction)) {
284 return false;
285 }
286 } else {
287 if (!check_edge_against_rect(prevPt, pts[nextPt], rect, direction)) {
288 return false;
289 }
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000290 }
291 prevPt = pts[nextPt];
292 }
293 }
294
295 return check_edge_against_rect(prevPt, firstPt, rect, direction);
296}
297
robertphillips@google.com7101abe2013-10-29 22:45:37 +0000298uint32_t SkPath::getGenerationID() const {
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000299 uint32_t genID = fPathRef->genID();
djsollen523cda32015-02-17 08:06:32 -0800300#ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000301 SkASSERT((unsigned)fFillType < (1 << (32 - kPathRefGenIDBitCnt)));
302 genID |= static_cast<uint32_t>(fFillType) << kPathRefGenIDBitCnt;
303#endif
304 return genID;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000305}
djsollen@google.come63793a2012-03-21 15:39:03 +0000306
reed@android.com8a1c16f2008-12-17 15:59:43 +0000307void SkPath::reset() {
308 SkDEBUGCODE(this->validate();)
309
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000310 fPathRef.reset(SkPathRef::CreateEmpty());
bungeman@google.coma5809a32013-06-21 15:13:34 +0000311 this->resetFields();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000312}
313
314void SkPath::rewind() {
315 SkDEBUGCODE(this->validate();)
316
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000317 SkPathRef::Rewind(&fPathRef);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000318 this->resetFields();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000319}
320
reed@google.com7e6c4d12012-05-10 14:05:43 +0000321bool SkPath::isLine(SkPoint line[2]) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000322 int verbCount = fPathRef->countVerbs();
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000323
commit-bot@chromium.orga62efcc2013-08-05 13:23:13 +0000324 if (2 == verbCount) {
325 SkASSERT(kMove_Verb == fPathRef->atVerb(0));
326 if (kLine_Verb == fPathRef->atVerb(1)) {
327 SkASSERT(2 == fPathRef->countPoints());
reed@google.com7e6c4d12012-05-10 14:05:43 +0000328 if (line) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000329 const SkPoint* pts = fPathRef->points();
reed@google.com7e6c4d12012-05-10 14:05:43 +0000330 line[0] = pts[0];
331 line[1] = pts[1];
332 }
333 return true;
334 }
335 }
336 return false;
337}
338
caryclark@google.comf1316942011-07-26 19:54:45 +0000339/*
340 Determines if path is a rect by keeping track of changes in direction
341 and looking for a loop either clockwise or counterclockwise.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000342
caryclark@google.comf1316942011-07-26 19:54:45 +0000343 The direction is computed such that:
344 0: vertical up
caryclark@google.comf68154a2012-11-21 15:18:06 +0000345 1: horizontal left
caryclark@google.comf1316942011-07-26 19:54:45 +0000346 2: vertical down
caryclark@google.comf68154a2012-11-21 15:18:06 +0000347 3: horizontal right
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000348
caryclark@google.comf1316942011-07-26 19:54:45 +0000349A rectangle cycles up/right/down/left or up/left/down/right.
350
351The test fails if:
352 The path is closed, and followed by a line.
353 A second move creates a new endpoint.
354 A diagonal line is parsed.
355 There's more than four changes of direction.
356 There's a discontinuity on the line (e.g., a move in the middle)
357 The line reverses direction.
caryclark@google.comf1316942011-07-26 19:54:45 +0000358 The path contains a quadratic or cubic.
359 The path contains fewer than four points.
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000360 *The rectangle doesn't complete a cycle.
361 *The final point isn't equal to the first point.
362
363 *These last two conditions we relax if we have a 3-edge path that would
364 form a rectangle if it were closed (as we do when we fill a path)
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000365
caryclark@google.comf1316942011-07-26 19:54:45 +0000366It's OK if the path has:
367 Several colinear line segments composing a rectangle side.
368 Single points on the rectangle side.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000369
caryclark@google.comf1316942011-07-26 19:54:45 +0000370The direction takes advantage of the corners found since opposite sides
371must travel in opposite directions.
372
373FIXME: Allow colinear quads and cubics to be treated like lines.
374FIXME: If the API passes fill-only, return true if the filled stroke
375 is a rectangle, though the caller failed to close the path.
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000376
377 first,last,next direction state-machine:
378 0x1 is set if the segment is horizontal
379 0x2 is set if the segment is moving to the right or down
380 thus:
381 two directions are opposites iff (dirA ^ dirB) == 0x2
382 two directions are perpendicular iff (dirA ^ dirB) == 0x1
skia.committer@gmail.comf5e67c12014-01-16 07:01:48 +0000383
caryclark@google.comf1316942011-07-26 19:54:45 +0000384 */
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000385static int rect_make_dir(SkScalar dx, SkScalar dy) {
386 return ((0 != dx) << 0) | ((dx > 0 || dy > 0) << 1);
387}
caryclark@google.comf68154a2012-11-21 15:18:06 +0000388bool SkPath::isRectContour(bool allowPartial, int* currVerb, const SkPoint** ptsPtr,
389 bool* isClosed, Direction* direction) const {
caryclark@google.comf1316942011-07-26 19:54:45 +0000390 int corners = 0;
391 SkPoint first, last;
caryclark@google.com56f233a2012-11-19 13:06:06 +0000392 const SkPoint* pts = *ptsPtr;
halcanary96fcdcc2015-08-27 07:41:13 -0700393 const SkPoint* savePts = nullptr;
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000394 first.set(0, 0);
395 last.set(0, 0);
396 int firstDirection = 0;
397 int lastDirection = 0;
398 int nextDirection = 0;
399 bool closedOrMoved = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000400 bool autoClose = false;
caryclark95bc5f32015-04-08 08:34:15 -0700401 bool insertClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000402 int verbCnt = fPathRef->countVerbs();
caryclark@google.com56f233a2012-11-19 13:06:06 +0000403 while (*currVerb < verbCnt && (!allowPartial || !autoClose)) {
caryclark95bc5f32015-04-08 08:34:15 -0700404 uint8_t verb = insertClose ? (uint8_t) kClose_Verb : fPathRef->atVerb(*currVerb);
405 switch (verb) {
caryclark@google.comf1316942011-07-26 19:54:45 +0000406 case kClose_Verb:
caryclark@google.com56f233a2012-11-19 13:06:06 +0000407 savePts = pts;
408 pts = *ptsPtr;
caryclark@google.comf1316942011-07-26 19:54:45 +0000409 autoClose = true;
caryclark95bc5f32015-04-08 08:34:15 -0700410 insertClose = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000411 case kLine_Verb: {
412 SkScalar left = last.fX;
413 SkScalar top = last.fY;
414 SkScalar right = pts->fX;
415 SkScalar bottom = pts->fY;
416 ++pts;
417 if (left != right && top != bottom) {
418 return false; // diagonal
419 }
420 if (left == right && top == bottom) {
421 break; // single point on side OK
422 }
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000423 nextDirection = rect_make_dir(right - left, bottom - top);
caryclark@google.comf1316942011-07-26 19:54:45 +0000424 if (0 == corners) {
425 firstDirection = nextDirection;
426 first = last;
427 last = pts[-1];
428 corners = 1;
429 closedOrMoved = false;
430 break;
431 }
432 if (closedOrMoved) {
433 return false; // closed followed by a line
434 }
caryclark@google.combfe90372012-11-21 13:56:20 +0000435 if (autoClose && nextDirection == firstDirection) {
436 break; // colinear with first
437 }
caryclark@google.comf1316942011-07-26 19:54:45 +0000438 closedOrMoved = autoClose;
439 if (lastDirection != nextDirection) {
440 if (++corners > 4) {
441 return false; // too many direction changes
442 }
443 }
444 last = pts[-1];
445 if (lastDirection == nextDirection) {
446 break; // colinear segment
447 }
448 // Possible values for corners are 2, 3, and 4.
449 // When corners == 3, nextDirection opposes firstDirection.
450 // Otherwise, nextDirection at corner 2 opposes corner 4.
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000451 int turn = firstDirection ^ (corners - 1);
caryclark@google.comf1316942011-07-26 19:54:45 +0000452 int directionCycle = 3 == corners ? 0 : nextDirection ^ turn;
453 if ((directionCycle ^ turn) != nextDirection) {
454 return false; // direction didn't follow cycle
455 }
456 break;
457 }
458 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000459 case kConic_Verb:
caryclark@google.comf1316942011-07-26 19:54:45 +0000460 case kCubic_Verb:
461 return false; // quadratic, cubic not allowed
462 case kMove_Verb:
caryclark95bc5f32015-04-08 08:34:15 -0700463 if (allowPartial && !autoClose && firstDirection) {
464 insertClose = true;
465 *currVerb -= 1; // try move again afterwards
466 goto addMissingClose;
467 }
caryclark@google.comf1316942011-07-26 19:54:45 +0000468 last = *pts++;
469 closedOrMoved = true;
470 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000471 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000472 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000473 break;
caryclark@google.comf1316942011-07-26 19:54:45 +0000474 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000475 *currVerb += 1;
caryclark@google.comf1316942011-07-26 19:54:45 +0000476 lastDirection = nextDirection;
caryclark95bc5f32015-04-08 08:34:15 -0700477addMissingClose:
478 ;
caryclark@google.comf1316942011-07-26 19:54:45 +0000479 }
480 // Success if 4 corners and first point equals last
caryclark@google.combfe90372012-11-21 13:56:20 +0000481 bool result = 4 == corners && (first == last || autoClose);
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000482 if (!result) {
483 // check if we are just an incomplete rectangle, in which case we can
484 // return true, but not claim to be closed.
485 // e.g.
486 // 3 sided rectangle
487 // 4 sided but the last edge is not long enough to reach the start
488 //
489 SkScalar closeX = first.x() - last.x();
490 SkScalar closeY = first.y() - last.y();
491 if (closeX && closeY) {
492 return false; // we're diagonal, abort (can we ever reach this?)
493 }
494 int closeDirection = rect_make_dir(closeX, closeY);
495 // make sure the close-segment doesn't double-back on itself
496 if (3 == corners || (4 == corners && closeDirection == lastDirection)) {
497 result = true;
498 autoClose = false; // we are not closed
499 }
500 }
caryclark@google.combfe90372012-11-21 13:56:20 +0000501 if (savePts) {
502 *ptsPtr = savePts;
503 }
caryclark@google.comf68154a2012-11-21 15:18:06 +0000504 if (result && isClosed) {
505 *isClosed = autoClose;
506 }
507 if (result && direction) {
sugoi@google.com12b4e272012-12-06 20:13:11 +0000508 *direction = firstDirection == ((lastDirection + 1) & 3) ? kCCW_Direction : kCW_Direction;
caryclark@google.comf68154a2012-11-21 15:18:06 +0000509 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000510 return result;
511}
512
robertphillips4f662e62014-12-29 14:06:51 -0800513bool SkPath::isRect(SkRect* rect, bool* isClosed, Direction* direction) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000514 SkDEBUGCODE(this->validate();)
515 int currVerb = 0;
516 const SkPoint* pts = fPathRef->points();
robertphillipsfe7c4272014-12-29 11:36:39 -0800517 const SkPoint* first = pts;
robertphillips4f662e62014-12-29 14:06:51 -0800518 if (!this->isRectContour(false, &currVerb, &pts, isClosed, direction)) {
robertphillipsfe7c4272014-12-29 11:36:39 -0800519 return false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000520 }
robertphillipsfe7c4272014-12-29 11:36:39 -0800521 if (rect) {
robertphillips4f662e62014-12-29 14:06:51 -0800522 int32_t num = SkToS32(pts - first);
523 if (num) {
524 rect->set(first, num);
robertphillipsfe7c4272014-12-29 11:36:39 -0800525 } else {
526 // 'pts' isn't updated for open rects
527 *rect = this->getBounds();
528 }
529 }
530 return true;
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000531}
skia.committer@gmail.com020b25b2013-06-22 07:00:58 +0000532
caryclark95bc5f32015-04-08 08:34:15 -0700533bool SkPath::isNestedFillRects(SkRect rects[2], Direction dirs[2]) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000534 SkDEBUGCODE(this->validate();)
535 int currVerb = 0;
536 const SkPoint* pts = fPathRef->points();
537 const SkPoint* first = pts;
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000538 Direction testDirs[2];
halcanary96fcdcc2015-08-27 07:41:13 -0700539 if (!isRectContour(true, &currVerb, &pts, nullptr, &testDirs[0])) {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000540 return false;
541 }
542 const SkPoint* last = pts;
543 SkRect testRects[2];
caryclark95bc5f32015-04-08 08:34:15 -0700544 bool isClosed;
545 if (isRectContour(false, &currVerb, &pts, &isClosed, &testDirs[1])) {
scroggo@google.com614f9e32013-05-09 18:05:32 +0000546 testRects[0].set(first, SkToS32(last - first));
caryclark95bc5f32015-04-08 08:34:15 -0700547 if (!isClosed) {
548 pts = fPathRef->points() + fPathRef->countPoints();
549 }
scroggo@google.com614f9e32013-05-09 18:05:32 +0000550 testRects[1].set(last, SkToS32(pts - last));
caryclark@google.com56f233a2012-11-19 13:06:06 +0000551 if (testRects[0].contains(testRects[1])) {
552 if (rects) {
553 rects[0] = testRects[0];
554 rects[1] = testRects[1];
555 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000556 if (dirs) {
557 dirs[0] = testDirs[0];
558 dirs[1] = testDirs[1];
559 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000560 return true;
561 }
562 if (testRects[1].contains(testRects[0])) {
563 if (rects) {
564 rects[0] = testRects[1];
565 rects[1] = testRects[0];
566 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000567 if (dirs) {
568 dirs[0] = testDirs[1];
569 dirs[1] = testDirs[0];
570 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000571 return true;
572 }
573 }
574 return false;
575}
576
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000577int SkPath::countPoints() const {
578 return fPathRef->countPoints();
579}
580
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000581int SkPath::getPoints(SkPoint dst[], int max) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000582 SkDEBUGCODE(this->validate();)
583
584 SkASSERT(max >= 0);
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000585 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000586 int count = SkMin32(max, fPathRef->countPoints());
mtklein8bf5d172015-12-09 13:15:07 -0800587 sk_careful_memcpy(dst, fPathRef->points(), count * sizeof(SkPoint));
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000588 return fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000589}
590
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000591SkPoint SkPath::getPoint(int index) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000592 if ((unsigned)index < (unsigned)fPathRef->countPoints()) {
593 return fPathRef->atPoint(index);
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000594 }
595 return SkPoint::Make(0, 0);
596}
597
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000598int SkPath::countVerbs() const {
599 return fPathRef->countVerbs();
600}
601
602static inline void copy_verbs_reverse(uint8_t* inorderDst,
603 const uint8_t* reversedSrc,
604 int count) {
605 for (int i = 0; i < count; ++i) {
606 inorderDst[i] = reversedSrc[~i];
607 }
608}
609
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000610int SkPath::getVerbs(uint8_t dst[], int max) const {
611 SkDEBUGCODE(this->validate();)
612
613 SkASSERT(max >= 0);
614 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000615 int count = SkMin32(max, fPathRef->countVerbs());
616 copy_verbs_reverse(dst, fPathRef->verbs(), count);
617 return fPathRef->countVerbs();
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000618}
619
reed@google.com294dd7b2011-10-11 11:58:32 +0000620bool SkPath::getLastPt(SkPoint* lastPt) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000621 SkDEBUGCODE(this->validate();)
622
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000623 int count = fPathRef->countPoints();
reed@google.com294dd7b2011-10-11 11:58:32 +0000624 if (count > 0) {
625 if (lastPt) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000626 *lastPt = fPathRef->atPoint(count - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000627 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000628 return true;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000629 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000630 if (lastPt) {
631 lastPt->set(0, 0);
632 }
633 return false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000634}
635
caryclarkaec25102015-04-29 08:28:30 -0700636void SkPath::setPt(int index, SkScalar x, SkScalar y) {
637 SkDEBUGCODE(this->validate();)
638
639 int count = fPathRef->countPoints();
640 if (count <= index) {
641 return;
642 } else {
643 SkPathRef::Editor ed(&fPathRef);
644 ed.atPoint(index)->set(x, y);
645 }
646}
647
reed@android.com8a1c16f2008-12-17 15:59:43 +0000648void SkPath::setLastPt(SkScalar x, SkScalar y) {
649 SkDEBUGCODE(this->validate();)
650
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000651 int count = fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000652 if (count == 0) {
653 this->moveTo(x, y);
654 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000655 SkPathRef::Editor ed(&fPathRef);
656 ed.atPoint(count-1)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000657 }
658}
659
reed@google.com04863fa2011-05-15 04:08:24 +0000660void SkPath::setConvexity(Convexity c) {
661 if (fConvexity != c) {
662 fConvexity = c;
reed@google.com04863fa2011-05-15 04:08:24 +0000663 }
664}
665
reed@android.com8a1c16f2008-12-17 15:59:43 +0000666//////////////////////////////////////////////////////////////////////////////
667// Construction methods
668
reed026beb52015-06-10 14:23:15 -0700669#define DIRTY_AFTER_EDIT \
670 do { \
671 fConvexity = kUnknown_Convexity; \
672 fFirstDirection = SkPathPriv::kUnknown_FirstDirection; \
reed@google.comb54455e2011-05-16 14:16:04 +0000673 } while (0)
674
reed@android.com8a1c16f2008-12-17 15:59:43 +0000675void SkPath::incReserve(U16CPU inc) {
676 SkDEBUGCODE(this->validate();)
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000677 SkPathRef::Editor(&fPathRef, inc, inc);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000678 SkDEBUGCODE(this->validate();)
679}
680
681void SkPath::moveTo(SkScalar x, SkScalar y) {
682 SkDEBUGCODE(this->validate();)
683
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000684 SkPathRef::Editor ed(&fPathRef);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000685
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000686 // remember our index
687 fLastMoveToIndex = fPathRef->countPoints();
688
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000689 ed.growForVerb(kMove_Verb)->set(x, y);
bsalomonb17c1292014-08-28 14:04:55 -0700690
691 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000692}
693
694void SkPath::rMoveTo(SkScalar x, SkScalar y) {
695 SkPoint pt;
696 this->getLastPt(&pt);
697 this->moveTo(pt.fX + x, pt.fY + y);
698}
699
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000700void SkPath::injectMoveToIfNeeded() {
701 if (fLastMoveToIndex < 0) {
702 SkScalar x, y;
703 if (fPathRef->countVerbs() == 0) {
704 x = y = 0;
705 } else {
706 const SkPoint& pt = fPathRef->atPoint(~fLastMoveToIndex);
707 x = pt.fX;
708 y = pt.fY;
709 }
710 this->moveTo(x, y);
711 }
712}
713
reed@android.com8a1c16f2008-12-17 15:59:43 +0000714void SkPath::lineTo(SkScalar x, SkScalar y) {
715 SkDEBUGCODE(this->validate();)
716
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000717 this->injectMoveToIfNeeded();
718
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000719 SkPathRef::Editor ed(&fPathRef);
720 ed.growForVerb(kLine_Verb)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000721
reed@google.comb54455e2011-05-16 14:16:04 +0000722 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000723}
724
725void SkPath::rLineTo(SkScalar x, SkScalar y) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000726 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000727 SkPoint pt;
728 this->getLastPt(&pt);
729 this->lineTo(pt.fX + x, pt.fY + y);
730}
731
732void SkPath::quadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
733 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000734
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000735 this->injectMoveToIfNeeded();
736
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000737 SkPathRef::Editor ed(&fPathRef);
738 SkPoint* pts = ed.growForVerb(kQuad_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000739 pts[0].set(x1, y1);
740 pts[1].set(x2, y2);
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000741
reed@google.comb54455e2011-05-16 14:16:04 +0000742 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000743}
744
745void SkPath::rQuadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000746 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000747 SkPoint pt;
748 this->getLastPt(&pt);
749 this->quadTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2);
750}
751
reed@google.com277c3f82013-05-31 15:17:50 +0000752void SkPath::conicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
753 SkScalar w) {
754 // check for <= 0 or NaN with this test
755 if (!(w > 0)) {
756 this->lineTo(x2, y2);
757 } else if (!SkScalarIsFinite(w)) {
758 this->lineTo(x1, y1);
759 this->lineTo(x2, y2);
760 } else if (SK_Scalar1 == w) {
761 this->quadTo(x1, y1, x2, y2);
762 } else {
763 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000764
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000765 this->injectMoveToIfNeeded();
766
reed@google.com277c3f82013-05-31 15:17:50 +0000767 SkPathRef::Editor ed(&fPathRef);
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000768 SkPoint* pts = ed.growForVerb(kConic_Verb, w);
reed@google.com277c3f82013-05-31 15:17:50 +0000769 pts[0].set(x1, y1);
770 pts[1].set(x2, y2);
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000771
reed@google.com277c3f82013-05-31 15:17:50 +0000772 DIRTY_AFTER_EDIT;
773 }
774}
775
776void SkPath::rConicTo(SkScalar dx1, SkScalar dy1, SkScalar dx2, SkScalar dy2,
777 SkScalar w) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000778 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@google.com277c3f82013-05-31 15:17:50 +0000779 SkPoint pt;
780 this->getLastPt(&pt);
781 this->conicTo(pt.fX + dx1, pt.fY + dy1, pt.fX + dx2, pt.fY + dy2, w);
782}
783
reed@android.com8a1c16f2008-12-17 15:59:43 +0000784void SkPath::cubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
785 SkScalar x3, SkScalar y3) {
786 SkDEBUGCODE(this->validate();)
787
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000788 this->injectMoveToIfNeeded();
789
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000790 SkPathRef::Editor ed(&fPathRef);
791 SkPoint* pts = ed.growForVerb(kCubic_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000792 pts[0].set(x1, y1);
793 pts[1].set(x2, y2);
794 pts[2].set(x3, y3);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000795
reed@google.comb54455e2011-05-16 14:16:04 +0000796 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000797}
798
799void SkPath::rCubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
800 SkScalar x3, SkScalar y3) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000801 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000802 SkPoint pt;
803 this->getLastPt(&pt);
804 this->cubicTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2,
805 pt.fX + x3, pt.fY + y3);
806}
807
808void SkPath::close() {
809 SkDEBUGCODE(this->validate();)
810
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000811 int count = fPathRef->countVerbs();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000812 if (count > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000813 switch (fPathRef->atVerb(count - 1)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000814 case kLine_Verb:
815 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000816 case kConic_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +0000817 case kCubic_Verb:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000818 case kMove_Verb: {
819 SkPathRef::Editor ed(&fPathRef);
820 ed.growForVerb(kClose_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000821 break;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000822 }
reed@google.com277c3f82013-05-31 15:17:50 +0000823 case kClose_Verb:
reed@google.comfa2f2a42013-05-30 15:29:48 +0000824 // don't add a close if it's the first verb or a repeat
reed@google.com7950a9e2013-05-30 14:57:55 +0000825 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000826 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000827 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000828 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000829 }
830 }
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000831
832 // signal that we need a moveTo to follow us (unless we're done)
833#if 0
834 if (fLastMoveToIndex >= 0) {
835 fLastMoveToIndex = ~fLastMoveToIndex;
836 }
837#else
838 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
839#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000840}
841
842///////////////////////////////////////////////////////////////////////////////
reed@google.comabf15c12011-01-18 20:35:51 +0000843
fmalitac08d53e2015-11-17 09:53:29 -0800844namespace {
845
846template <unsigned N>
847class PointIterator {
848public:
849 PointIterator(SkPath::Direction dir, unsigned startIndex)
850 : fCurrent(startIndex % N)
851 , fAdvance(dir == SkPath::kCW_Direction ? 1 : N - 1) { }
852
853 const SkPoint& current() const {
854 SkASSERT(fCurrent < N);
855 return fPts[fCurrent];
856 }
857
858 const SkPoint& next() {
859 fCurrent = (fCurrent + fAdvance) % N;
860 return this->current();
861 }
862
863protected:
864 SkPoint fPts[N];
865
866private:
867 unsigned fCurrent;
868 unsigned fAdvance;
869};
870
871class RectPointIterator : public PointIterator<4> {
872public:
873 RectPointIterator(const SkRect& rect, SkPath::Direction dir, unsigned startIndex)
874 : PointIterator(dir, startIndex) {
875
876 fPts[0] = SkPoint::Make(rect.fLeft, rect.fTop);
877 fPts[1] = SkPoint::Make(rect.fRight, rect.fTop);
878 fPts[2] = SkPoint::Make(rect.fRight, rect.fBottom);
879 fPts[3] = SkPoint::Make(rect.fLeft, rect.fBottom);
880 }
881};
882
883class OvalPointIterator : public PointIterator<4> {
884public:
885 OvalPointIterator(const SkRect& oval, SkPath::Direction dir, unsigned startIndex)
886 : PointIterator(dir, startIndex) {
887
888 const SkScalar cx = oval.centerX();
889 const SkScalar cy = oval.centerY();
890
891 fPts[0] = SkPoint::Make(cx, oval.fTop);
892 fPts[1] = SkPoint::Make(oval.fRight, cy);
893 fPts[2] = SkPoint::Make(cx, oval.fBottom);
894 fPts[3] = SkPoint::Make(oval.fLeft, cy);
895 }
896};
897
898class RRectPointIterator : public PointIterator<8> {
899public:
900 RRectPointIterator(const SkRRect& rrect, SkPath::Direction dir, unsigned startIndex)
901 : PointIterator(dir, startIndex) {
902
903 const SkRect& bounds = rrect.getBounds();
904 const SkScalar L = bounds.fLeft;
905 const SkScalar T = bounds.fTop;
906 const SkScalar R = bounds.fRight;
907 const SkScalar B = bounds.fBottom;
908
909 fPts[0] = SkPoint::Make(L + rrect.radii(SkRRect::kUpperLeft_Corner).fX, T);
910 fPts[1] = SkPoint::Make(R - rrect.radii(SkRRect::kUpperRight_Corner).fX, T);
911 fPts[2] = SkPoint::Make(R, T + rrect.radii(SkRRect::kUpperRight_Corner).fY);
912 fPts[3] = SkPoint::Make(R, B - rrect.radii(SkRRect::kLowerRight_Corner).fY);
913 fPts[4] = SkPoint::Make(R - rrect.radii(SkRRect::kLowerRight_Corner).fX, B);
914 fPts[5] = SkPoint::Make(L + rrect.radii(SkRRect::kLowerLeft_Corner).fX, B);
915 fPts[6] = SkPoint::Make(L, B - rrect.radii(SkRRect::kLowerLeft_Corner).fY);
916 fPts[7] = SkPoint::Make(L, T + rrect.radii(SkRRect::kUpperLeft_Corner).fY);
917 }
918};
919
920} // anonymous namespace
921
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000922static void assert_known_direction(int dir) {
923 SkASSERT(SkPath::kCW_Direction == dir || SkPath::kCCW_Direction == dir);
924}
925
reed@android.com8a1c16f2008-12-17 15:59:43 +0000926void SkPath::addRect(const SkRect& rect, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -0800927 this->addRect(rect, dir, 0);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000928}
929
930void SkPath::addRect(SkScalar left, SkScalar top, SkScalar right,
931 SkScalar bottom, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -0800932 this->addRect(SkRect::MakeLTRB(left, top, right, bottom), dir, 0);
933}
934
935void SkPath::addRect(const SkRect &rect, Direction dir, unsigned startIndex) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000936 assert_known_direction(dir);
reed026beb52015-06-10 14:23:15 -0700937 fFirstDirection = this->hasOnlyMoveTos() ?
fmalitac08d53e2015-11-17 09:53:29 -0800938 (SkPathPriv::FirstDirection)dir : SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000939 SkAutoDisableDirectionCheck addc(this);
fmalitac08d53e2015-11-17 09:53:29 -0800940 SkAutoPathBoundsUpdate apbu(this, rect);
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000941
fmalitac08d53e2015-11-17 09:53:29 -0800942 SkDEBUGCODE(int initialVerbCount = this->countVerbs());
reed@android.com8a1c16f2008-12-17 15:59:43 +0000943
fmalitac08d53e2015-11-17 09:53:29 -0800944 const int kVerbs = 5; // moveTo + 3x lineTo + close
945 this->incReserve(kVerbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000946
fmalitac08d53e2015-11-17 09:53:29 -0800947 RectPointIterator iter(rect, dir, startIndex);
948
949 this->moveTo(iter.current());
950 this->lineTo(iter.next());
951 this->lineTo(iter.next());
952 this->lineTo(iter.next());
reed@android.com8a1c16f2008-12-17 15:59:43 +0000953 this->close();
fmalitac08d53e2015-11-17 09:53:29 -0800954
955 SkASSERT(this->countVerbs() == initialVerbCount + kVerbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000956}
957
reed@google.com744faba2012-05-29 19:54:52 +0000958void SkPath::addPoly(const SkPoint pts[], int count, bool close) {
959 SkDEBUGCODE(this->validate();)
960 if (count <= 0) {
961 return;
962 }
963
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000964 fLastMoveToIndex = fPathRef->countPoints();
965
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000966 // +close makes room for the extra kClose_Verb
967 SkPathRef::Editor ed(&fPathRef, count+close, count);
968
969 ed.growForVerb(kMove_Verb)->set(pts[0].fX, pts[0].fY);
reed@google.com744faba2012-05-29 19:54:52 +0000970 if (count > 1) {
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000971 SkPoint* p = ed.growForRepeatedVerb(kLine_Verb, count - 1);
972 memcpy(p, &pts[1], (count-1) * sizeof(SkPoint));
reed@google.com744faba2012-05-29 19:54:52 +0000973 }
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000974
reed@google.com744faba2012-05-29 19:54:52 +0000975 if (close) {
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000976 ed.growForVerb(kClose_Verb);
caryclark63c684a2015-02-25 09:04:04 -0800977 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
reed@google.com744faba2012-05-29 19:54:52 +0000978 }
979
reed@google.com744faba2012-05-29 19:54:52 +0000980 DIRTY_AFTER_EDIT;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000981 SkDEBUGCODE(this->validate();)
reed@google.com744faba2012-05-29 19:54:52 +0000982}
983
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000984#include "SkGeometry.h"
985
reedf90ea012015-01-29 12:03:58 -0800986static bool arc_is_lone_point(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
987 SkPoint* pt) {
988 if (0 == sweepAngle && (0 == startAngle || SkIntToScalar(360) == startAngle)) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000989 // Chrome uses this path to move into and out of ovals. If not
990 // treated as a special case the moves can distort the oval's
991 // bounding box (and break the circle special case).
reedf90ea012015-01-29 12:03:58 -0800992 pt->set(oval.fRight, oval.centerY());
993 return true;
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000994 } else if (0 == oval.width() && 0 == oval.height()) {
995 // Chrome will sometimes create 0 radius round rects. Having degenerate
996 // quad segments in the path prevents the path from being recognized as
997 // a rect.
998 // TODO: optimizing the case where only one of width or height is zero
999 // should also be considered. This case, however, doesn't seem to be
1000 // as common as the single point case.
reedf90ea012015-01-29 12:03:58 -08001001 pt->set(oval.fRight, oval.fTop);
1002 return true;
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001003 }
reedf90ea012015-01-29 12:03:58 -08001004 return false;
1005}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001006
reedd5d27d92015-02-09 13:54:43 -08001007// Return the unit vectors pointing at the start/stop points for the given start/sweep angles
1008//
1009static void angles_to_unit_vectors(SkScalar startAngle, SkScalar sweepAngle,
1010 SkVector* startV, SkVector* stopV, SkRotationDirection* dir) {
1011 startV->fY = SkScalarSinCos(SkDegreesToRadians(startAngle), &startV->fX);
1012 stopV->fY = SkScalarSinCos(SkDegreesToRadians(startAngle + sweepAngle), &stopV->fX);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001013
1014 /* If the sweep angle is nearly (but less than) 360, then due to precision
reedd5d27d92015-02-09 13:54:43 -08001015 loss in radians-conversion and/or sin/cos, we may end up with coincident
1016 vectors, which will fool SkBuildQuadArc into doing nothing (bad) instead
1017 of drawing a nearly complete circle (good).
1018 e.g. canvas.drawArc(0, 359.99, ...)
1019 -vs- canvas.drawArc(0, 359.9, ...)
1020 We try to detect this edge case, and tweak the stop vector
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001021 */
reedd5d27d92015-02-09 13:54:43 -08001022 if (*startV == *stopV) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001023 SkScalar sw = SkScalarAbs(sweepAngle);
1024 if (sw < SkIntToScalar(360) && sw > SkIntToScalar(359)) {
1025 SkScalar stopRad = SkDegreesToRadians(startAngle + sweepAngle);
1026 // make a guess at a tiny angle (in radians) to tweak by
1027 SkScalar deltaRad = SkScalarCopySign(SK_Scalar1/512, sweepAngle);
1028 // not sure how much will be enough, so we use a loop
1029 do {
1030 stopRad -= deltaRad;
reedd5d27d92015-02-09 13:54:43 -08001031 stopV->fY = SkScalarSinCos(stopRad, &stopV->fX);
1032 } while (*startV == *stopV);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001033 }
1034 }
reedd5d27d92015-02-09 13:54:43 -08001035 *dir = sweepAngle > 0 ? kCW_SkRotationDirection : kCCW_SkRotationDirection;
1036}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001037
reed9e779d42015-02-17 11:43:14 -08001038/**
1039 * If this returns 0, then the caller should just line-to the singlePt, else it should
1040 * ignore singlePt and append the specified number of conics.
1041 */
reedd5d27d92015-02-09 13:54:43 -08001042static int build_arc_conics(const SkRect& oval, const SkVector& start, const SkVector& stop,
reed9e779d42015-02-17 11:43:14 -08001043 SkRotationDirection dir, SkConic conics[SkConic::kMaxConicsForArc],
1044 SkPoint* singlePt) {
reedd5d27d92015-02-09 13:54:43 -08001045 SkMatrix matrix;
1046
1047 matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height()));
1048 matrix.postTranslate(oval.centerX(), oval.centerY());
1049
reed9e779d42015-02-17 11:43:14 -08001050 int count = SkConic::BuildUnitArc(start, stop, dir, &matrix, conics);
1051 if (0 == count) {
1052 matrix.mapXY(start.x(), start.y(), singlePt);
1053 }
1054 return count;
reedd5d27d92015-02-09 13:54:43 -08001055}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001056
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001057void SkPath::addRoundRect(const SkRect& rect, const SkScalar radii[],
reed@android.com8a1c16f2008-12-17 15:59:43 +00001058 Direction dir) {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001059 SkRRect rrect;
1060 rrect.setRectRadii(rect, (const SkVector*) radii);
1061 this->addRRect(rrect, dir);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001062}
1063
reed@google.com4ed0fb72012-12-12 20:48:18 +00001064void SkPath::addRRect(const SkRRect& rrect, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -08001065 // legacy start indices: 6 (CW) and 7(CCW)
1066 this->addRRect(rrect, dir, dir == kCW_Direction ? 6 : 7);
1067}
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001068
fmalitac08d53e2015-11-17 09:53:29 -08001069void SkPath::addRRect(const SkRRect &rrect, Direction dir, unsigned startIndex) {
1070 assert_known_direction(dir);
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001071
fmalitac08d53e2015-11-17 09:53:29 -08001072 if (rrect.isEmpty()) {
1073 return;
reed1b28a3a2014-12-17 14:42:09 -08001074 }
fmalitac08d53e2015-11-17 09:53:29 -08001075
caryclarkda707bf2015-11-19 14:47:43 -08001076 bool isRRect = hasOnlyMoveTos();
fmalitac08d53e2015-11-17 09:53:29 -08001077 const SkRect& bounds = rrect.getBounds();
1078
1079 if (rrect.isRect()) {
1080 // degenerate(rect) => radii points are collapsing
1081 this->addRect(bounds, dir, (startIndex + 1) / 2);
1082 } else if (rrect.isOval()) {
1083 // degenerate(oval) => line points are collapsing
1084 this->addOval(bounds, dir, startIndex / 2);
1085 } else {
1086 fFirstDirection = this->hasOnlyMoveTos() ?
1087 (SkPathPriv::FirstDirection)dir : SkPathPriv::kUnknown_FirstDirection;
1088
1089 SkAutoPathBoundsUpdate apbu(this, bounds);
1090 SkAutoDisableDirectionCheck addc(this);
1091
1092 // we start with a conic on odd indices when moving CW vs. even indices when moving CCW
1093 const bool startsWithConic = ((startIndex & 1) == (dir == kCW_Direction));
1094 const SkScalar weight = SK_ScalarRoot2Over2;
1095
1096 SkDEBUGCODE(int initialVerbCount = this->countVerbs());
1097 const int kVerbs = startsWithConic
1098 ? 9 // moveTo + 4x conicTo + 3x lineTo + close
1099 : 10; // moveTo + 4x lineTo + 4x conicTo + close
1100 this->incReserve(kVerbs);
1101
1102 RRectPointIterator rrectIter(rrect, dir, startIndex);
1103 // Corner iterator indices follow the collapsed radii model,
1104 // adjusted such that the start pt is "behind" the radii start pt.
1105 const unsigned rectStartIndex = startIndex / 2 + (dir == kCW_Direction ? 0 : 1);
1106 RectPointIterator rectIter(bounds, dir, rectStartIndex);
1107
1108 this->moveTo(rrectIter.current());
1109 if (startsWithConic) {
1110 for (unsigned i = 0; i < 3; ++i) {
1111 this->conicTo(rectIter.next(), rrectIter.next(), weight);
1112 this->lineTo(rrectIter.next());
1113 }
1114 this->conicTo(rectIter.next(), rrectIter.next(), weight);
1115 // final lineTo handled by close().
1116 } else {
1117 for (unsigned i = 0; i < 4; ++i) {
1118 this->lineTo(rrectIter.next());
1119 this->conicTo(rectIter.next(), rrectIter.next(), weight);
1120 }
1121 }
1122 this->close();
1123
caryclarkda707bf2015-11-19 14:47:43 -08001124 SkPathRef::Editor ed(&fPathRef);
1125 ed.setIsRRect(isRRect);
1126
fmalitac08d53e2015-11-17 09:53:29 -08001127 SkASSERT(this->countVerbs() == initialVerbCount + kVerbs);
1128 }
1129
1130 SkDEBUGCODE(fPathRef->validate();)
reed@google.com4ed0fb72012-12-12 20:48:18 +00001131}
1132
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001133bool SkPath::hasOnlyMoveTos() const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001134 int count = fPathRef->countVerbs();
1135 const uint8_t* verbs = const_cast<const SkPathRef*>(fPathRef.get())->verbsMemBegin();
1136 for (int i = 0; i < count; ++i) {
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001137 if (*verbs == kLine_Verb ||
1138 *verbs == kQuad_Verb ||
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001139 *verbs == kConic_Verb ||
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001140 *verbs == kCubic_Verb) {
1141 return false;
1142 }
1143 ++verbs;
1144 }
1145 return true;
1146}
1147
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001148void SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
1149 Direction dir) {
1150 assert_known_direction(dir);
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001151
humper@google.com75e3ca12013-04-08 21:44:11 +00001152 if (rx < 0 || ry < 0) {
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001153 SkErrorInternals::SetError( kInvalidArgument_SkError,
humper@google.com75e3ca12013-04-08 21:44:11 +00001154 "I got %f and %f as radii to SkPath::AddRoundRect, "
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001155 "but negative radii are not allowed.",
humper@google.com75e3ca12013-04-08 21:44:11 +00001156 SkScalarToDouble(rx), SkScalarToDouble(ry) );
1157 return;
1158 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001159
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001160 SkRRect rrect;
1161 rrect.setRectXY(rect, rx, ry);
1162 this->addRRect(rrect, dir);
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001163}
1164
reed@android.com8a1c16f2008-12-17 15:59:43 +00001165void SkPath::addOval(const SkRect& oval, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -08001166 // legacy start index: 1
1167 this->addOval(oval, dir, 1);
1168}
1169
1170void SkPath::addOval(const SkRect &oval, Direction dir, unsigned startPointIndex) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +00001171 assert_known_direction(dir);
1172
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001173 /* If addOval() is called after previous moveTo(),
1174 this path is still marked as an oval. This is used to
1175 fit into WebKit's calling sequences.
1176 We can't simply check isEmpty() in this case, as additional
1177 moveTo() would mark the path non empty.
1178 */
robertphillips@google.com466310d2013-12-03 16:43:54 +00001179 bool isOval = hasOnlyMoveTos();
1180 if (isOval) {
reed026beb52015-06-10 14:23:15 -07001181 fFirstDirection = (SkPathPriv::FirstDirection)dir;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001182 } else {
reed026beb52015-06-10 14:23:15 -07001183 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001184 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001185
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001186 SkAutoDisableDirectionCheck addc(this);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001187 SkAutoPathBoundsUpdate apbu(this, oval);
1188
fmalitac08d53e2015-11-17 09:53:29 -08001189 SkDEBUGCODE(int initialVerbCount = this->countVerbs());
1190 const int kVerbs = 6; // moveTo + 4x conicTo + close
1191 this->incReserve(kVerbs);
1192
1193 OvalPointIterator ovalIter(oval, dir, startPointIndex);
1194 // The corner iterator pts are tracking "behind" the oval/radii pts.
1195 RectPointIterator rectIter(oval, dir, startPointIndex + (dir == kCW_Direction ? 0 : 1));
reed220f9262014-12-17 08:21:04 -08001196 const SkScalar weight = SK_ScalarRoot2Over2;
1197
fmalitac08d53e2015-11-17 09:53:29 -08001198 this->moveTo(ovalIter.current());
1199 for (unsigned i = 0; i < 4; ++i) {
1200 this->conicTo(rectIter.next(), ovalIter.next(), weight);
reed220f9262014-12-17 08:21:04 -08001201 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001202 this->close();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001203
fmalitac08d53e2015-11-17 09:53:29 -08001204 SkASSERT(this->countVerbs() == initialVerbCount + kVerbs);
1205
robertphillips@google.com466310d2013-12-03 16:43:54 +00001206 SkPathRef::Editor ed(&fPathRef);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001207
robertphillips@google.com466310d2013-12-03 16:43:54 +00001208 ed.setIsOval(isOval);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001209}
1210
reed@android.com8a1c16f2008-12-17 15:59:43 +00001211void SkPath::addCircle(SkScalar x, SkScalar y, SkScalar r, Direction dir) {
1212 if (r > 0) {
fmalitac08d53e2015-11-17 09:53:29 -08001213 this->addOval(SkRect::MakeLTRB(x - r, y - r, x + r, y + r), dir);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001214 }
1215}
1216
reed@android.com8a1c16f2008-12-17 15:59:43 +00001217void SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
1218 bool forceMoveTo) {
1219 if (oval.width() < 0 || oval.height() < 0) {
1220 return;
1221 }
1222
reedf90ea012015-01-29 12:03:58 -08001223 if (fPathRef->countVerbs() == 0) {
1224 forceMoveTo = true;
1225 }
1226
1227 SkPoint lonePt;
1228 if (arc_is_lone_point(oval, startAngle, sweepAngle, &lonePt)) {
1229 forceMoveTo ? this->moveTo(lonePt) : this->lineTo(lonePt);
1230 return;
1231 }
1232
reedd5d27d92015-02-09 13:54:43 -08001233 SkVector startV, stopV;
1234 SkRotationDirection dir;
1235 angles_to_unit_vectors(startAngle, sweepAngle, &startV, &stopV, &dir);
1236
reed9e779d42015-02-17 11:43:14 -08001237 SkPoint singlePt;
reedd5d27d92015-02-09 13:54:43 -08001238 SkConic conics[SkConic::kMaxConicsForArc];
reed9e779d42015-02-17 11:43:14 -08001239 int count = build_arc_conics(oval, startV, stopV, dir, conics, &singlePt);
reedd5d27d92015-02-09 13:54:43 -08001240 if (count) {
1241 this->incReserve(count * 2 + 1);
1242 const SkPoint& pt = conics[0].fPts[0];
1243 forceMoveTo ? this->moveTo(pt) : this->lineTo(pt);
1244 for (int i = 0; i < count; ++i) {
1245 this->conicTo(conics[i].fPts[1], conics[i].fPts[2], conics[i].fW);
1246 }
reed9e779d42015-02-17 11:43:14 -08001247 } else {
1248 forceMoveTo ? this->moveTo(singlePt) : this->lineTo(singlePt);
reedd5d27d92015-02-09 13:54:43 -08001249 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001250}
1251
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001252void SkPath::addArc(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001253 if (oval.isEmpty() || 0 == sweepAngle) {
1254 return;
1255 }
1256
1257 const SkScalar kFullCircleAngle = SkIntToScalar(360);
1258
1259 if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
1260 this->addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction);
reedc7789042015-01-29 12:59:11 -08001261 } else {
1262 this->arcTo(oval, startAngle, sweepAngle, true);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001263 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001264}
1265
1266/*
1267 Need to handle the case when the angle is sharp, and our computed end-points
1268 for the arc go behind pt1 and/or p2...
1269*/
reedc7789042015-01-29 12:59:11 -08001270void SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2, SkScalar radius) {
reeda8b326c2014-12-09 11:50:32 -08001271 if (radius == 0) {
1272 this->lineTo(x1, y1);
1273 return;
1274 }
1275
1276 SkVector before, after;
reed@google.comabf15c12011-01-18 20:35:51 +00001277
reed@android.com8a1c16f2008-12-17 15:59:43 +00001278 // need to know our prev pt so we can construct tangent vectors
1279 {
1280 SkPoint start;
1281 this->getLastPt(&start);
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001282 // Handle degenerate cases by adding a line to the first point and
1283 // bailing out.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001284 before.setNormalize(x1 - start.fX, y1 - start.fY);
1285 after.setNormalize(x2 - x1, y2 - y1);
1286 }
reed@google.comabf15c12011-01-18 20:35:51 +00001287
reed@android.com8a1c16f2008-12-17 15:59:43 +00001288 SkScalar cosh = SkPoint::DotProduct(before, after);
1289 SkScalar sinh = SkPoint::CrossProduct(before, after);
1290
1291 if (SkScalarNearlyZero(sinh)) { // angle is too tight
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001292 this->lineTo(x1, y1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001293 return;
1294 }
reed@google.comabf15c12011-01-18 20:35:51 +00001295
reed@android.com8a1c16f2008-12-17 15:59:43 +00001296 SkScalar dist = SkScalarMulDiv(radius, SK_Scalar1 - cosh, sinh);
1297 if (dist < 0) {
1298 dist = -dist;
1299 }
1300
1301 SkScalar xx = x1 - SkScalarMul(dist, before.fX);
1302 SkScalar yy = y1 - SkScalarMul(dist, before.fY);
1303 SkRotationDirection arcDir;
1304
1305 // now turn before/after into normals
1306 if (sinh > 0) {
1307 before.rotateCCW();
1308 after.rotateCCW();
1309 arcDir = kCW_SkRotationDirection;
1310 } else {
1311 before.rotateCW();
1312 after.rotateCW();
1313 arcDir = kCCW_SkRotationDirection;
1314 }
1315
1316 SkMatrix matrix;
1317 SkPoint pts[kSkBuildQuadArcStorage];
reed@google.comabf15c12011-01-18 20:35:51 +00001318
reed@android.com8a1c16f2008-12-17 15:59:43 +00001319 matrix.setScale(radius, radius);
1320 matrix.postTranslate(xx - SkScalarMul(radius, before.fX),
1321 yy - SkScalarMul(radius, before.fY));
reed@google.comabf15c12011-01-18 20:35:51 +00001322
reed@android.com8a1c16f2008-12-17 15:59:43 +00001323 int count = SkBuildQuadArc(before, after, arcDir, &matrix, pts);
reed@google.comabf15c12011-01-18 20:35:51 +00001324
reed@android.com8a1c16f2008-12-17 15:59:43 +00001325 this->incReserve(count);
1326 // [xx,yy] == pts[0]
1327 this->lineTo(xx, yy);
1328 for (int i = 1; i < count; i += 2) {
1329 this->quadTo(pts[i], pts[i+1]);
1330 }
1331}
1332
1333///////////////////////////////////////////////////////////////////////////////
1334
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001335void SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy, AddPathMode mode) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001336 SkMatrix matrix;
1337
1338 matrix.setTranslate(dx, dy);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001339 this->addPath(path, matrix, mode);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001340}
1341
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001342void SkPath::addPath(const SkPath& path, const SkMatrix& matrix, AddPathMode mode) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001343 SkPathRef::Editor(&fPathRef, path.countVerbs(), path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001344
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001345 RawIter iter(path);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001346 SkPoint pts[4];
1347 Verb verb;
1348
1349 SkMatrix::MapPtsProc proc = matrix.getMapPtsProc();
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001350 bool firstVerb = true;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001351 while ((verb = iter.next(pts)) != kDone_Verb) {
1352 switch (verb) {
1353 case kMove_Verb:
1354 proc(matrix, &pts[0], &pts[0], 1);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001355 if (firstVerb && mode == kExtend_AddPathMode && !isEmpty()) {
1356 injectMoveToIfNeeded(); // In case last contour is closed
1357 this->lineTo(pts[0]);
1358 } else {
1359 this->moveTo(pts[0]);
1360 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001361 break;
1362 case kLine_Verb:
1363 proc(matrix, &pts[1], &pts[1], 1);
1364 this->lineTo(pts[1]);
1365 break;
1366 case kQuad_Verb:
1367 proc(matrix, &pts[1], &pts[1], 2);
1368 this->quadTo(pts[1], pts[2]);
1369 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001370 case kConic_Verb:
1371 proc(matrix, &pts[1], &pts[1], 2);
1372 this->conicTo(pts[1], pts[2], iter.conicWeight());
1373 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001374 case kCubic_Verb:
1375 proc(matrix, &pts[1], &pts[1], 3);
1376 this->cubicTo(pts[1], pts[2], pts[3]);
1377 break;
1378 case kClose_Verb:
1379 this->close();
1380 break;
1381 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001382 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001383 }
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001384 firstVerb = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001385 }
1386}
1387
1388///////////////////////////////////////////////////////////////////////////////
1389
reed@google.com277c3f82013-05-31 15:17:50 +00001390static int pts_in_verb(unsigned verb) {
1391 static const uint8_t gPtsInVerb[] = {
1392 1, // kMove
1393 1, // kLine
1394 2, // kQuad
1395 2, // kConic
1396 3, // kCubic
1397 0, // kClose
1398 0 // kDone
1399 };
1400
1401 SkASSERT(verb < SK_ARRAY_COUNT(gPtsInVerb));
1402 return gPtsInVerb[verb];
1403}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001404
reed@android.com8a1c16f2008-12-17 15:59:43 +00001405// ignore the last point of the 1st contour
1406void SkPath::reversePathTo(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001407 int i, vcount = path.fPathRef->countVerbs();
1408 // exit early if the path is empty, or just has a moveTo.
1409 if (vcount < 2) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001410 return;
1411 }
1412
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001413 SkPathRef::Editor(&fPathRef, vcount, path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001414
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001415 const uint8_t* verbs = path.fPathRef->verbs();
1416 const SkPoint* pts = path.fPathRef->points();
reed@google.com277c3f82013-05-31 15:17:50 +00001417 const SkScalar* conicWeights = path.fPathRef->conicWeights();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001418
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001419 SkASSERT(verbs[~0] == kMove_Verb);
1420 for (i = 1; i < vcount; ++i) {
reed@google.com277c3f82013-05-31 15:17:50 +00001421 unsigned v = verbs[~i];
1422 int n = pts_in_verb(v);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001423 if (n == 0) {
1424 break;
1425 }
1426 pts += n;
reed@google.com277c3f82013-05-31 15:17:50 +00001427 conicWeights += (SkPath::kConic_Verb == v);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001428 }
1429
1430 while (--i > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001431 switch (verbs[~i]) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001432 case kLine_Verb:
1433 this->lineTo(pts[-1].fX, pts[-1].fY);
1434 break;
1435 case kQuad_Verb:
1436 this->quadTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY);
1437 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001438 case kConic_Verb:
1439 this->conicTo(pts[-1], pts[-2], *--conicWeights);
1440 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001441 case kCubic_Verb:
1442 this->cubicTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY,
1443 pts[-3].fX, pts[-3].fY);
1444 break;
1445 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001446 SkDEBUGFAIL("bad verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001447 break;
1448 }
reed@google.com277c3f82013-05-31 15:17:50 +00001449 pts -= pts_in_verb(verbs[~i]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001450 }
1451}
1452
reed@google.com63d73742012-01-10 15:33:12 +00001453void SkPath::reverseAddPath(const SkPath& src) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001454 SkPathRef::Editor ed(&fPathRef, src.fPathRef->countPoints(), src.fPathRef->countVerbs());
reed@google.com63d73742012-01-10 15:33:12 +00001455
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001456 const SkPoint* pts = src.fPathRef->pointsEnd();
1457 // we will iterator through src's verbs backwards
1458 const uint8_t* verbs = src.fPathRef->verbsMemBegin(); // points at the last verb
1459 const uint8_t* verbsEnd = src.fPathRef->verbs(); // points just past the first verb
reed@google.com277c3f82013-05-31 15:17:50 +00001460 const SkScalar* conicWeights = src.fPathRef->conicWeightsEnd();
reed@google.com63d73742012-01-10 15:33:12 +00001461
1462 bool needMove = true;
1463 bool needClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001464 while (verbs < verbsEnd) {
1465 uint8_t v = *(verbs++);
reed@google.com277c3f82013-05-31 15:17:50 +00001466 int n = pts_in_verb(v);
reed@google.com63d73742012-01-10 15:33:12 +00001467
1468 if (needMove) {
1469 --pts;
1470 this->moveTo(pts->fX, pts->fY);
1471 needMove = false;
1472 }
1473 pts -= n;
1474 switch (v) {
1475 case kMove_Verb:
1476 if (needClose) {
1477 this->close();
1478 needClose = false;
1479 }
1480 needMove = true;
1481 pts += 1; // so we see the point in "if (needMove)" above
1482 break;
1483 case kLine_Verb:
1484 this->lineTo(pts[0]);
1485 break;
1486 case kQuad_Verb:
1487 this->quadTo(pts[1], pts[0]);
1488 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001489 case kConic_Verb:
1490 this->conicTo(pts[1], pts[0], *--conicWeights);
1491 break;
reed@google.com63d73742012-01-10 15:33:12 +00001492 case kCubic_Verb:
1493 this->cubicTo(pts[2], pts[1], pts[0]);
1494 break;
1495 case kClose_Verb:
1496 needClose = true;
1497 break;
1498 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00001499 SkDEBUGFAIL("unexpected verb");
reed@google.com63d73742012-01-10 15:33:12 +00001500 }
1501 }
1502}
1503
reed@android.com8a1c16f2008-12-17 15:59:43 +00001504///////////////////////////////////////////////////////////////////////////////
1505
1506void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
1507 SkMatrix matrix;
1508
1509 matrix.setTranslate(dx, dy);
1510 this->transform(matrix, dst);
1511}
1512
reed@android.com8a1c16f2008-12-17 15:59:43 +00001513static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
1514 int level = 2) {
1515 if (--level >= 0) {
1516 SkPoint tmp[7];
1517
1518 SkChopCubicAtHalf(pts, tmp);
1519 subdivide_cubic_to(path, &tmp[0], level);
1520 subdivide_cubic_to(path, &tmp[3], level);
1521 } else {
1522 path->cubicTo(pts[1], pts[2], pts[3]);
1523 }
1524}
1525
1526void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
1527 SkDEBUGCODE(this->validate();)
halcanary96fcdcc2015-08-27 07:41:13 -07001528 if (dst == nullptr) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001529 dst = (SkPath*)this;
1530 }
1531
tomhudson@google.com8d430182011-06-06 19:11:19 +00001532 if (matrix.hasPerspective()) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001533 SkPath tmp;
1534 tmp.fFillType = fFillType;
1535
1536 SkPath::Iter iter(*this, false);
1537 SkPoint pts[4];
1538 SkPath::Verb verb;
1539
reed@google.com4a3b7142012-05-16 17:16:46 +00001540 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001541 switch (verb) {
1542 case kMove_Verb:
1543 tmp.moveTo(pts[0]);
1544 break;
1545 case kLine_Verb:
1546 tmp.lineTo(pts[1]);
1547 break;
1548 case kQuad_Verb:
reed220f9262014-12-17 08:21:04 -08001549 // promote the quad to a conic
1550 tmp.conicTo(pts[1], pts[2],
1551 SkConic::TransformW(pts, SK_Scalar1, matrix));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001552 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001553 case kConic_Verb:
reed220f9262014-12-17 08:21:04 -08001554 tmp.conicTo(pts[1], pts[2],
1555 SkConic::TransformW(pts, iter.conicWeight(), matrix));
reed@google.com277c3f82013-05-31 15:17:50 +00001556 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001557 case kCubic_Verb:
1558 subdivide_cubic_to(&tmp, pts);
1559 break;
1560 case kClose_Verb:
1561 tmp.close();
1562 break;
1563 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001564 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001565 break;
1566 }
1567 }
1568
1569 dst->swap(tmp);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001570 SkPathRef::Editor ed(&dst->fPathRef);
1571 matrix.mapPoints(ed.points(), ed.pathRef()->countPoints());
reed026beb52015-06-10 14:23:15 -07001572 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001573 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001574 SkPathRef::CreateTransformedCopy(&dst->fPathRef, *fPathRef.get(), matrix);
1575
reed@android.com8a1c16f2008-12-17 15:59:43 +00001576 if (this != dst) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001577 dst->fFillType = fFillType;
reed@google.com2a6f8ab2011-10-25 18:41:23 +00001578 dst->fConvexity = fConvexity;
jvanverthb3eb6872014-10-24 07:12:51 -07001579 dst->fIsVolatile = fIsVolatile;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001580 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001581
reed026beb52015-06-10 14:23:15 -07001582 if (SkPathPriv::kUnknown_FirstDirection == fFirstDirection) {
1583 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001584 } else {
1585 SkScalar det2x2 =
1586 SkScalarMul(matrix.get(SkMatrix::kMScaleX), matrix.get(SkMatrix::kMScaleY)) -
1587 SkScalarMul(matrix.get(SkMatrix::kMSkewX), matrix.get(SkMatrix::kMSkewY));
1588 if (det2x2 < 0) {
herb9f4dbca2015-09-28 11:05:47 -07001589 dst->fFirstDirection = SkPathPriv::OppositeFirstDirection(
1590 (SkPathPriv::FirstDirection)fFirstDirection.load());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001591 } else if (det2x2 > 0) {
herb9f4dbca2015-09-28 11:05:47 -07001592 dst->fFirstDirection = fFirstDirection.load();
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001593 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001594 dst->fConvexity = kUnknown_Convexity;
reed026beb52015-06-10 14:23:15 -07001595 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001596 }
1597 }
1598
reed@android.com8a1c16f2008-12-17 15:59:43 +00001599 SkDEBUGCODE(dst->validate();)
1600 }
1601}
1602
reed@android.com8a1c16f2008-12-17 15:59:43 +00001603///////////////////////////////////////////////////////////////////////////////
1604///////////////////////////////////////////////////////////////////////////////
1605
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001606enum SegmentState {
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001607 kEmptyContour_SegmentState, // The current contour is empty. We may be
1608 // starting processing or we may have just
1609 // closed a contour.
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001610 kAfterMove_SegmentState, // We have seen a move, but nothing else.
1611 kAfterPrimitive_SegmentState // We have seen a primitive but not yet
1612 // closed the path. Also the initial state.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001613};
1614
1615SkPath::Iter::Iter() {
1616#ifdef SK_DEBUG
halcanary96fcdcc2015-08-27 07:41:13 -07001617 fPts = nullptr;
1618 fConicWeights = nullptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001619 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001620 fForceClose = fCloseLine = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001621 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001622#endif
1623 // need to init enough to make next() harmlessly return kDone_Verb
halcanary96fcdcc2015-08-27 07:41:13 -07001624 fVerbs = nullptr;
1625 fVerbStop = nullptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001626 fNeedClose = false;
1627}
1628
1629SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
1630 this->setPath(path, forceClose);
1631}
1632
1633void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001634 fPts = path.fPathRef->points();
1635 fVerbs = path.fPathRef->verbs();
1636 fVerbStop = path.fPathRef->verbsMemBegin();
reed@google.com277c3f82013-05-31 15:17:50 +00001637 fConicWeights = path.fPathRef->conicWeights() - 1; // begin one behind
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001638 fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org72785c42011-12-29 21:03:28 +00001639 fMoveTo.fX = fMoveTo.fY = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001640 fForceClose = SkToU8(forceClose);
1641 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001642 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001643}
1644
1645bool SkPath::Iter::isClosedContour() const {
halcanary96fcdcc2015-08-27 07:41:13 -07001646 if (fVerbs == nullptr || fVerbs == fVerbStop) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001647 return false;
1648 }
1649 if (fForceClose) {
1650 return true;
1651 }
1652
1653 const uint8_t* verbs = fVerbs;
1654 const uint8_t* stop = fVerbStop;
reed@google.comabf15c12011-01-18 20:35:51 +00001655
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001656 if (kMove_Verb == *(verbs - 1)) {
1657 verbs -= 1; // skip the initial moveto
reed@android.com8a1c16f2008-12-17 15:59:43 +00001658 }
1659
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001660 while (verbs > stop) {
1661 // verbs points one beyond the current verb, decrement first.
1662 unsigned v = *(--verbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001663 if (kMove_Verb == v) {
1664 break;
1665 }
1666 if (kClose_Verb == v) {
1667 return true;
1668 }
1669 }
1670 return false;
1671}
1672
1673SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001674 SkASSERT(pts);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001675 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001676 // A special case: if both points are NaN, SkPoint::operation== returns
1677 // false, but the iterator expects that they are treated as the same.
1678 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001679 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1680 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001681 return kClose_Verb;
1682 }
1683
reed@google.com9e25dbf2012-05-15 17:05:38 +00001684 pts[0] = fLastPt;
1685 pts[1] = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001686 fLastPt = fMoveTo;
1687 fCloseLine = true;
1688 return kLine_Verb;
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +00001689 } else {
1690 pts[0] = fMoveTo;
1691 return kClose_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001692 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001693}
1694
reed@google.com9e25dbf2012-05-15 17:05:38 +00001695const SkPoint& SkPath::Iter::cons_moveTo() {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001696 if (fSegmentState == kAfterMove_SegmentState) {
1697 // Set the first return pt to the move pt
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001698 fSegmentState = kAfterPrimitive_SegmentState;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001699 return fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001700 } else {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001701 SkASSERT(fSegmentState == kAfterPrimitive_SegmentState);
1702 // Set the first return pt to the last pt of the previous primitive.
reed@google.com9e25dbf2012-05-15 17:05:38 +00001703 return fPts[-1];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001704 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001705}
1706
caryclarke8c56662015-07-14 11:19:26 -07001707void SkPath::Iter::consumeDegenerateSegments(bool exact) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001708 // We need to step over anything that will not move the current draw point
1709 // forward before the next move is seen
1710 const uint8_t* lastMoveVerb = 0;
1711 const SkPoint* lastMovePt = 0;
halcanary96fcdcc2015-08-27 07:41:13 -07001712 const SkScalar* lastMoveWeight = nullptr;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001713 SkPoint lastPt = fLastPt;
1714 while (fVerbs != fVerbStop) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001715 unsigned verb = *(fVerbs - 1); // fVerbs is one beyond the current verb
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001716 switch (verb) {
1717 case kMove_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001718 // Keep a record of this most recent move
1719 lastMoveVerb = fVerbs;
1720 lastMovePt = fPts;
robertphillipsb8de1f42015-02-23 11:17:01 -08001721 lastMoveWeight = fConicWeights;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001722 lastPt = fPts[0];
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001723 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001724 fPts++;
1725 break;
1726
1727 case kClose_Verb:
schenney@chromium.org7e963602012-06-13 17:05:43 +00001728 // A close when we are in a segment is always valid except when it
1729 // follows a move which follows a segment.
1730 if (fSegmentState == kAfterPrimitive_SegmentState && !lastMoveVerb) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001731 return;
1732 }
1733 // A close at any other time must be ignored
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001734 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001735 break;
1736
1737 case kLine_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001738 if (!IsLineDegenerate(lastPt, fPts[0], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001739 if (lastMoveVerb) {
1740 fVerbs = lastMoveVerb;
1741 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001742 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001743 return;
1744 }
1745 return;
1746 }
1747 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001748 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001749 fPts++;
1750 break;
1751
reed@google.com277c3f82013-05-31 15:17:50 +00001752 case kConic_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001753 case kQuad_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001754 if (!IsQuadDegenerate(lastPt, fPts[0], fPts[1], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001755 if (lastMoveVerb) {
1756 fVerbs = lastMoveVerb;
1757 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001758 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001759 return;
1760 }
1761 return;
1762 }
1763 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001764 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001765 fPts += 2;
reed@google.com277c3f82013-05-31 15:17:50 +00001766 fConicWeights += (kConic_Verb == verb);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001767 break;
1768
1769 case kCubic_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001770 if (!IsCubicDegenerate(lastPt, fPts[0], fPts[1], fPts[2], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001771 if (lastMoveVerb) {
1772 fVerbs = lastMoveVerb;
1773 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001774 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001775 return;
1776 }
1777 return;
1778 }
1779 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001780 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001781 fPts += 3;
1782 break;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001783
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001784 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001785 SkDEBUGFAIL("Should never see kDone_Verb");
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001786 }
1787 }
1788}
1789
reed@google.com4a3b7142012-05-16 17:16:46 +00001790SkPath::Verb SkPath::Iter::doNext(SkPoint ptsParam[4]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001791 SkASSERT(ptsParam);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001792
reed@android.com8a1c16f2008-12-17 15:59:43 +00001793 if (fVerbs == fVerbStop) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001794 // Close the curve if requested and if there is some curve to close
1795 if (fNeedClose && fSegmentState == kAfterPrimitive_SegmentState) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001796 if (kLine_Verb == this->autoClose(ptsParam)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001797 return kLine_Verb;
1798 }
1799 fNeedClose = false;
1800 return kClose_Verb;
1801 }
1802 return kDone_Verb;
1803 }
1804
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001805 // fVerbs is one beyond the current verb, decrement first
1806 unsigned verb = *(--fVerbs);
reed@google.com9e25dbf2012-05-15 17:05:38 +00001807 const SkPoint* SK_RESTRICT srcPts = fPts;
1808 SkPoint* SK_RESTRICT pts = ptsParam;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001809
1810 switch (verb) {
1811 case kMove_Verb:
1812 if (fNeedClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001813 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001814 verb = this->autoClose(pts);
1815 if (verb == kClose_Verb) {
1816 fNeedClose = false;
1817 }
1818 return (Verb)verb;
1819 }
1820 if (fVerbs == fVerbStop) { // might be a trailing moveto
1821 return kDone_Verb;
1822 }
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001823 fMoveTo = *srcPts;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001824 pts[0] = *srcPts;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001825 srcPts += 1;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001826 fSegmentState = kAfterMove_SegmentState;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001827 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001828 fNeedClose = fForceClose;
1829 break;
1830 case kLine_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001831 pts[0] = this->cons_moveTo();
1832 pts[1] = srcPts[0];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001833 fLastPt = srcPts[0];
1834 fCloseLine = false;
1835 srcPts += 1;
1836 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001837 case kConic_Verb:
1838 fConicWeights += 1;
1839 // fall-through
reed@android.com8a1c16f2008-12-17 15:59:43 +00001840 case kQuad_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001841 pts[0] = this->cons_moveTo();
1842 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001843 fLastPt = srcPts[1];
1844 srcPts += 2;
1845 break;
1846 case kCubic_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001847 pts[0] = this->cons_moveTo();
1848 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001849 fLastPt = srcPts[2];
1850 srcPts += 3;
1851 break;
1852 case kClose_Verb:
1853 verb = this->autoClose(pts);
1854 if (verb == kLine_Verb) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001855 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001856 } else {
1857 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001858 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001859 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001860 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001861 break;
1862 }
1863 fPts = srcPts;
1864 return (Verb)verb;
1865}
1866
1867///////////////////////////////////////////////////////////////////////////////
1868
reed@android.com8a1c16f2008-12-17 15:59:43 +00001869/*
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001870 Format in compressed buffer: [ptCount, verbCount, pts[], verbs[]]
reed@android.com8a1c16f2008-12-17 15:59:43 +00001871*/
1872
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00001873size_t SkPath::writeToMemory(void* storage) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001874 SkDEBUGCODE(this->validate();)
1875
halcanary96fcdcc2015-08-27 07:41:13 -07001876 if (nullptr == storage) {
robertphillips@google.comca0c8382013-09-26 12:18:23 +00001877 const int byteCount = sizeof(int32_t) + fPathRef->writeSize();
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001878 return SkAlign4(byteCount);
1879 }
1880
1881 SkWBuffer buffer(storage);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001882
robertphillips@google.com466310d2013-12-03 16:43:54 +00001883 int32_t packed = (fConvexity << kConvexity_SerializationShift) |
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001884 (fFillType << kFillType_SerializationShift) |
reed026beb52015-06-10 14:23:15 -07001885 (fFirstDirection << kDirection_SerializationShift) |
reed8f086022015-06-11 14:22:19 -07001886 (fIsVolatile << kIsVolatile_SerializationShift) |
1887 kCurrent_Version;
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001888
robertphillips@google.com2972bb52012-08-07 17:32:51 +00001889 buffer.write32(packed);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001890
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001891 fPathRef->writeToBuffer(&buffer);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001892
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001893 buffer.padToAlign4();
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00001894 return buffer.pos();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001895}
1896
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00001897size_t SkPath::readFromMemory(const void* storage, size_t length) {
1898 SkRBufferWithSizeCheck buffer(storage, length);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001899
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00001900 int32_t packed;
1901 if (!buffer.readS32(&packed)) {
1902 return 0;
1903 }
1904
reed8f086022015-06-11 14:22:19 -07001905 unsigned version = packed & 0xFF;
mtklein1b249332015-07-07 12:21:21 -07001906
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001907 fConvexity = (packed >> kConvexity_SerializationShift) & 0xFF;
1908 fFillType = (packed >> kFillType_SerializationShift) & 0xFF;
reed8f086022015-06-11 14:22:19 -07001909 uint8_t dir = (packed >> kDirection_SerializationShift) & 0x3;
jvanverthb3eb6872014-10-24 07:12:51 -07001910 fIsVolatile = (packed >> kIsVolatile_SerializationShift) & 0x1;
commit-bot@chromium.orgfed2ab62014-01-23 15:16:05 +00001911 SkPathRef* pathRef = SkPathRef::CreateFromBuffer(&buffer);
reed@google.comabf15c12011-01-18 20:35:51 +00001912
reed8f086022015-06-11 14:22:19 -07001913 // compatibility check
1914 if (version < kPathPrivFirstDirection_Version) {
1915 switch (dir) { // old values
1916 case 0:
1917 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
1918 break;
1919 case 1:
1920 fFirstDirection = SkPathPriv::kCW_FirstDirection;
1921 break;
1922 case 2:
1923 fFirstDirection = SkPathPriv::kCCW_FirstDirection;
1924 break;
1925 default:
1926 SkASSERT(false);
1927 }
1928 } else {
1929 fFirstDirection = dir;
1930 }
1931
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00001932 size_t sizeRead = 0;
1933 if (buffer.isValid()) {
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00001934 fPathRef.reset(pathRef);
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00001935 SkDEBUGCODE(this->validate();)
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00001936 buffer.skipToAlign4();
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00001937 sizeRead = buffer.pos();
bsalomon49f085d2014-09-05 13:34:00 -07001938 } else if (pathRef) {
halcanary96fcdcc2015-08-27 07:41:13 -07001939 // If the buffer is not valid, pathRef should be nullptr
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00001940 sk_throw();
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00001941 }
1942 return sizeRead;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001943}
1944
1945///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00001946
reede05fed02014-12-15 07:59:53 -08001947#include "SkStringUtils.h"
caryclark66a5d8b2014-06-24 08:30:15 -07001948#include "SkStream.h"
reed@google.com51bbe752013-01-17 22:07:50 +00001949
reed@google.com51bbe752013-01-17 22:07:50 +00001950static void append_params(SkString* str, const char label[], const SkPoint pts[],
reede05fed02014-12-15 07:59:53 -08001951 int count, SkScalarAsStringType strType, SkScalar conicWeight = -1) {
reed@google.com51bbe752013-01-17 22:07:50 +00001952 str->append(label);
1953 str->append("(");
skia.committer@gmail.com15dd3002013-01-18 07:07:28 +00001954
reed@google.com51bbe752013-01-17 22:07:50 +00001955 const SkScalar* values = &pts[0].fX;
1956 count *= 2;
1957
1958 for (int i = 0; i < count; ++i) {
reede05fed02014-12-15 07:59:53 -08001959 SkAppendScalar(str, values[i], strType);
reed@google.com51bbe752013-01-17 22:07:50 +00001960 if (i < count - 1) {
1961 str->append(", ");
1962 }
1963 }
reed@google.com277c3f82013-05-31 15:17:50 +00001964 if (conicWeight >= 0) {
1965 str->append(", ");
reede05fed02014-12-15 07:59:53 -08001966 SkAppendScalar(str, conicWeight, strType);
reed@google.com277c3f82013-05-31 15:17:50 +00001967 }
caryclark08fa28c2014-10-23 13:08:56 -07001968 str->append(");");
reede05fed02014-12-15 07:59:53 -08001969 if (kHex_SkScalarAsStringType == strType) {
caryclark08fa28c2014-10-23 13:08:56 -07001970 str->append(" // ");
1971 for (int i = 0; i < count; ++i) {
reede05fed02014-12-15 07:59:53 -08001972 SkAppendScalarDec(str, values[i]);
caryclark08fa28c2014-10-23 13:08:56 -07001973 if (i < count - 1) {
1974 str->append(", ");
1975 }
1976 }
1977 if (conicWeight >= 0) {
1978 str->append(", ");
reede05fed02014-12-15 07:59:53 -08001979 SkAppendScalarDec(str, conicWeight);
caryclark08fa28c2014-10-23 13:08:56 -07001980 }
1981 }
1982 str->append("\n");
reed@google.com51bbe752013-01-17 22:07:50 +00001983}
1984
caryclarke9562592014-09-15 09:26:09 -07001985void SkPath::dump(SkWStream* wStream, bool forceClose, bool dumpAsHex) const {
reede05fed02014-12-15 07:59:53 -08001986 SkScalarAsStringType asType = dumpAsHex ? kHex_SkScalarAsStringType : kDec_SkScalarAsStringType;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001987 Iter iter(*this, forceClose);
1988 SkPoint pts[4];
1989 Verb verb;
1990
caryclark66a5d8b2014-06-24 08:30:15 -07001991 if (!wStream) {
1992 SkDebugf("path: forceClose=%s\n", forceClose ? "true" : "false");
1993 }
reed@google.com51bbe752013-01-17 22:07:50 +00001994 SkString builder;
1995
reed@google.com4a3b7142012-05-16 17:16:46 +00001996 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001997 switch (verb) {
1998 case kMove_Verb:
reede05fed02014-12-15 07:59:53 -08001999 append_params(&builder, "path.moveTo", &pts[0], 1, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002000 break;
2001 case kLine_Verb:
reede05fed02014-12-15 07:59:53 -08002002 append_params(&builder, "path.lineTo", &pts[1], 1, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002003 break;
2004 case kQuad_Verb:
reede05fed02014-12-15 07:59:53 -08002005 append_params(&builder, "path.quadTo", &pts[1], 2, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002006 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002007 case kConic_Verb:
reede05fed02014-12-15 07:59:53 -08002008 append_params(&builder, "path.conicTo", &pts[1], 2, asType, iter.conicWeight());
reed@google.com277c3f82013-05-31 15:17:50 +00002009 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002010 case kCubic_Verb:
reede05fed02014-12-15 07:59:53 -08002011 append_params(&builder, "path.cubicTo", &pts[1], 3, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002012 break;
2013 case kClose_Verb:
caryclark66a5d8b2014-06-24 08:30:15 -07002014 builder.append("path.close();\n");
reed@android.com8a1c16f2008-12-17 15:59:43 +00002015 break;
2016 default:
2017 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
2018 verb = kDone_Verb; // stop the loop
2019 break;
2020 }
caryclark1049f122015-04-20 08:31:59 -07002021 if (!wStream && builder.size()) {
2022 SkDebugf("%s", builder.c_str());
2023 builder.reset();
2024 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002025 }
caryclark66a5d8b2014-06-24 08:30:15 -07002026 if (wStream) {
2027 wStream->writeText(builder.c_str());
caryclark66a5d8b2014-06-24 08:30:15 -07002028 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002029}
2030
reed@android.come522ca52009-11-23 20:10:41 +00002031void SkPath::dump() const {
halcanary96fcdcc2015-08-27 07:41:13 -07002032 this->dump(nullptr, false, false);
caryclarke9562592014-09-15 09:26:09 -07002033}
2034
2035void SkPath::dumpHex() const {
halcanary96fcdcc2015-08-27 07:41:13 -07002036 this->dump(nullptr, false, true);
reed@android.come522ca52009-11-23 20:10:41 +00002037}
2038
2039#ifdef SK_DEBUG
2040void SkPath::validate() const {
reed@android.come522ca52009-11-23 20:10:41 +00002041 SkASSERT((fFillType & ~3) == 0);
reed@google.comabf15c12011-01-18 20:35:51 +00002042
djsollen@google.com077348c2012-10-22 20:23:32 +00002043#ifdef SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002044 if (!fBoundsIsDirty) {
2045 SkRect bounds;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002046
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002047 bool isFinite = compute_pt_bounds(&bounds, *fPathRef.get());
robertphillips@google.com5d8d1862012-08-15 14:36:41 +00002048 SkASSERT(SkToBool(fIsFinite) == isFinite);
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002049
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002050 if (fPathRef->countPoints() <= 1) {
reed@android.come522ca52009-11-23 20:10:41 +00002051 // if we're empty, fBounds may be empty but translated, so we can't
2052 // necessarily compare to bounds directly
2053 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
2054 // be [2, 2, 2, 2]
2055 SkASSERT(bounds.isEmpty());
2056 SkASSERT(fBounds.isEmpty());
2057 } else {
reed@google.comeac52bd2011-11-14 18:13:59 +00002058 if (bounds.isEmpty()) {
2059 SkASSERT(fBounds.isEmpty());
2060 } else {
reed@google.com3563c9e2011-11-14 19:34:57 +00002061 if (!fBounds.isEmpty()) {
2062 SkASSERT(fBounds.contains(bounds));
2063 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002064 }
reed@android.come522ca52009-11-23 20:10:41 +00002065 }
2066 }
djsollen@google.com077348c2012-10-22 20:23:32 +00002067#endif // SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002068}
djsollen@google.com077348c2012-10-22 20:23:32 +00002069#endif // SK_DEBUG
reed@android.come522ca52009-11-23 20:10:41 +00002070
reed@google.com04863fa2011-05-15 04:08:24 +00002071///////////////////////////////////////////////////////////////////////////////
2072
reed@google.com0b7b9822011-05-16 12:29:27 +00002073static int sign(SkScalar x) { return x < 0; }
2074#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00002075
robertphillipsc506e302014-09-16 09:43:31 -07002076enum DirChange {
2077 kLeft_DirChange,
2078 kRight_DirChange,
2079 kStraight_DirChange,
2080 kBackwards_DirChange,
2081
2082 kInvalid_DirChange
2083};
2084
2085
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002086static bool almost_equal(SkScalar compA, SkScalar compB) {
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002087 // The error epsilon was empirically derived; worse case round rects
2088 // with a mid point outset by 2x float epsilon in tests had an error
2089 // of 12.
2090 const int epsilon = 16;
2091 if (!SkScalarIsFinite(compA) || !SkScalarIsFinite(compB)) {
2092 return false;
2093 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002094 // no need to check for small numbers because SkPath::Iter has removed degenerate values
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002095 int aBits = SkFloatAs2sCompliment(compA);
2096 int bBits = SkFloatAs2sCompliment(compB);
2097 return aBits < bBits + epsilon && bBits < aBits + epsilon;
reed@google.com04863fa2011-05-15 04:08:24 +00002098}
2099
caryclarkb4216502015-03-02 13:02:34 -08002100static bool approximately_zero_when_compared_to(double x, double y) {
2101 return x == 0 || fabs(x) < fabs(y * FLT_EPSILON);
robertphillipsc506e302014-09-16 09:43:31 -07002102}
2103
caryclarkb4216502015-03-02 13:02:34 -08002104
reed@google.com04863fa2011-05-15 04:08:24 +00002105// only valid for a single contour
2106struct Convexicator {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002107 Convexicator()
2108 : fPtCount(0)
2109 , fConvexity(SkPath::kConvex_Convexity)
reed026beb52015-06-10 14:23:15 -07002110 , fFirstDirection(SkPathPriv::kUnknown_FirstDirection)
caryclark5ccef572015-03-02 10:07:56 -08002111 , fIsFinite(true)
2112 , fIsCurve(false) {
robertphillipsc506e302014-09-16 09:43:31 -07002113 fExpectedDir = kInvalid_DirChange;
reed@google.com04863fa2011-05-15 04:08:24 +00002114 // warnings
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002115 fLastPt.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002116 fCurrPt.set(0, 0);
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002117 fLastVec.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002118 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00002119
2120 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00002121 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00002122 }
2123
2124 SkPath::Convexity getConvexity() const { return fConvexity; }
2125
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002126 /** The direction returned is only valid if the path is determined convex */
reed026beb52015-06-10 14:23:15 -07002127 SkPathPriv::FirstDirection getFirstDirection() const { return fFirstDirection; }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002128
reed@google.com04863fa2011-05-15 04:08:24 +00002129 void addPt(const SkPoint& pt) {
caryclarkd3d1a982014-12-08 04:57:38 -08002130 if (SkPath::kConcave_Convexity == fConvexity || !fIsFinite) {
reed@google.com04863fa2011-05-15 04:08:24 +00002131 return;
2132 }
2133
2134 if (0 == fPtCount) {
2135 fCurrPt = pt;
2136 ++fPtCount;
2137 } else {
2138 SkVector vec = pt - fCurrPt;
caryclarkd3d1a982014-12-08 04:57:38 -08002139 SkScalar lengthSqd = vec.lengthSqd();
2140 if (!SkScalarIsFinite(lengthSqd)) {
2141 fIsFinite = false;
caryclarke8c56662015-07-14 11:19:26 -07002142 } else if (lengthSqd) {
caryclarkb4216502015-03-02 13:02:34 -08002143 fPriorPt = fLastPt;
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002144 fLastPt = fCurrPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002145 fCurrPt = pt;
2146 if (++fPtCount == 2) {
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002147 fFirstVec = fLastVec = vec;
reed@google.com04863fa2011-05-15 04:08:24 +00002148 } else {
2149 SkASSERT(fPtCount > 2);
2150 this->addVec(vec);
2151 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002152
reed@google.com85b6e392011-05-15 20:25:17 +00002153 int sx = sign(vec.fX);
2154 int sy = sign(vec.fY);
2155 fDx += (sx != fSx);
2156 fDy += (sy != fSy);
2157 fSx = sx;
2158 fSy = sy;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002159
reed@google.com85b6e392011-05-15 20:25:17 +00002160 if (fDx > 3 || fDy > 3) {
2161 fConvexity = SkPath::kConcave_Convexity;
2162 }
reed@google.com04863fa2011-05-15 04:08:24 +00002163 }
2164 }
2165 }
2166
2167 void close() {
2168 if (fPtCount > 2) {
2169 this->addVec(fFirstVec);
2170 }
2171 }
2172
caryclarkb4216502015-03-02 13:02:34 -08002173 DirChange directionChange(const SkVector& curVec) {
2174 SkScalar cross = SkPoint::CrossProduct(fLastVec, curVec);
2175
2176 SkScalar smallest = SkTMin(fCurrPt.fX, SkTMin(fCurrPt.fY, SkTMin(fLastPt.fX, fLastPt.fY)));
2177 SkScalar largest = SkTMax(fCurrPt.fX, SkTMax(fCurrPt.fY, SkTMax(fLastPt.fX, fLastPt.fY)));
2178 largest = SkTMax(largest, -smallest);
2179
2180 if (!almost_equal(largest, largest + cross)) {
2181 int sign = SkScalarSignAsInt(cross);
2182 if (sign) {
2183 return (1 == sign) ? kRight_DirChange : kLeft_DirChange;
2184 }
2185 }
2186
2187 if (cross) {
2188 double dLastVecX = SkScalarToDouble(fLastPt.fX) - SkScalarToDouble(fPriorPt.fX);
2189 double dLastVecY = SkScalarToDouble(fLastPt.fY) - SkScalarToDouble(fPriorPt.fY);
2190 double dCurrVecX = SkScalarToDouble(fCurrPt.fX) - SkScalarToDouble(fLastPt.fX);
2191 double dCurrVecY = SkScalarToDouble(fCurrPt.fY) - SkScalarToDouble(fLastPt.fY);
2192 double dCross = dLastVecX * dCurrVecY - dLastVecY * dCurrVecX;
2193 if (!approximately_zero_when_compared_to(dCross, SkScalarToDouble(largest))) {
2194 int sign = SkScalarSignAsInt(SkDoubleToScalar(dCross));
2195 if (sign) {
2196 return (1 == sign) ? kRight_DirChange : kLeft_DirChange;
2197 }
2198 }
2199 }
2200
2201 if (!SkScalarNearlyZero(fLastVec.lengthSqd(), SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
2202 !SkScalarNearlyZero(curVec.lengthSqd(), SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
2203 fLastVec.dot(curVec) < 0.0f) {
2204 return kBackwards_DirChange;
2205 }
2206
2207 return kStraight_DirChange;
2208 }
2209
2210
caryclarkd3d1a982014-12-08 04:57:38 -08002211 bool isFinite() const {
2212 return fIsFinite;
2213 }
2214
caryclark5ccef572015-03-02 10:07:56 -08002215 void setCurve(bool isCurve) {
2216 fIsCurve = isCurve;
2217 }
2218
reed@google.com04863fa2011-05-15 04:08:24 +00002219private:
2220 void addVec(const SkVector& vec) {
2221 SkASSERT(vec.fX || vec.fY);
caryclarkb4216502015-03-02 13:02:34 -08002222 DirChange dir = this->directionChange(vec);
robertphillipsc506e302014-09-16 09:43:31 -07002223 switch (dir) {
2224 case kLeft_DirChange: // fall through
2225 case kRight_DirChange:
2226 if (kInvalid_DirChange == fExpectedDir) {
2227 fExpectedDir = dir;
reed026beb52015-06-10 14:23:15 -07002228 fFirstDirection = (kRight_DirChange == dir) ? SkPathPriv::kCW_FirstDirection
2229 : SkPathPriv::kCCW_FirstDirection;
robertphillipsc506e302014-09-16 09:43:31 -07002230 } else if (dir != fExpectedDir) {
2231 fConvexity = SkPath::kConcave_Convexity;
reed026beb52015-06-10 14:23:15 -07002232 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
robertphillipsc506e302014-09-16 09:43:31 -07002233 }
2234 fLastVec = vec;
2235 break;
2236 case kStraight_DirChange:
2237 break;
2238 case kBackwards_DirChange:
caryclark5ccef572015-03-02 10:07:56 -08002239 if (fIsCurve) {
2240 fConvexity = SkPath::kConcave_Convexity;
reed026beb52015-06-10 14:23:15 -07002241 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
caryclark5ccef572015-03-02 10:07:56 -08002242 }
robertphillipsc506e302014-09-16 09:43:31 -07002243 fLastVec = vec;
2244 break;
2245 case kInvalid_DirChange:
2246 SkFAIL("Use of invalid direction change flag");
2247 break;
reed@google.com04863fa2011-05-15 04:08:24 +00002248 }
2249 }
2250
caryclarkb4216502015-03-02 13:02:34 -08002251 SkPoint fPriorPt;
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002252 SkPoint fLastPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002253 SkPoint fCurrPt;
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002254 // fLastVec does not necessarily start at fLastPt. We only advance it when the cross product
2255 // value with the current vec is deemed to be of a significant value.
2256 SkVector fLastVec, fFirstVec;
reed@google.com04863fa2011-05-15 04:08:24 +00002257 int fPtCount; // non-degenerate points
robertphillipsc506e302014-09-16 09:43:31 -07002258 DirChange fExpectedDir;
reed@google.com04863fa2011-05-15 04:08:24 +00002259 SkPath::Convexity fConvexity;
reed026beb52015-06-10 14:23:15 -07002260 SkPathPriv::FirstDirection fFirstDirection;
reed@google.com0b7b9822011-05-16 12:29:27 +00002261 int fDx, fDy, fSx, fSy;
caryclarkd3d1a982014-12-08 04:57:38 -08002262 bool fIsFinite;
caryclark5ccef572015-03-02 10:07:56 -08002263 bool fIsCurve;
reed@google.com04863fa2011-05-15 04:08:24 +00002264};
2265
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002266SkPath::Convexity SkPath::internalGetConvexity() const {
2267 SkASSERT(kUnknown_Convexity == fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002268 SkPoint pts[4];
2269 SkPath::Verb verb;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002270 SkPath::Iter iter(*this, true);
reed@google.com04863fa2011-05-15 04:08:24 +00002271
2272 int contourCount = 0;
2273 int count;
2274 Convexicator state;
2275
caryclarkd3d1a982014-12-08 04:57:38 -08002276 if (!isFinite()) {
2277 return kUnknown_Convexity;
2278 }
caryclarke8c56662015-07-14 11:19:26 -07002279 while ((verb = iter.next(pts, true, true)) != SkPath::kDone_Verb) {
reed@google.com04863fa2011-05-15 04:08:24 +00002280 switch (verb) {
2281 case kMove_Verb:
2282 if (++contourCount > 1) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002283 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002284 return kConcave_Convexity;
2285 }
2286 pts[1] = pts[0];
caryclark5ccef572015-03-02 10:07:56 -08002287 // fall through
2288 case kLine_Verb:
reed@google.com04863fa2011-05-15 04:08:24 +00002289 count = 1;
caryclark5ccef572015-03-02 10:07:56 -08002290 state.setCurve(false);
reed@google.com04863fa2011-05-15 04:08:24 +00002291 break;
caryclark5ccef572015-03-02 10:07:56 -08002292 case kQuad_Verb:
2293 // fall through
2294 case kConic_Verb:
2295 // fall through
2296 case kCubic_Verb:
2297 count = 2 + (kCubic_Verb == verb);
2298 // As an additional enhancement, this could set curve true only
2299 // if the curve is nonlinear
2300 state.setCurve(true);
2301 break;
reed@google.com04863fa2011-05-15 04:08:24 +00002302 case kClose_Verb:
caryclark5ccef572015-03-02 10:07:56 -08002303 state.setCurve(false);
reed@google.com04863fa2011-05-15 04:08:24 +00002304 state.close();
2305 count = 0;
2306 break;
2307 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00002308 SkDEBUGFAIL("bad verb");
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002309 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002310 return kConcave_Convexity;
2311 }
2312
2313 for (int i = 1; i <= count; i++) {
2314 state.addPt(pts[i]);
2315 }
2316 // early exit
caryclarkd3d1a982014-12-08 04:57:38 -08002317 if (!state.isFinite()) {
2318 return kUnknown_Convexity;
2319 }
reed@google.com04863fa2011-05-15 04:08:24 +00002320 if (kConcave_Convexity == state.getConvexity()) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002321 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002322 return kConcave_Convexity;
2323 }
2324 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002325 fConvexity = state.getConvexity();
reed026beb52015-06-10 14:23:15 -07002326 if (kConvex_Convexity == fConvexity && SkPathPriv::kUnknown_FirstDirection == fFirstDirection) {
2327 fFirstDirection = state.getFirstDirection();
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002328 }
2329 return static_cast<Convexity>(fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002330}
reed@google.com69a99432012-01-10 18:00:10 +00002331
2332///////////////////////////////////////////////////////////////////////////////
2333
2334class ContourIter {
2335public:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002336 ContourIter(const SkPathRef& pathRef);
reed@google.com69a99432012-01-10 18:00:10 +00002337
2338 bool done() const { return fDone; }
2339 // if !done() then these may be called
2340 int count() const { return fCurrPtCount; }
2341 const SkPoint* pts() const { return fCurrPt; }
2342 void next();
2343
2344private:
2345 int fCurrPtCount;
2346 const SkPoint* fCurrPt;
2347 const uint8_t* fCurrVerb;
2348 const uint8_t* fStopVerbs;
reed@google.com277c3f82013-05-31 15:17:50 +00002349 const SkScalar* fCurrConicWeight;
reed@google.com69a99432012-01-10 18:00:10 +00002350 bool fDone;
reed@google.comd1ab9322012-01-10 18:40:03 +00002351 SkDEBUGCODE(int fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002352};
2353
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002354ContourIter::ContourIter(const SkPathRef& pathRef) {
2355 fStopVerbs = pathRef.verbsMemBegin();
reed@google.com69a99432012-01-10 18:00:10 +00002356 fDone = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002357 fCurrPt = pathRef.points();
2358 fCurrVerb = pathRef.verbs();
reed@google.com277c3f82013-05-31 15:17:50 +00002359 fCurrConicWeight = pathRef.conicWeights();
reed@google.com69a99432012-01-10 18:00:10 +00002360 fCurrPtCount = 0;
reed@google.comd1ab9322012-01-10 18:40:03 +00002361 SkDEBUGCODE(fContourCounter = 0;)
reed@google.com69a99432012-01-10 18:00:10 +00002362 this->next();
2363}
2364
2365void ContourIter::next() {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002366 if (fCurrVerb <= fStopVerbs) {
reed@google.com69a99432012-01-10 18:00:10 +00002367 fDone = true;
2368 }
2369 if (fDone) {
2370 return;
2371 }
2372
2373 // skip pts of prev contour
2374 fCurrPt += fCurrPtCount;
2375
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002376 SkASSERT(SkPath::kMove_Verb == fCurrVerb[~0]);
reed@google.com69a99432012-01-10 18:00:10 +00002377 int ptCount = 1; // moveTo
2378 const uint8_t* verbs = fCurrVerb;
2379
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002380 for (--verbs; verbs > fStopVerbs; --verbs) {
2381 switch (verbs[~0]) {
reed@google.com69a99432012-01-10 18:00:10 +00002382 case SkPath::kMove_Verb:
reed@google.com69a99432012-01-10 18:00:10 +00002383 goto CONTOUR_END;
2384 case SkPath::kLine_Verb:
2385 ptCount += 1;
2386 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002387 case SkPath::kConic_Verb:
2388 fCurrConicWeight += 1;
2389 // fall-through
reed@google.com69a99432012-01-10 18:00:10 +00002390 case SkPath::kQuad_Verb:
2391 ptCount += 2;
2392 break;
2393 case SkPath::kCubic_Verb:
2394 ptCount += 3;
2395 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002396 case SkPath::kClose_Verb:
2397 break;
2398 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00002399 SkDEBUGFAIL("unexpected verb");
reed@google.com69a99432012-01-10 18:00:10 +00002400 break;
2401 }
2402 }
2403CONTOUR_END:
2404 fCurrPtCount = ptCount;
2405 fCurrVerb = verbs;
reed@google.comd1ab9322012-01-10 18:40:03 +00002406 SkDEBUGCODE(++fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002407}
2408
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002409// returns cross product of (p1 - p0) and (p2 - p0)
reed@google.com69a99432012-01-10 18:00:10 +00002410static SkScalar cross_prod(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002411 SkScalar cross = SkPoint::CrossProduct(p1 - p0, p2 - p0);
2412 // We may get 0 when the above subtracts underflow. We expect this to be
2413 // very rare and lazily promote to double.
2414 if (0 == cross) {
2415 double p0x = SkScalarToDouble(p0.fX);
2416 double p0y = SkScalarToDouble(p0.fY);
2417
2418 double p1x = SkScalarToDouble(p1.fX);
2419 double p1y = SkScalarToDouble(p1.fY);
2420
2421 double p2x = SkScalarToDouble(p2.fX);
2422 double p2y = SkScalarToDouble(p2.fY);
2423
2424 cross = SkDoubleToScalar((p1x - p0x) * (p2y - p0y) -
2425 (p1y - p0y) * (p2x - p0x));
2426
2427 }
2428 return cross;
reed@google.com69a99432012-01-10 18:00:10 +00002429}
2430
reed@google.comc1ea60a2012-01-31 15:15:36 +00002431// Returns the first pt with the maximum Y coordinate
reed@google.com69a99432012-01-10 18:00:10 +00002432static int find_max_y(const SkPoint pts[], int count) {
2433 SkASSERT(count > 0);
2434 SkScalar max = pts[0].fY;
reed@google.comc1ea60a2012-01-31 15:15:36 +00002435 int firstIndex = 0;
reed@google.com69a99432012-01-10 18:00:10 +00002436 for (int i = 1; i < count; ++i) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002437 SkScalar y = pts[i].fY;
2438 if (y > max) {
2439 max = y;
2440 firstIndex = i;
reed@google.com69a99432012-01-10 18:00:10 +00002441 }
2442 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002443 return firstIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002444}
2445
reed@google.comcabaf1d2012-01-11 21:03:05 +00002446static int find_diff_pt(const SkPoint pts[], int index, int n, int inc) {
2447 int i = index;
2448 for (;;) {
2449 i = (i + inc) % n;
2450 if (i == index) { // we wrapped around, so abort
2451 break;
2452 }
2453 if (pts[index] != pts[i]) { // found a different point, success!
2454 break;
2455 }
2456 }
2457 return i;
2458}
2459
reed@google.comc1ea60a2012-01-31 15:15:36 +00002460/**
2461 * Starting at index, and moving forward (incrementing), find the xmin and
2462 * xmax of the contiguous points that have the same Y.
2463 */
2464static int find_min_max_x_at_y(const SkPoint pts[], int index, int n,
2465 int* maxIndexPtr) {
2466 const SkScalar y = pts[index].fY;
2467 SkScalar min = pts[index].fX;
2468 SkScalar max = min;
2469 int minIndex = index;
2470 int maxIndex = index;
2471 for (int i = index + 1; i < n; ++i) {
2472 if (pts[i].fY != y) {
2473 break;
2474 }
2475 SkScalar x = pts[i].fX;
2476 if (x < min) {
2477 min = x;
2478 minIndex = i;
2479 } else if (x > max) {
2480 max = x;
2481 maxIndex = i;
2482 }
2483 }
2484 *maxIndexPtr = maxIndex;
2485 return minIndex;
2486}
2487
reed026beb52015-06-10 14:23:15 -07002488static void crossToDir(SkScalar cross, SkPathPriv::FirstDirection* dir) {
2489 *dir = cross > 0 ? SkPathPriv::kCW_FirstDirection : SkPathPriv::kCCW_FirstDirection;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002490}
2491
reed@google.comac8543f2012-01-30 20:51:25 +00002492/*
2493 * We loop through all contours, and keep the computed cross-product of the
2494 * contour that contained the global y-max. If we just look at the first
2495 * contour, we may find one that is wound the opposite way (correctly) since
2496 * it is the interior of a hole (e.g. 'o'). Thus we must find the contour
2497 * that is outer most (or at least has the global y-max) before we can consider
2498 * its cross product.
2499 */
reed026beb52015-06-10 14:23:15 -07002500bool SkPathPriv::CheapComputeFirstDirection(const SkPath& path, FirstDirection* dir) {
herb9f4dbca2015-09-28 11:05:47 -07002501 if (kUnknown_FirstDirection != path.fFirstDirection.load()) {
2502 *dir = static_cast<FirstDirection>(path.fFirstDirection.load());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002503 return true;
2504 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002505
2506 // don't want to pay the cost for computing this if it
2507 // is unknown, so we don't call isConvex()
reed026beb52015-06-10 14:23:15 -07002508 if (SkPath::kConvex_Convexity == path.getConvexityOrUnknown()) {
2509 SkASSERT(kUnknown_FirstDirection == path.fFirstDirection);
herb9f4dbca2015-09-28 11:05:47 -07002510 *dir = static_cast<FirstDirection>(path.fFirstDirection.load());
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002511 return false;
2512 }
reed@google.com69a99432012-01-10 18:00:10 +00002513
reed026beb52015-06-10 14:23:15 -07002514 ContourIter iter(*path.fPathRef.get());
reed@google.com69a99432012-01-10 18:00:10 +00002515
reed@google.comac8543f2012-01-30 20:51:25 +00002516 // initialize with our logical y-min
reed026beb52015-06-10 14:23:15 -07002517 SkScalar ymax = path.getBounds().fTop;
reed@google.comac8543f2012-01-30 20:51:25 +00002518 SkScalar ymaxCross = 0;
2519
reed@google.com69a99432012-01-10 18:00:10 +00002520 for (; !iter.done(); iter.next()) {
2521 int n = iter.count();
reed@google.comcabaf1d2012-01-11 21:03:05 +00002522 if (n < 3) {
2523 continue;
2524 }
djsollen@google.come63793a2012-03-21 15:39:03 +00002525
reed@google.comcabaf1d2012-01-11 21:03:05 +00002526 const SkPoint* pts = iter.pts();
reed@google.com69a99432012-01-10 18:00:10 +00002527 SkScalar cross = 0;
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002528 int index = find_max_y(pts, n);
2529 if (pts[index].fY < ymax) {
2530 continue;
2531 }
2532
2533 // If there is more than 1 distinct point at the y-max, we take the
2534 // x-min and x-max of them and just subtract to compute the dir.
2535 if (pts[(index + 1) % n].fY == pts[index].fY) {
2536 int maxIndex;
2537 int minIndex = find_min_max_x_at_y(pts, index, n, &maxIndex);
2538 if (minIndex == maxIndex) {
2539 goto TRY_CROSSPROD;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002540 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002541 SkASSERT(pts[minIndex].fY == pts[index].fY);
2542 SkASSERT(pts[maxIndex].fY == pts[index].fY);
2543 SkASSERT(pts[minIndex].fX <= pts[maxIndex].fX);
2544 // we just subtract the indices, and let that auto-convert to
2545 // SkScalar, since we just want - or + to signal the direction.
2546 cross = minIndex - maxIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002547 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002548 TRY_CROSSPROD:
2549 // Find a next and prev index to use for the cross-product test,
2550 // but we try to find pts that form non-zero vectors from pts[index]
2551 //
2552 // Its possible that we can't find two non-degenerate vectors, so
2553 // we have to guard our search (e.g. all the pts could be in the
2554 // same place).
2555
2556 // we pass n - 1 instead of -1 so we don't foul up % operator by
2557 // passing it a negative LH argument.
2558 int prev = find_diff_pt(pts, index, n, n - 1);
2559 if (prev == index) {
2560 // completely degenerate, skip to next contour
reed@google.comac8543f2012-01-30 20:51:25 +00002561 continue;
2562 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002563 int next = find_diff_pt(pts, index, n, 1);
2564 SkASSERT(next != index);
2565 cross = cross_prod(pts[prev], pts[index], pts[next]);
2566 // if we get a zero and the points are horizontal, then we look at the spread in
2567 // x-direction. We really should continue to walk away from the degeneracy until
2568 // there is a divergence.
2569 if (0 == cross && pts[prev].fY == pts[index].fY && pts[next].fY == pts[index].fY) {
2570 // construct the subtract so we get the correct Direction below
2571 cross = pts[index].fX - pts[next].fX;
reed@google.com188bfcf2012-01-17 18:26:38 +00002572 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002573 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002574
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002575 if (cross) {
2576 // record our best guess so far
2577 ymax = pts[index].fY;
2578 ymaxCross = cross;
reed@google.com69a99432012-01-10 18:00:10 +00002579 }
2580 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002581 if (ymaxCross) {
2582 crossToDir(ymaxCross, dir);
reed026beb52015-06-10 14:23:15 -07002583 path.fFirstDirection = *dir;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002584 return true;
2585 } else {
2586 return false;
2587 }
reed@google.comac8543f2012-01-30 20:51:25 +00002588}
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002589
2590///////////////////////////////////////////////////////////////////////////////
2591
caryclark9aacd902015-12-14 08:38:09 -08002592static bool between(SkScalar a, SkScalar b, SkScalar c) {
2593 SkASSERT(((a <= b && b <= c) || (a >= b && b >= c)) == ((a - b) * (c - b) <= 0)
2594 || (SkScalarNearlyZero(a) && SkScalarNearlyZero(b) && SkScalarNearlyZero(c)));
2595 return (a - b) * (c - b) <= 0;
2596}
2597
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002598static SkScalar eval_cubic_coeff(SkScalar A, SkScalar B, SkScalar C,
2599 SkScalar D, SkScalar t) {
2600 return SkScalarMulAdd(SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C), t, D);
2601}
2602
2603static SkScalar eval_cubic_pts(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2604 SkScalar t) {
2605 SkScalar A = c3 + 3*(c1 - c2) - c0;
2606 SkScalar B = 3*(c2 - c1 - c1 + c0);
2607 SkScalar C = 3*(c1 - c0);
2608 SkScalar D = c0;
2609 return eval_cubic_coeff(A, B, C, D, t);
2610}
2611
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002612template <size_t N> static void find_minmax(const SkPoint pts[],
2613 SkScalar* minPtr, SkScalar* maxPtr) {
2614 SkScalar min, max;
2615 min = max = pts[0].fX;
2616 for (size_t i = 1; i < N; ++i) {
2617 min = SkMinScalar(min, pts[i].fX);
2618 max = SkMaxScalar(max, pts[i].fX);
2619 }
2620 *minPtr = min;
2621 *maxPtr = max;
2622}
2623
caryclark9cb5d752015-12-18 04:35:24 -08002624static bool checkOnCurve(SkScalar x, SkScalar y, const SkPoint& start, const SkPoint& end) {
2625 if (start.fY == end.fY) {
2626 return between(start.fX, x, end.fX) && x != end.fX;
2627 } else {
2628 return x == start.fX && y == start.fY;
2629 }
2630}
2631
caryclark9aacd902015-12-14 08:38:09 -08002632static int winding_mono_cubic(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
caryclark9cb5d752015-12-18 04:35:24 -08002633 SkScalar y0 = pts[0].fY;
2634 SkScalar y3 = pts[3].fY;
2635
2636 int dir = 1;
2637 if (y0 > y3) {
2638 SkTSwap(y0, y3);
2639 dir = -1;
2640 }
2641 if (y < y0 || y > y3) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002642 return 0;
2643 }
caryclark9cb5d752015-12-18 04:35:24 -08002644 if (checkOnCurve(x, y, pts[0], pts[3])) {
2645 *onCurveCount += 1;
caryclark9aacd902015-12-14 08:38:09 -08002646 return 0;
2647 }
caryclark9cb5d752015-12-18 04:35:24 -08002648 if (y == y3) {
2649 return 0;
2650 }
2651
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002652 // quickreject or quickaccept
2653 SkScalar min, max;
2654 find_minmax<4>(pts, &min, &max);
2655 if (x < min) {
2656 return 0;
2657 }
2658 if (x > max) {
2659 return dir;
2660 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002661
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002662 // compute the actual x(t) value
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002663 SkScalar t;
caryclark9aacd902015-12-14 08:38:09 -08002664 SkAssertResult(SkCubicClipper::ChopMonoAtY(pts, y, &t));
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002665 SkScalar xt = eval_cubic_pts(pts[0].fX, pts[1].fX, pts[2].fX, pts[3].fX, t);
caryclark9aacd902015-12-14 08:38:09 -08002666 if (SkScalarNearlyEqual(xt, x)) {
2667 if (x != pts[3].fX || y != pts[3].fY) { // don't test end points; they're start points
2668 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08002669 return 0;
caryclark9aacd902015-12-14 08:38:09 -08002670 }
2671 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002672 return xt < x ? dir : 0;
2673}
2674
caryclark9aacd902015-12-14 08:38:09 -08002675static int winding_cubic(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002676 SkPoint dst[10];
2677 int n = SkChopCubicAtYExtrema(pts, dst);
2678 int w = 0;
2679 for (int i = 0; i <= n; ++i) {
caryclark9aacd902015-12-14 08:38:09 -08002680 w += winding_mono_cubic(&dst[i * 3], x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002681 }
2682 return w;
2683}
2684
caryclark9aacd902015-12-14 08:38:09 -08002685static double conic_eval_numerator(const SkScalar src[], SkScalar w, SkScalar t) {
2686 SkASSERT(src);
2687 SkASSERT(t >= 0 && t <= 1);
2688 SkScalar src2w = src[2] * w;
2689 SkScalar C = src[0];
2690 SkScalar A = src[4] - 2 * src2w + C;
2691 SkScalar B = 2 * (src2w - C);
2692 return (A * t + B) * t + C;
2693}
2694
2695
2696static double conic_eval_denominator(SkScalar w, SkScalar t) {
2697 SkScalar B = 2 * (w - 1);
2698 SkScalar C = 1;
2699 SkScalar A = -B;
2700 return (A * t + B) * t + C;
2701}
2702
2703static int winding_mono_conic(const SkConic& conic, SkScalar x, SkScalar y, int* onCurveCount) {
2704 const SkPoint* pts = conic.fPts;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002705 SkScalar y0 = pts[0].fY;
2706 SkScalar y2 = pts[2].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002707
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002708 int dir = 1;
2709 if (y0 > y2) {
2710 SkTSwap(y0, y2);
2711 dir = -1;
2712 }
caryclark9aacd902015-12-14 08:38:09 -08002713 if (y < y0 || y > y2) {
2714 return 0;
2715 }
caryclark9cb5d752015-12-18 04:35:24 -08002716 if (checkOnCurve(x, y, pts[0], pts[2])) {
2717 *onCurveCount += 1;
2718 return 0;
2719 }
caryclark9aacd902015-12-14 08:38:09 -08002720 if (y == y2) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002721 return 0;
2722 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002723
caryclark9aacd902015-12-14 08:38:09 -08002724 SkScalar roots[2];
2725 SkScalar A = pts[2].fY;
2726 SkScalar B = pts[1].fY * conic.fW - y * conic.fW + y;
2727 SkScalar C = pts[0].fY;
2728 A += C - 2 * B; // A = a + c - 2*(b*w - yCept*w + yCept)
2729 B -= C; // B = b*w - w * yCept + yCept - a
2730 C -= y;
2731 int n = SkFindUnitQuadRoots(A, 2 * B, C, roots);
2732 SkASSERT(n <= 1);
2733 SkScalar xt;
2734 if (0 == n) {
caryclark9cb5d752015-12-18 04:35:24 -08002735 // zero roots are returned only when y0 == y
2736 // Need [0] if dir == 1
2737 // and [2] if dir == -1
2738 xt = pts[1 - dir].fX;
caryclark9aacd902015-12-14 08:38:09 -08002739 } else {
2740 SkScalar t = roots[0];
2741 xt = conic_eval_numerator(&pts[0].fX, conic.fW, t) / conic_eval_denominator(conic.fW, t);
2742 }
2743 if (SkScalarNearlyEqual(xt, x)) {
2744 if (x != pts[2].fX || y != pts[2].fY) { // don't test end points; they're start points
2745 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08002746 return 0;
caryclark9aacd902015-12-14 08:38:09 -08002747 }
2748 }
2749 return xt < x ? dir : 0;
2750}
2751
2752static bool is_mono_quad(SkScalar y0, SkScalar y1, SkScalar y2) {
2753 // return SkScalarSignAsInt(y0 - y1) + SkScalarSignAsInt(y1 - y2) != 0;
2754 if (y0 == y1) {
2755 return true;
2756 }
2757 if (y0 < y1) {
2758 return y1 <= y2;
2759 } else {
2760 return y1 >= y2;
2761 }
2762}
2763
2764static int winding_conic(const SkPoint pts[], SkScalar x, SkScalar y, SkScalar weight,
2765 int* onCurveCount) {
2766 SkConic conic(pts, weight);
caryclark9aacd902015-12-14 08:38:09 -08002767 SkConic chopped[2];
caryclark1e1e5092016-01-06 08:27:44 -08002768 // If the data points are very large, the conic may not be monotonic but may also
2769 // fail to chop. Then, the chopper does not split the original conic in two.
caryclarke114dd62016-01-06 08:15:44 -08002770 bool isMono = is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY) || !conic.chopAtYExtrema(chopped);
2771 int w = winding_mono_conic(isMono ? conic : chopped[0], x, y, onCurveCount);
2772 if (!isMono) {
caryclark9aacd902015-12-14 08:38:09 -08002773 w += winding_mono_conic(chopped[1], x, y, onCurveCount);
2774 }
2775 return w;
2776}
2777
2778static int winding_mono_quad(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
2779 SkScalar y0 = pts[0].fY;
2780 SkScalar y2 = pts[2].fY;
2781
2782 int dir = 1;
2783 if (y0 > y2) {
2784 SkTSwap(y0, y2);
2785 dir = -1;
2786 }
2787 if (y < y0 || y > y2) {
2788 return 0;
2789 }
caryclark9cb5d752015-12-18 04:35:24 -08002790 if (checkOnCurve(x, y, pts[0], pts[2])) {
2791 *onCurveCount += 1;
2792 return 0;
2793 }
caryclark9aacd902015-12-14 08:38:09 -08002794 if (y == y2) {
caryclark9aacd902015-12-14 08:38:09 -08002795 return 0;
2796 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002797 // bounds check on X (not required. is it faster?)
2798#if 0
2799 if (pts[0].fX > x && pts[1].fX > x && pts[2].fX > x) {
2800 return 0;
2801 }
2802#endif
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002803
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002804 SkScalar roots[2];
2805 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
2806 2 * (pts[1].fY - pts[0].fY),
2807 pts[0].fY - y,
2808 roots);
2809 SkASSERT(n <= 1);
2810 SkScalar xt;
2811 if (0 == n) {
caryclark9cb5d752015-12-18 04:35:24 -08002812 // zero roots are returned only when y0 == y
2813 // Need [0] if dir == 1
2814 // and [2] if dir == -1
2815 xt = pts[1 - dir].fX;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002816 } else {
2817 SkScalar t = roots[0];
2818 SkScalar C = pts[0].fX;
2819 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
2820 SkScalar B = 2 * (pts[1].fX - C);
2821 xt = SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C);
2822 }
caryclark9aacd902015-12-14 08:38:09 -08002823 if (SkScalarNearlyEqual(xt, x)) {
2824 if (x != pts[2].fX || y != pts[2].fY) { // don't test end points; they're start points
2825 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08002826 return 0;
caryclark9aacd902015-12-14 08:38:09 -08002827 }
2828 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002829 return xt < x ? dir : 0;
2830}
2831
caryclark9aacd902015-12-14 08:38:09 -08002832static int winding_quad(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002833 SkPoint dst[5];
2834 int n = 0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002835
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002836 if (!is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY)) {
2837 n = SkChopQuadAtYExtrema(pts, dst);
2838 pts = dst;
2839 }
caryclark9aacd902015-12-14 08:38:09 -08002840 int w = winding_mono_quad(pts, x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002841 if (n > 0) {
caryclark9aacd902015-12-14 08:38:09 -08002842 w += winding_mono_quad(&pts[2], x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002843 }
2844 return w;
2845}
2846
caryclark9aacd902015-12-14 08:38:09 -08002847static int winding_line(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002848 SkScalar x0 = pts[0].fX;
2849 SkScalar y0 = pts[0].fY;
2850 SkScalar x1 = pts[1].fX;
2851 SkScalar y1 = pts[1].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002852
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002853 SkScalar dy = y1 - y0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002854
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002855 int dir = 1;
2856 if (y0 > y1) {
2857 SkTSwap(y0, y1);
2858 dir = -1;
2859 }
caryclark9aacd902015-12-14 08:38:09 -08002860 if (y < y0 || y > y1) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002861 return 0;
2862 }
caryclark9cb5d752015-12-18 04:35:24 -08002863 if (checkOnCurve(x, y, pts[0], pts[1])) {
2864 *onCurveCount += 1;
2865 return 0;
2866 }
2867 if (y == y1) {
caryclark9aacd902015-12-14 08:38:09 -08002868 return 0;
2869 }
2870 SkScalar cross = SkScalarMul(x1 - x0, y - pts[0].fY) - SkScalarMul(dy, x - x0);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002871
caryclark9aacd902015-12-14 08:38:09 -08002872 if (!cross) {
fsc91065d2015-12-17 09:03:27 -08002873 // zero cross means the point is on the line, and since the case where
2874 // y of the query point is at the end point is handled above, we can be
2875 // sure that we're on the line (excluding the end point) here
caryclark9cb5d752015-12-18 04:35:24 -08002876 if (x != x1 || y != pts[1].fY) {
2877 *onCurveCount += 1;
2878 }
caryclark9aacd902015-12-14 08:38:09 -08002879 dir = 0;
2880 } else if (SkScalarSignAsInt(cross) == dir) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002881 dir = 0;
2882 }
2883 return dir;
2884}
2885
caryclark9aacd902015-12-14 08:38:09 -08002886static void tangent_cubic(const SkPoint pts[], SkScalar x, SkScalar y,
2887 SkTDArray<SkVector>* tangents) {
2888 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)
2889 && !between(pts[2].fY, y, pts[3].fY)) {
2890 return;
2891 }
2892 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)
2893 && !between(pts[2].fX, x, pts[3].fX)) {
2894 return;
2895 }
2896 SkPoint dst[10];
2897 int n = SkChopCubicAtYExtrema(pts, dst);
2898 for (int i = 0; i <= n; ++i) {
2899 SkPoint* c = &dst[i * 3];
2900 SkScalar t;
2901 SkAssertResult(SkCubicClipper::ChopMonoAtY(c, y, &t));
2902 SkScalar xt = eval_cubic_pts(c[0].fX, c[1].fX, c[2].fX, c[3].fX, t);
2903 if (!SkScalarNearlyEqual(x, xt)) {
2904 continue;
2905 }
2906 SkVector tangent;
2907 SkEvalCubicAt(c, t, nullptr, &tangent, nullptr);
2908 tangents->push(tangent);
2909 }
2910}
2911
2912static void tangent_conic(const SkPoint pts[], SkScalar x, SkScalar y, SkScalar w,
2913 SkTDArray<SkVector>* tangents) {
2914 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)) {
2915 return;
2916 }
2917 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)) {
2918 return;
2919 }
2920 SkScalar roots[2];
2921 SkScalar A = pts[2].fY;
2922 SkScalar B = pts[1].fY * w - y * w + y;
2923 SkScalar C = pts[0].fY;
2924 A += C - 2 * B; // A = a + c - 2*(b*w - yCept*w + yCept)
2925 B -= C; // B = b*w - w * yCept + yCept - a
2926 C -= y;
2927 int n = SkFindUnitQuadRoots(A, 2 * B, C, roots);
2928 for (int index = 0; index < n; ++index) {
2929 SkScalar t = roots[index];
2930 SkScalar xt = conic_eval_numerator(&pts[0].fX, w, t) / conic_eval_denominator(w, t);
2931 if (!SkScalarNearlyEqual(x, xt)) {
2932 continue;
2933 }
2934 SkConic conic(pts, w);
2935 tangents->push(conic.evalTangentAt(t));
2936 }
2937}
2938
2939static void tangent_quad(const SkPoint pts[], SkScalar x, SkScalar y,
2940 SkTDArray<SkVector>* tangents) {
2941 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)) {
2942 return;
2943 }
2944 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)) {
2945 return;
2946 }
2947 SkScalar roots[2];
2948 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
2949 2 * (pts[1].fY - pts[0].fY),
2950 pts[0].fY - y,
2951 roots);
2952 for (int index = 0; index < n; ++index) {
2953 SkScalar t = roots[index];
2954 SkScalar C = pts[0].fX;
2955 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
2956 SkScalar B = 2 * (pts[1].fX - C);
2957 SkScalar xt = (A * t + B) * t + C;
2958 if (!SkScalarNearlyEqual(x, xt)) {
2959 continue;
2960 }
2961 tangents->push(SkEvalQuadTangentAt(pts, t));
2962 }
2963}
2964
2965static void tangent_line(const SkPoint pts[], SkScalar x, SkScalar y,
2966 SkTDArray<SkVector>* tangents) {
2967 SkScalar y0 = pts[0].fY;
2968 SkScalar y1 = pts[1].fY;
2969 if (!between(y0, y, y1)) {
2970 return;
2971 }
2972 SkScalar x0 = pts[0].fX;
2973 SkScalar x1 = pts[1].fX;
2974 if (!between(x0, x, x1)) {
2975 return;
2976 }
2977 SkScalar dx = x1 - x0;
2978 SkScalar dy = y1 - y0;
2979 if (!SkScalarNearlyEqual((x - x0) * dy, dx * (y - y0))) {
2980 return;
2981 }
2982 SkVector v;
2983 v.set(dx, dy);
2984 tangents->push(v);
2985}
2986
reed@google.com4db592c2013-10-30 17:39:43 +00002987static bool contains_inclusive(const SkRect& r, SkScalar x, SkScalar y) {
2988 return r.fLeft <= x && x <= r.fRight && r.fTop <= y && y <= r.fBottom;
2989}
2990
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002991bool SkPath::contains(SkScalar x, SkScalar y) const {
2992 bool isInverse = this->isInverseFillType();
2993 if (this->isEmpty()) {
2994 return isInverse;
2995 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002996
reed@google.com4db592c2013-10-30 17:39:43 +00002997 if (!contains_inclusive(this->getBounds(), x, y)) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002998 return isInverse;
2999 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003000
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003001 SkPath::Iter iter(*this, true);
3002 bool done = false;
3003 int w = 0;
caryclark9aacd902015-12-14 08:38:09 -08003004 int onCurveCount = 0;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003005 do {
3006 SkPoint pts[4];
3007 switch (iter.next(pts, false)) {
3008 case SkPath::kMove_Verb:
3009 case SkPath::kClose_Verb:
3010 break;
3011 case SkPath::kLine_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003012 w += winding_line(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003013 break;
3014 case SkPath::kQuad_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003015 w += winding_quad(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003016 break;
reed@google.com277c3f82013-05-31 15:17:50 +00003017 case SkPath::kConic_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003018 w += winding_conic(pts, x, y, iter.conicWeight(), &onCurveCount);
reed@google.com277c3f82013-05-31 15:17:50 +00003019 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003020 case SkPath::kCubic_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003021 w += winding_cubic(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003022 break;
3023 case SkPath::kDone_Verb:
3024 done = true;
3025 break;
reed@google.com277c3f82013-05-31 15:17:50 +00003026 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003027 } while (!done);
caryclark9aacd902015-12-14 08:38:09 -08003028 bool evenOddFill = SkPath::kEvenOdd_FillType == this->getFillType()
3029 || SkPath::kInverseEvenOdd_FillType == this->getFillType();
3030 if (evenOddFill) {
3031 w &= 1;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003032 }
caryclark9aacd902015-12-14 08:38:09 -08003033 if (w) {
3034 return !isInverse;
3035 }
3036 if (onCurveCount <= 1) {
3037 return SkToBool(onCurveCount) ^ isInverse;
3038 }
3039 if ((onCurveCount & 1) || evenOddFill) {
3040 return SkToBool(onCurveCount & 1) ^ isInverse;
3041 }
3042 // If the point touches an even number of curves, and the fill is winding, check for
3043 // coincidence. Count coincidence as places where the on curve points have identical tangents.
3044 iter.setPath(*this, true);
caryclark9cb5d752015-12-18 04:35:24 -08003045 done = false;
caryclark9aacd902015-12-14 08:38:09 -08003046 SkTDArray<SkVector> tangents;
3047 do {
3048 SkPoint pts[4];
3049 int oldCount = tangents.count();
3050 switch (iter.next(pts, false)) {
3051 case SkPath::kMove_Verb:
3052 case SkPath::kClose_Verb:
3053 break;
3054 case SkPath::kLine_Verb:
3055 tangent_line(pts, x, y, &tangents);
3056 break;
3057 case SkPath::kQuad_Verb:
3058 tangent_quad(pts, x, y, &tangents);
3059 break;
3060 case SkPath::kConic_Verb:
3061 tangent_conic(pts, x, y, iter.conicWeight(), &tangents);
3062 break;
3063 case SkPath::kCubic_Verb:
3064 tangent_cubic(pts, x, y, &tangents);
3065 break;
3066 case SkPath::kDone_Verb:
3067 done = true;
3068 break;
3069 }
3070 if (tangents.count() > oldCount) {
3071 int last = tangents.count() - 1;
3072 const SkVector& tangent = tangents[last];
3073 if (SkScalarNearlyZero(tangent.lengthSqd())) {
3074 tangents.remove(last);
3075 } else {
3076 for (int index = 0; index < last; ++index) {
3077 const SkVector& test = tangents[index];
3078 if (SkScalarNearlyZero(test.cross(tangent))
caryclark9cb5d752015-12-18 04:35:24 -08003079 && SkScalarSignAsInt(tangent.fX * test.fX) <= 0
3080 && SkScalarSignAsInt(tangent.fY * test.fY) <= 0) {
caryclark9aacd902015-12-14 08:38:09 -08003081 tangents.remove(last);
3082 tangents.removeShuffle(index);
3083 break;
3084 }
3085 }
3086 }
3087 }
3088 } while (!done);
3089 return SkToBool(tangents.count()) ^ isInverse;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003090}
fmalitaaa0df4e2015-12-01 09:13:23 -08003091
3092int SkPath::ConvertConicToQuads(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2,
3093 SkScalar w, SkPoint pts[], int pow2) {
3094 const SkConic conic(p0, p1, p2, w);
3095 return conic.chopIntoQuadsPOW2(pts, pow2);
3096}