blob: 10e80a47a33804476650c6a99c9d0248a609f94a [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
caryclark8e7b19d2016-02-18 04:11:48 -0800196bool SkPath::isInterpolatable(const SkPath& compare) const {
197 int count = fPathRef->countVerbs();
198 if (count != compare.fPathRef->countVerbs()) {
199 return false;
200 }
201 if (!count) {
202 return true;
203 }
204 if (memcmp(fPathRef->verbsMemBegin(), compare.fPathRef->verbsMemBegin(),
205 count)) {
206 return false;
207 }
208 return !SkToBool(memcmp(fPathRef->conicWeights(), compare.fPathRef->conicWeights(),
209 fPathRef->countWeights() * sizeof(*fPathRef->conicWeights())));
210}
211
212bool SkPath::interpolate(const SkPath& ending, SkScalar weight, SkPath* out) const {
213 int verbCount = fPathRef->countVerbs();
214 if (verbCount != ending.fPathRef->countVerbs()) {
215 return false;
216 }
caryclarka1105382016-02-18 06:13:25 -0800217 if (!verbCount) {
218 return true;
219 }
caryclark8e7b19d2016-02-18 04:11:48 -0800220 out->reset();
221 out->addPath(*this);
222 fPathRef->interpolate(*ending.fPathRef, weight, out->fPathRef);
223 return true;
224}
225
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000226static inline bool check_edge_against_rect(const SkPoint& p0,
227 const SkPoint& p1,
228 const SkRect& rect,
reed026beb52015-06-10 14:23:15 -0700229 SkPathPriv::FirstDirection dir) {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000230 const SkPoint* edgeBegin;
231 SkVector v;
reed026beb52015-06-10 14:23:15 -0700232 if (SkPathPriv::kCW_FirstDirection == dir) {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000233 v = p1 - p0;
234 edgeBegin = &p0;
235 } else {
236 v = p0 - p1;
237 edgeBegin = &p1;
238 }
239 if (v.fX || v.fY) {
240 // check the cross product of v with the vec from edgeBegin to each rect corner
241 SkScalar yL = SkScalarMul(v.fY, rect.fLeft - edgeBegin->fX);
242 SkScalar xT = SkScalarMul(v.fX, rect.fTop - edgeBegin->fY);
243 SkScalar yR = SkScalarMul(v.fY, rect.fRight - edgeBegin->fX);
244 SkScalar xB = SkScalarMul(v.fX, rect.fBottom - edgeBegin->fY);
245 if ((xT < yL) || (xT < yR) || (xB < yL) || (xB < yR)) {
246 return false;
247 }
248 }
249 return true;
250}
251
252bool SkPath::conservativelyContainsRect(const SkRect& rect) const {
253 // This only handles non-degenerate convex paths currently.
254 if (kConvex_Convexity != this->getConvexity()) {
255 return false;
256 }
skia.committer@gmail.comcec8de62012-11-14 02:01:22 +0000257
reed026beb52015-06-10 14:23:15 -0700258 SkPathPriv::FirstDirection direction;
259 if (!SkPathPriv::CheapComputeFirstDirection(*this, &direction)) {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000260 return false;
261 }
262
263 SkPoint firstPt;
264 SkPoint prevPt;
265 RawIter iter(*this);
266 SkPath::Verb verb;
267 SkPoint pts[4];
268 SkDEBUGCODE(int moveCnt = 0;)
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000269 SkDEBUGCODE(int segmentCount = 0;)
270 SkDEBUGCODE(int closeCount = 0;)
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000271
272 while ((verb = iter.next(pts)) != kDone_Verb) {
273 int nextPt = -1;
274 switch (verb) {
275 case kMove_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000276 SkASSERT(!segmentCount && !closeCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000277 SkDEBUGCODE(++moveCnt);
278 firstPt = prevPt = pts[0];
279 break;
280 case kLine_Verb:
281 nextPt = 1;
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000282 SkASSERT(moveCnt && !closeCount);
283 SkDEBUGCODE(++segmentCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000284 break;
285 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000286 case kConic_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000287 SkASSERT(moveCnt && !closeCount);
288 SkDEBUGCODE(++segmentCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000289 nextPt = 2;
290 break;
291 case kCubic_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000292 SkASSERT(moveCnt && !closeCount);
293 SkDEBUGCODE(++segmentCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000294 nextPt = 3;
295 break;
296 case kClose_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000297 SkDEBUGCODE(++closeCount;)
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000298 break;
299 default:
300 SkDEBUGFAIL("unknown verb");
301 }
302 if (-1 != nextPt) {
reed220f9262014-12-17 08:21:04 -0800303 if (SkPath::kConic_Verb == verb) {
304 SkConic orig;
305 orig.set(pts, iter.conicWeight());
306 SkPoint quadPts[5];
307 int count = orig.chopIntoQuadsPOW2(quadPts, 1);
djsollenf2b340f2016-01-29 08:51:04 -0800308 SkASSERT_RELEASE(2 == count);
reed220f9262014-12-17 08:21:04 -0800309
310 if (!check_edge_against_rect(quadPts[0], quadPts[2], rect, direction)) {
311 return false;
312 }
313 if (!check_edge_against_rect(quadPts[2], quadPts[4], rect, direction)) {
314 return false;
315 }
316 } else {
317 if (!check_edge_against_rect(prevPt, pts[nextPt], rect, direction)) {
318 return false;
319 }
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000320 }
321 prevPt = pts[nextPt];
322 }
323 }
324
325 return check_edge_against_rect(prevPt, firstPt, rect, direction);
326}
327
robertphillips@google.com7101abe2013-10-29 22:45:37 +0000328uint32_t SkPath::getGenerationID() const {
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000329 uint32_t genID = fPathRef->genID();
djsollen523cda32015-02-17 08:06:32 -0800330#ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000331 SkASSERT((unsigned)fFillType < (1 << (32 - kPathRefGenIDBitCnt)));
332 genID |= static_cast<uint32_t>(fFillType) << kPathRefGenIDBitCnt;
333#endif
334 return genID;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000335}
djsollen@google.come63793a2012-03-21 15:39:03 +0000336
reed@android.com8a1c16f2008-12-17 15:59:43 +0000337void SkPath::reset() {
338 SkDEBUGCODE(this->validate();)
339
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000340 fPathRef.reset(SkPathRef::CreateEmpty());
bungeman@google.coma5809a32013-06-21 15:13:34 +0000341 this->resetFields();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000342}
343
344void SkPath::rewind() {
345 SkDEBUGCODE(this->validate();)
346
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000347 SkPathRef::Rewind(&fPathRef);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000348 this->resetFields();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000349}
350
fsb1475b02016-01-20 09:51:07 -0800351bool SkPath::isLastContourClosed() const {
352 int verbCount = fPathRef->countVerbs();
353 if (0 == verbCount) {
354 return false;
355 }
356 return kClose_Verb == fPathRef->atVerb(verbCount - 1);
357}
358
reed@google.com7e6c4d12012-05-10 14:05:43 +0000359bool SkPath::isLine(SkPoint line[2]) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000360 int verbCount = fPathRef->countVerbs();
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000361
commit-bot@chromium.orga62efcc2013-08-05 13:23:13 +0000362 if (2 == verbCount) {
363 SkASSERT(kMove_Verb == fPathRef->atVerb(0));
364 if (kLine_Verb == fPathRef->atVerb(1)) {
365 SkASSERT(2 == fPathRef->countPoints());
reed@google.com7e6c4d12012-05-10 14:05:43 +0000366 if (line) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000367 const SkPoint* pts = fPathRef->points();
reed@google.com7e6c4d12012-05-10 14:05:43 +0000368 line[0] = pts[0];
369 line[1] = pts[1];
370 }
371 return true;
372 }
373 }
374 return false;
375}
376
caryclark@google.comf1316942011-07-26 19:54:45 +0000377/*
378 Determines if path is a rect by keeping track of changes in direction
379 and looking for a loop either clockwise or counterclockwise.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000380
caryclark@google.comf1316942011-07-26 19:54:45 +0000381 The direction is computed such that:
382 0: vertical up
caryclark@google.comf68154a2012-11-21 15:18:06 +0000383 1: horizontal left
caryclark@google.comf1316942011-07-26 19:54:45 +0000384 2: vertical down
caryclark@google.comf68154a2012-11-21 15:18:06 +0000385 3: horizontal right
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000386
caryclark@google.comf1316942011-07-26 19:54:45 +0000387A rectangle cycles up/right/down/left or up/left/down/right.
388
389The test fails if:
390 The path is closed, and followed by a line.
391 A second move creates a new endpoint.
392 A diagonal line is parsed.
393 There's more than four changes of direction.
394 There's a discontinuity on the line (e.g., a move in the middle)
395 The line reverses direction.
caryclark@google.comf1316942011-07-26 19:54:45 +0000396 The path contains a quadratic or cubic.
397 The path contains fewer than four points.
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000398 *The rectangle doesn't complete a cycle.
399 *The final point isn't equal to the first point.
400
401 *These last two conditions we relax if we have a 3-edge path that would
402 form a rectangle if it were closed (as we do when we fill a path)
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000403
caryclark@google.comf1316942011-07-26 19:54:45 +0000404It's OK if the path has:
405 Several colinear line segments composing a rectangle side.
406 Single points on the rectangle side.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000407
caryclark@google.comf1316942011-07-26 19:54:45 +0000408The direction takes advantage of the corners found since opposite sides
409must travel in opposite directions.
410
411FIXME: Allow colinear quads and cubics to be treated like lines.
412FIXME: If the API passes fill-only, return true if the filled stroke
413 is a rectangle, though the caller failed to close the path.
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000414
415 first,last,next direction state-machine:
416 0x1 is set if the segment is horizontal
417 0x2 is set if the segment is moving to the right or down
418 thus:
419 two directions are opposites iff (dirA ^ dirB) == 0x2
420 two directions are perpendicular iff (dirA ^ dirB) == 0x1
skia.committer@gmail.comf5e67c12014-01-16 07:01:48 +0000421
caryclark@google.comf1316942011-07-26 19:54:45 +0000422 */
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000423static int rect_make_dir(SkScalar dx, SkScalar dy) {
424 return ((0 != dx) << 0) | ((dx > 0 || dy > 0) << 1);
425}
caryclark@google.comf68154a2012-11-21 15:18:06 +0000426bool SkPath::isRectContour(bool allowPartial, int* currVerb, const SkPoint** ptsPtr,
427 bool* isClosed, Direction* direction) const {
caryclark@google.comf1316942011-07-26 19:54:45 +0000428 int corners = 0;
429 SkPoint first, last;
caryclark@google.com56f233a2012-11-19 13:06:06 +0000430 const SkPoint* pts = *ptsPtr;
halcanary96fcdcc2015-08-27 07:41:13 -0700431 const SkPoint* savePts = nullptr;
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000432 first.set(0, 0);
433 last.set(0, 0);
434 int firstDirection = 0;
435 int lastDirection = 0;
436 int nextDirection = 0;
437 bool closedOrMoved = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000438 bool autoClose = false;
caryclark95bc5f32015-04-08 08:34:15 -0700439 bool insertClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000440 int verbCnt = fPathRef->countVerbs();
caryclark@google.com56f233a2012-11-19 13:06:06 +0000441 while (*currVerb < verbCnt && (!allowPartial || !autoClose)) {
caryclark95bc5f32015-04-08 08:34:15 -0700442 uint8_t verb = insertClose ? (uint8_t) kClose_Verb : fPathRef->atVerb(*currVerb);
443 switch (verb) {
caryclark@google.comf1316942011-07-26 19:54:45 +0000444 case kClose_Verb:
caryclark@google.com56f233a2012-11-19 13:06:06 +0000445 savePts = pts;
446 pts = *ptsPtr;
caryclark@google.comf1316942011-07-26 19:54:45 +0000447 autoClose = true;
caryclark95bc5f32015-04-08 08:34:15 -0700448 insertClose = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000449 case kLine_Verb: {
450 SkScalar left = last.fX;
451 SkScalar top = last.fY;
452 SkScalar right = pts->fX;
453 SkScalar bottom = pts->fY;
454 ++pts;
455 if (left != right && top != bottom) {
456 return false; // diagonal
457 }
458 if (left == right && top == bottom) {
459 break; // single point on side OK
460 }
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000461 nextDirection = rect_make_dir(right - left, bottom - top);
caryclark@google.comf1316942011-07-26 19:54:45 +0000462 if (0 == corners) {
463 firstDirection = nextDirection;
464 first = last;
465 last = pts[-1];
466 corners = 1;
467 closedOrMoved = false;
468 break;
469 }
470 if (closedOrMoved) {
471 return false; // closed followed by a line
472 }
caryclark@google.combfe90372012-11-21 13:56:20 +0000473 if (autoClose && nextDirection == firstDirection) {
474 break; // colinear with first
475 }
caryclark@google.comf1316942011-07-26 19:54:45 +0000476 closedOrMoved = autoClose;
477 if (lastDirection != nextDirection) {
478 if (++corners > 4) {
479 return false; // too many direction changes
480 }
481 }
482 last = pts[-1];
483 if (lastDirection == nextDirection) {
484 break; // colinear segment
485 }
486 // Possible values for corners are 2, 3, and 4.
487 // When corners == 3, nextDirection opposes firstDirection.
488 // Otherwise, nextDirection at corner 2 opposes corner 4.
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000489 int turn = firstDirection ^ (corners - 1);
caryclark@google.comf1316942011-07-26 19:54:45 +0000490 int directionCycle = 3 == corners ? 0 : nextDirection ^ turn;
491 if ((directionCycle ^ turn) != nextDirection) {
492 return false; // direction didn't follow cycle
493 }
494 break;
495 }
496 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000497 case kConic_Verb:
caryclark@google.comf1316942011-07-26 19:54:45 +0000498 case kCubic_Verb:
499 return false; // quadratic, cubic not allowed
500 case kMove_Verb:
caryclark95bc5f32015-04-08 08:34:15 -0700501 if (allowPartial && !autoClose && firstDirection) {
502 insertClose = true;
503 *currVerb -= 1; // try move again afterwards
504 goto addMissingClose;
505 }
caryclark@google.comf1316942011-07-26 19:54:45 +0000506 last = *pts++;
507 closedOrMoved = true;
508 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000509 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000510 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000511 break;
caryclark@google.comf1316942011-07-26 19:54:45 +0000512 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000513 *currVerb += 1;
caryclark@google.comf1316942011-07-26 19:54:45 +0000514 lastDirection = nextDirection;
caryclark95bc5f32015-04-08 08:34:15 -0700515addMissingClose:
516 ;
caryclark@google.comf1316942011-07-26 19:54:45 +0000517 }
518 // Success if 4 corners and first point equals last
caryclark@google.combfe90372012-11-21 13:56:20 +0000519 bool result = 4 == corners && (first == last || autoClose);
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000520 if (!result) {
521 // check if we are just an incomplete rectangle, in which case we can
522 // return true, but not claim to be closed.
523 // e.g.
524 // 3 sided rectangle
525 // 4 sided but the last edge is not long enough to reach the start
526 //
527 SkScalar closeX = first.x() - last.x();
528 SkScalar closeY = first.y() - last.y();
529 if (closeX && closeY) {
530 return false; // we're diagonal, abort (can we ever reach this?)
531 }
532 int closeDirection = rect_make_dir(closeX, closeY);
533 // make sure the close-segment doesn't double-back on itself
534 if (3 == corners || (4 == corners && closeDirection == lastDirection)) {
535 result = true;
536 autoClose = false; // we are not closed
537 }
538 }
caryclark@google.combfe90372012-11-21 13:56:20 +0000539 if (savePts) {
540 *ptsPtr = savePts;
541 }
caryclark@google.comf68154a2012-11-21 15:18:06 +0000542 if (result && isClosed) {
543 *isClosed = autoClose;
544 }
545 if (result && direction) {
sugoi@google.com12b4e272012-12-06 20:13:11 +0000546 *direction = firstDirection == ((lastDirection + 1) & 3) ? kCCW_Direction : kCW_Direction;
caryclark@google.comf68154a2012-11-21 15:18:06 +0000547 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000548 return result;
549}
550
robertphillips4f662e62014-12-29 14:06:51 -0800551bool SkPath::isRect(SkRect* rect, bool* isClosed, Direction* direction) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000552 SkDEBUGCODE(this->validate();)
553 int currVerb = 0;
554 const SkPoint* pts = fPathRef->points();
robertphillipsfe7c4272014-12-29 11:36:39 -0800555 const SkPoint* first = pts;
robertphillips4f662e62014-12-29 14:06:51 -0800556 if (!this->isRectContour(false, &currVerb, &pts, isClosed, direction)) {
robertphillipsfe7c4272014-12-29 11:36:39 -0800557 return false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000558 }
robertphillipsfe7c4272014-12-29 11:36:39 -0800559 if (rect) {
robertphillips4f662e62014-12-29 14:06:51 -0800560 int32_t num = SkToS32(pts - first);
561 if (num) {
562 rect->set(first, num);
robertphillipsfe7c4272014-12-29 11:36:39 -0800563 } else {
564 // 'pts' isn't updated for open rects
565 *rect = this->getBounds();
566 }
567 }
568 return true;
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000569}
skia.committer@gmail.com020b25b2013-06-22 07:00:58 +0000570
caryclark95bc5f32015-04-08 08:34:15 -0700571bool SkPath::isNestedFillRects(SkRect rects[2], Direction dirs[2]) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000572 SkDEBUGCODE(this->validate();)
573 int currVerb = 0;
574 const SkPoint* pts = fPathRef->points();
575 const SkPoint* first = pts;
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000576 Direction testDirs[2];
halcanary96fcdcc2015-08-27 07:41:13 -0700577 if (!isRectContour(true, &currVerb, &pts, nullptr, &testDirs[0])) {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000578 return false;
579 }
580 const SkPoint* last = pts;
581 SkRect testRects[2];
caryclark95bc5f32015-04-08 08:34:15 -0700582 bool isClosed;
583 if (isRectContour(false, &currVerb, &pts, &isClosed, &testDirs[1])) {
scroggo@google.com614f9e32013-05-09 18:05:32 +0000584 testRects[0].set(first, SkToS32(last - first));
caryclark95bc5f32015-04-08 08:34:15 -0700585 if (!isClosed) {
586 pts = fPathRef->points() + fPathRef->countPoints();
587 }
scroggo@google.com614f9e32013-05-09 18:05:32 +0000588 testRects[1].set(last, SkToS32(pts - last));
caryclark@google.com56f233a2012-11-19 13:06:06 +0000589 if (testRects[0].contains(testRects[1])) {
590 if (rects) {
591 rects[0] = testRects[0];
592 rects[1] = testRects[1];
593 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000594 if (dirs) {
595 dirs[0] = testDirs[0];
596 dirs[1] = testDirs[1];
597 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000598 return true;
599 }
600 if (testRects[1].contains(testRects[0])) {
601 if (rects) {
602 rects[0] = testRects[1];
603 rects[1] = testRects[0];
604 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000605 if (dirs) {
606 dirs[0] = testDirs[1];
607 dirs[1] = testDirs[0];
608 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000609 return true;
610 }
611 }
612 return false;
613}
614
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000615int SkPath::countPoints() const {
616 return fPathRef->countPoints();
617}
618
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000619int SkPath::getPoints(SkPoint dst[], int max) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000620 SkDEBUGCODE(this->validate();)
621
622 SkASSERT(max >= 0);
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000623 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000624 int count = SkMin32(max, fPathRef->countPoints());
mtklein8bf5d172015-12-09 13:15:07 -0800625 sk_careful_memcpy(dst, fPathRef->points(), count * sizeof(SkPoint));
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000626 return fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000627}
628
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000629SkPoint SkPath::getPoint(int index) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000630 if ((unsigned)index < (unsigned)fPathRef->countPoints()) {
631 return fPathRef->atPoint(index);
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000632 }
633 return SkPoint::Make(0, 0);
634}
635
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000636int SkPath::countVerbs() const {
637 return fPathRef->countVerbs();
638}
639
640static inline void copy_verbs_reverse(uint8_t* inorderDst,
641 const uint8_t* reversedSrc,
642 int count) {
643 for (int i = 0; i < count; ++i) {
644 inorderDst[i] = reversedSrc[~i];
645 }
646}
647
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000648int SkPath::getVerbs(uint8_t dst[], int max) const {
649 SkDEBUGCODE(this->validate();)
650
651 SkASSERT(max >= 0);
652 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000653 int count = SkMin32(max, fPathRef->countVerbs());
654 copy_verbs_reverse(dst, fPathRef->verbs(), count);
655 return fPathRef->countVerbs();
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000656}
657
reed@google.com294dd7b2011-10-11 11:58:32 +0000658bool SkPath::getLastPt(SkPoint* lastPt) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000659 SkDEBUGCODE(this->validate();)
660
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000661 int count = fPathRef->countPoints();
reed@google.com294dd7b2011-10-11 11:58:32 +0000662 if (count > 0) {
663 if (lastPt) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000664 *lastPt = fPathRef->atPoint(count - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000665 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000666 return true;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000667 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000668 if (lastPt) {
669 lastPt->set(0, 0);
670 }
671 return false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000672}
673
caryclarkaec25102015-04-29 08:28:30 -0700674void SkPath::setPt(int index, SkScalar x, SkScalar y) {
675 SkDEBUGCODE(this->validate();)
676
677 int count = fPathRef->countPoints();
678 if (count <= index) {
679 return;
680 } else {
681 SkPathRef::Editor ed(&fPathRef);
682 ed.atPoint(index)->set(x, y);
683 }
684}
685
reed@android.com8a1c16f2008-12-17 15:59:43 +0000686void SkPath::setLastPt(SkScalar x, SkScalar y) {
687 SkDEBUGCODE(this->validate();)
688
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000689 int count = fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000690 if (count == 0) {
691 this->moveTo(x, y);
692 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000693 SkPathRef::Editor ed(&fPathRef);
694 ed.atPoint(count-1)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000695 }
696}
697
reed@google.com04863fa2011-05-15 04:08:24 +0000698void SkPath::setConvexity(Convexity c) {
699 if (fConvexity != c) {
700 fConvexity = c;
reed@google.com04863fa2011-05-15 04:08:24 +0000701 }
702}
703
reed@android.com8a1c16f2008-12-17 15:59:43 +0000704//////////////////////////////////////////////////////////////////////////////
705// Construction methods
706
reed026beb52015-06-10 14:23:15 -0700707#define DIRTY_AFTER_EDIT \
708 do { \
709 fConvexity = kUnknown_Convexity; \
710 fFirstDirection = SkPathPriv::kUnknown_FirstDirection; \
reed@google.comb54455e2011-05-16 14:16:04 +0000711 } while (0)
712
reed@android.com8a1c16f2008-12-17 15:59:43 +0000713void SkPath::incReserve(U16CPU inc) {
714 SkDEBUGCODE(this->validate();)
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000715 SkPathRef::Editor(&fPathRef, inc, inc);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000716 SkDEBUGCODE(this->validate();)
717}
718
719void SkPath::moveTo(SkScalar x, SkScalar y) {
720 SkDEBUGCODE(this->validate();)
721
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000722 SkPathRef::Editor ed(&fPathRef);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000723
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000724 // remember our index
725 fLastMoveToIndex = fPathRef->countPoints();
726
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000727 ed.growForVerb(kMove_Verb)->set(x, y);
bsalomonb17c1292014-08-28 14:04:55 -0700728
729 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000730}
731
732void SkPath::rMoveTo(SkScalar x, SkScalar y) {
733 SkPoint pt;
734 this->getLastPt(&pt);
735 this->moveTo(pt.fX + x, pt.fY + y);
736}
737
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000738void SkPath::injectMoveToIfNeeded() {
739 if (fLastMoveToIndex < 0) {
740 SkScalar x, y;
741 if (fPathRef->countVerbs() == 0) {
742 x = y = 0;
743 } else {
744 const SkPoint& pt = fPathRef->atPoint(~fLastMoveToIndex);
745 x = pt.fX;
746 y = pt.fY;
747 }
748 this->moveTo(x, y);
749 }
750}
751
reed@android.com8a1c16f2008-12-17 15:59:43 +0000752void SkPath::lineTo(SkScalar x, SkScalar y) {
753 SkDEBUGCODE(this->validate();)
754
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000755 this->injectMoveToIfNeeded();
756
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000757 SkPathRef::Editor ed(&fPathRef);
758 ed.growForVerb(kLine_Verb)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000759
reed@google.comb54455e2011-05-16 14:16:04 +0000760 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000761}
762
763void SkPath::rLineTo(SkScalar x, SkScalar y) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000764 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000765 SkPoint pt;
766 this->getLastPt(&pt);
767 this->lineTo(pt.fX + x, pt.fY + y);
768}
769
770void SkPath::quadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
771 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000772
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000773 this->injectMoveToIfNeeded();
774
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000775 SkPathRef::Editor ed(&fPathRef);
776 SkPoint* pts = ed.growForVerb(kQuad_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000777 pts[0].set(x1, y1);
778 pts[1].set(x2, y2);
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000779
reed@google.comb54455e2011-05-16 14:16:04 +0000780 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000781}
782
783void SkPath::rQuadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000784 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000785 SkPoint pt;
786 this->getLastPt(&pt);
787 this->quadTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2);
788}
789
reed@google.com277c3f82013-05-31 15:17:50 +0000790void SkPath::conicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
791 SkScalar w) {
792 // check for <= 0 or NaN with this test
793 if (!(w > 0)) {
794 this->lineTo(x2, y2);
795 } else if (!SkScalarIsFinite(w)) {
796 this->lineTo(x1, y1);
797 this->lineTo(x2, y2);
798 } else if (SK_Scalar1 == w) {
799 this->quadTo(x1, y1, x2, y2);
800 } else {
801 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000802
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000803 this->injectMoveToIfNeeded();
804
reed@google.com277c3f82013-05-31 15:17:50 +0000805 SkPathRef::Editor ed(&fPathRef);
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000806 SkPoint* pts = ed.growForVerb(kConic_Verb, w);
reed@google.com277c3f82013-05-31 15:17:50 +0000807 pts[0].set(x1, y1);
808 pts[1].set(x2, y2);
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000809
reed@google.com277c3f82013-05-31 15:17:50 +0000810 DIRTY_AFTER_EDIT;
811 }
812}
813
814void SkPath::rConicTo(SkScalar dx1, SkScalar dy1, SkScalar dx2, SkScalar dy2,
815 SkScalar w) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000816 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@google.com277c3f82013-05-31 15:17:50 +0000817 SkPoint pt;
818 this->getLastPt(&pt);
819 this->conicTo(pt.fX + dx1, pt.fY + dy1, pt.fX + dx2, pt.fY + dy2, w);
820}
821
reed@android.com8a1c16f2008-12-17 15:59:43 +0000822void SkPath::cubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
823 SkScalar x3, SkScalar y3) {
824 SkDEBUGCODE(this->validate();)
825
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000826 this->injectMoveToIfNeeded();
827
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000828 SkPathRef::Editor ed(&fPathRef);
829 SkPoint* pts = ed.growForVerb(kCubic_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000830 pts[0].set(x1, y1);
831 pts[1].set(x2, y2);
832 pts[2].set(x3, y3);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000833
reed@google.comb54455e2011-05-16 14:16:04 +0000834 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000835}
836
837void SkPath::rCubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
838 SkScalar x3, SkScalar y3) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000839 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000840 SkPoint pt;
841 this->getLastPt(&pt);
842 this->cubicTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2,
843 pt.fX + x3, pt.fY + y3);
844}
845
846void SkPath::close() {
847 SkDEBUGCODE(this->validate();)
848
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000849 int count = fPathRef->countVerbs();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000850 if (count > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000851 switch (fPathRef->atVerb(count - 1)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000852 case kLine_Verb:
853 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000854 case kConic_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +0000855 case kCubic_Verb:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000856 case kMove_Verb: {
857 SkPathRef::Editor ed(&fPathRef);
858 ed.growForVerb(kClose_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000859 break;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000860 }
reed@google.com277c3f82013-05-31 15:17:50 +0000861 case kClose_Verb:
reed@google.comfa2f2a42013-05-30 15:29:48 +0000862 // don't add a close if it's the first verb or a repeat
reed@google.com7950a9e2013-05-30 14:57:55 +0000863 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000864 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000865 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000866 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000867 }
868 }
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000869
870 // signal that we need a moveTo to follow us (unless we're done)
871#if 0
872 if (fLastMoveToIndex >= 0) {
873 fLastMoveToIndex = ~fLastMoveToIndex;
874 }
875#else
876 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
877#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000878}
879
880///////////////////////////////////////////////////////////////////////////////
reed@google.comabf15c12011-01-18 20:35:51 +0000881
fmalitac08d53e2015-11-17 09:53:29 -0800882namespace {
883
884template <unsigned N>
885class PointIterator {
886public:
887 PointIterator(SkPath::Direction dir, unsigned startIndex)
888 : fCurrent(startIndex % N)
889 , fAdvance(dir == SkPath::kCW_Direction ? 1 : N - 1) { }
890
891 const SkPoint& current() const {
892 SkASSERT(fCurrent < N);
893 return fPts[fCurrent];
894 }
895
896 const SkPoint& next() {
897 fCurrent = (fCurrent + fAdvance) % N;
898 return this->current();
899 }
900
901protected:
902 SkPoint fPts[N];
903
904private:
905 unsigned fCurrent;
906 unsigned fAdvance;
907};
908
909class RectPointIterator : public PointIterator<4> {
910public:
911 RectPointIterator(const SkRect& rect, SkPath::Direction dir, unsigned startIndex)
912 : PointIterator(dir, startIndex) {
913
914 fPts[0] = SkPoint::Make(rect.fLeft, rect.fTop);
915 fPts[1] = SkPoint::Make(rect.fRight, rect.fTop);
916 fPts[2] = SkPoint::Make(rect.fRight, rect.fBottom);
917 fPts[3] = SkPoint::Make(rect.fLeft, rect.fBottom);
918 }
919};
920
921class OvalPointIterator : public PointIterator<4> {
922public:
923 OvalPointIterator(const SkRect& oval, SkPath::Direction dir, unsigned startIndex)
924 : PointIterator(dir, startIndex) {
925
926 const SkScalar cx = oval.centerX();
927 const SkScalar cy = oval.centerY();
928
929 fPts[0] = SkPoint::Make(cx, oval.fTop);
930 fPts[1] = SkPoint::Make(oval.fRight, cy);
931 fPts[2] = SkPoint::Make(cx, oval.fBottom);
932 fPts[3] = SkPoint::Make(oval.fLeft, cy);
933 }
934};
935
936class RRectPointIterator : public PointIterator<8> {
937public:
938 RRectPointIterator(const SkRRect& rrect, SkPath::Direction dir, unsigned startIndex)
939 : PointIterator(dir, startIndex) {
940
941 const SkRect& bounds = rrect.getBounds();
942 const SkScalar L = bounds.fLeft;
943 const SkScalar T = bounds.fTop;
944 const SkScalar R = bounds.fRight;
945 const SkScalar B = bounds.fBottom;
946
947 fPts[0] = SkPoint::Make(L + rrect.radii(SkRRect::kUpperLeft_Corner).fX, T);
948 fPts[1] = SkPoint::Make(R - rrect.radii(SkRRect::kUpperRight_Corner).fX, T);
949 fPts[2] = SkPoint::Make(R, T + rrect.radii(SkRRect::kUpperRight_Corner).fY);
950 fPts[3] = SkPoint::Make(R, B - rrect.radii(SkRRect::kLowerRight_Corner).fY);
951 fPts[4] = SkPoint::Make(R - rrect.radii(SkRRect::kLowerRight_Corner).fX, B);
952 fPts[5] = SkPoint::Make(L + rrect.radii(SkRRect::kLowerLeft_Corner).fX, B);
953 fPts[6] = SkPoint::Make(L, B - rrect.radii(SkRRect::kLowerLeft_Corner).fY);
954 fPts[7] = SkPoint::Make(L, T + rrect.radii(SkRRect::kUpperLeft_Corner).fY);
955 }
956};
957
958} // anonymous namespace
959
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000960static void assert_known_direction(int dir) {
961 SkASSERT(SkPath::kCW_Direction == dir || SkPath::kCCW_Direction == dir);
962}
963
reed@android.com8a1c16f2008-12-17 15:59:43 +0000964void SkPath::addRect(const SkRect& rect, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -0800965 this->addRect(rect, dir, 0);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000966}
967
968void SkPath::addRect(SkScalar left, SkScalar top, SkScalar right,
969 SkScalar bottom, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -0800970 this->addRect(SkRect::MakeLTRB(left, top, right, bottom), dir, 0);
971}
972
973void SkPath::addRect(const SkRect &rect, Direction dir, unsigned startIndex) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000974 assert_known_direction(dir);
reed026beb52015-06-10 14:23:15 -0700975 fFirstDirection = this->hasOnlyMoveTos() ?
fmalitac08d53e2015-11-17 09:53:29 -0800976 (SkPathPriv::FirstDirection)dir : SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000977 SkAutoDisableDirectionCheck addc(this);
fmalitac08d53e2015-11-17 09:53:29 -0800978 SkAutoPathBoundsUpdate apbu(this, rect);
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000979
fmalitac08d53e2015-11-17 09:53:29 -0800980 SkDEBUGCODE(int initialVerbCount = this->countVerbs());
reed@android.com8a1c16f2008-12-17 15:59:43 +0000981
fmalitac08d53e2015-11-17 09:53:29 -0800982 const int kVerbs = 5; // moveTo + 3x lineTo + close
983 this->incReserve(kVerbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000984
fmalitac08d53e2015-11-17 09:53:29 -0800985 RectPointIterator iter(rect, dir, startIndex);
986
987 this->moveTo(iter.current());
988 this->lineTo(iter.next());
989 this->lineTo(iter.next());
990 this->lineTo(iter.next());
reed@android.com8a1c16f2008-12-17 15:59:43 +0000991 this->close();
fmalitac08d53e2015-11-17 09:53:29 -0800992
993 SkASSERT(this->countVerbs() == initialVerbCount + kVerbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000994}
995
reed@google.com744faba2012-05-29 19:54:52 +0000996void SkPath::addPoly(const SkPoint pts[], int count, bool close) {
997 SkDEBUGCODE(this->validate();)
998 if (count <= 0) {
999 return;
1000 }
1001
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +00001002 fLastMoveToIndex = fPathRef->countPoints();
1003
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001004 // +close makes room for the extra kClose_Verb
1005 SkPathRef::Editor ed(&fPathRef, count+close, count);
1006
1007 ed.growForVerb(kMove_Verb)->set(pts[0].fX, pts[0].fY);
reed@google.com744faba2012-05-29 19:54:52 +00001008 if (count > 1) {
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001009 SkPoint* p = ed.growForRepeatedVerb(kLine_Verb, count - 1);
1010 memcpy(p, &pts[1], (count-1) * sizeof(SkPoint));
reed@google.com744faba2012-05-29 19:54:52 +00001011 }
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001012
reed@google.com744faba2012-05-29 19:54:52 +00001013 if (close) {
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001014 ed.growForVerb(kClose_Verb);
caryclark63c684a2015-02-25 09:04:04 -08001015 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
reed@google.com744faba2012-05-29 19:54:52 +00001016 }
1017
reed@google.com744faba2012-05-29 19:54:52 +00001018 DIRTY_AFTER_EDIT;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001019 SkDEBUGCODE(this->validate();)
reed@google.com744faba2012-05-29 19:54:52 +00001020}
1021
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001022#include "SkGeometry.h"
1023
reedf90ea012015-01-29 12:03:58 -08001024static bool arc_is_lone_point(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
1025 SkPoint* pt) {
1026 if (0 == sweepAngle && (0 == startAngle || SkIntToScalar(360) == startAngle)) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001027 // Chrome uses this path to move into and out of ovals. If not
1028 // treated as a special case the moves can distort the oval's
1029 // bounding box (and break the circle special case).
reedf90ea012015-01-29 12:03:58 -08001030 pt->set(oval.fRight, oval.centerY());
1031 return true;
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001032 } else if (0 == oval.width() && 0 == oval.height()) {
1033 // Chrome will sometimes create 0 radius round rects. Having degenerate
1034 // quad segments in the path prevents the path from being recognized as
1035 // a rect.
1036 // TODO: optimizing the case where only one of width or height is zero
1037 // should also be considered. This case, however, doesn't seem to be
1038 // as common as the single point case.
reedf90ea012015-01-29 12:03:58 -08001039 pt->set(oval.fRight, oval.fTop);
1040 return true;
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001041 }
reedf90ea012015-01-29 12:03:58 -08001042 return false;
1043}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001044
reedd5d27d92015-02-09 13:54:43 -08001045// Return the unit vectors pointing at the start/stop points for the given start/sweep angles
1046//
1047static void angles_to_unit_vectors(SkScalar startAngle, SkScalar sweepAngle,
1048 SkVector* startV, SkVector* stopV, SkRotationDirection* dir) {
1049 startV->fY = SkScalarSinCos(SkDegreesToRadians(startAngle), &startV->fX);
1050 stopV->fY = SkScalarSinCos(SkDegreesToRadians(startAngle + sweepAngle), &stopV->fX);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001051
1052 /* If the sweep angle is nearly (but less than) 360, then due to precision
reedd5d27d92015-02-09 13:54:43 -08001053 loss in radians-conversion and/or sin/cos, we may end up with coincident
1054 vectors, which will fool SkBuildQuadArc into doing nothing (bad) instead
1055 of drawing a nearly complete circle (good).
1056 e.g. canvas.drawArc(0, 359.99, ...)
1057 -vs- canvas.drawArc(0, 359.9, ...)
1058 We try to detect this edge case, and tweak the stop vector
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001059 */
reedd5d27d92015-02-09 13:54:43 -08001060 if (*startV == *stopV) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001061 SkScalar sw = SkScalarAbs(sweepAngle);
1062 if (sw < SkIntToScalar(360) && sw > SkIntToScalar(359)) {
1063 SkScalar stopRad = SkDegreesToRadians(startAngle + sweepAngle);
1064 // make a guess at a tiny angle (in radians) to tweak by
1065 SkScalar deltaRad = SkScalarCopySign(SK_Scalar1/512, sweepAngle);
1066 // not sure how much will be enough, so we use a loop
1067 do {
1068 stopRad -= deltaRad;
reedd5d27d92015-02-09 13:54:43 -08001069 stopV->fY = SkScalarSinCos(stopRad, &stopV->fX);
1070 } while (*startV == *stopV);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001071 }
1072 }
reedd5d27d92015-02-09 13:54:43 -08001073 *dir = sweepAngle > 0 ? kCW_SkRotationDirection : kCCW_SkRotationDirection;
1074}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001075
reed9e779d42015-02-17 11:43:14 -08001076/**
1077 * If this returns 0, then the caller should just line-to the singlePt, else it should
1078 * ignore singlePt and append the specified number of conics.
1079 */
reedd5d27d92015-02-09 13:54:43 -08001080static int build_arc_conics(const SkRect& oval, const SkVector& start, const SkVector& stop,
reed9e779d42015-02-17 11:43:14 -08001081 SkRotationDirection dir, SkConic conics[SkConic::kMaxConicsForArc],
1082 SkPoint* singlePt) {
reedd5d27d92015-02-09 13:54:43 -08001083 SkMatrix matrix;
1084
1085 matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height()));
1086 matrix.postTranslate(oval.centerX(), oval.centerY());
1087
reed9e779d42015-02-17 11:43:14 -08001088 int count = SkConic::BuildUnitArc(start, stop, dir, &matrix, conics);
1089 if (0 == count) {
1090 matrix.mapXY(start.x(), start.y(), singlePt);
1091 }
1092 return count;
reedd5d27d92015-02-09 13:54:43 -08001093}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001094
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001095void SkPath::addRoundRect(const SkRect& rect, const SkScalar radii[],
reed@android.com8a1c16f2008-12-17 15:59:43 +00001096 Direction dir) {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001097 SkRRect rrect;
1098 rrect.setRectRadii(rect, (const SkVector*) radii);
1099 this->addRRect(rrect, dir);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001100}
1101
reed@google.com4ed0fb72012-12-12 20:48:18 +00001102void SkPath::addRRect(const SkRRect& rrect, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -08001103 // legacy start indices: 6 (CW) and 7(CCW)
1104 this->addRRect(rrect, dir, dir == kCW_Direction ? 6 : 7);
1105}
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001106
fmalitac08d53e2015-11-17 09:53:29 -08001107void SkPath::addRRect(const SkRRect &rrect, Direction dir, unsigned startIndex) {
1108 assert_known_direction(dir);
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001109
fmalitac08d53e2015-11-17 09:53:29 -08001110 if (rrect.isEmpty()) {
1111 return;
reed1b28a3a2014-12-17 14:42:09 -08001112 }
fmalitac08d53e2015-11-17 09:53:29 -08001113
caryclarkda707bf2015-11-19 14:47:43 -08001114 bool isRRect = hasOnlyMoveTos();
fmalitac08d53e2015-11-17 09:53:29 -08001115 const SkRect& bounds = rrect.getBounds();
1116
1117 if (rrect.isRect()) {
1118 // degenerate(rect) => radii points are collapsing
1119 this->addRect(bounds, dir, (startIndex + 1) / 2);
1120 } else if (rrect.isOval()) {
1121 // degenerate(oval) => line points are collapsing
1122 this->addOval(bounds, dir, startIndex / 2);
1123 } else {
1124 fFirstDirection = this->hasOnlyMoveTos() ?
1125 (SkPathPriv::FirstDirection)dir : SkPathPriv::kUnknown_FirstDirection;
1126
1127 SkAutoPathBoundsUpdate apbu(this, bounds);
1128 SkAutoDisableDirectionCheck addc(this);
1129
1130 // we start with a conic on odd indices when moving CW vs. even indices when moving CCW
1131 const bool startsWithConic = ((startIndex & 1) == (dir == kCW_Direction));
1132 const SkScalar weight = SK_ScalarRoot2Over2;
1133
1134 SkDEBUGCODE(int initialVerbCount = this->countVerbs());
1135 const int kVerbs = startsWithConic
1136 ? 9 // moveTo + 4x conicTo + 3x lineTo + close
1137 : 10; // moveTo + 4x lineTo + 4x conicTo + close
1138 this->incReserve(kVerbs);
1139
1140 RRectPointIterator rrectIter(rrect, dir, startIndex);
1141 // Corner iterator indices follow the collapsed radii model,
1142 // adjusted such that the start pt is "behind" the radii start pt.
1143 const unsigned rectStartIndex = startIndex / 2 + (dir == kCW_Direction ? 0 : 1);
1144 RectPointIterator rectIter(bounds, dir, rectStartIndex);
1145
1146 this->moveTo(rrectIter.current());
1147 if (startsWithConic) {
1148 for (unsigned i = 0; i < 3; ++i) {
1149 this->conicTo(rectIter.next(), rrectIter.next(), weight);
1150 this->lineTo(rrectIter.next());
1151 }
1152 this->conicTo(rectIter.next(), rrectIter.next(), weight);
1153 // final lineTo handled by close().
1154 } else {
1155 for (unsigned i = 0; i < 4; ++i) {
1156 this->lineTo(rrectIter.next());
1157 this->conicTo(rectIter.next(), rrectIter.next(), weight);
1158 }
1159 }
1160 this->close();
1161
caryclarkda707bf2015-11-19 14:47:43 -08001162 SkPathRef::Editor ed(&fPathRef);
1163 ed.setIsRRect(isRRect);
1164
fmalitac08d53e2015-11-17 09:53:29 -08001165 SkASSERT(this->countVerbs() == initialVerbCount + kVerbs);
1166 }
1167
1168 SkDEBUGCODE(fPathRef->validate();)
reed@google.com4ed0fb72012-12-12 20:48:18 +00001169}
1170
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001171bool SkPath::hasOnlyMoveTos() const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001172 int count = fPathRef->countVerbs();
1173 const uint8_t* verbs = const_cast<const SkPathRef*>(fPathRef.get())->verbsMemBegin();
1174 for (int i = 0; i < count; ++i) {
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001175 if (*verbs == kLine_Verb ||
1176 *verbs == kQuad_Verb ||
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001177 *verbs == kConic_Verb ||
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001178 *verbs == kCubic_Verb) {
1179 return false;
1180 }
1181 ++verbs;
1182 }
1183 return true;
1184}
1185
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001186void SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
1187 Direction dir) {
1188 assert_known_direction(dir);
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001189
humper@google.com75e3ca12013-04-08 21:44:11 +00001190 if (rx < 0 || ry < 0) {
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001191 SkErrorInternals::SetError( kInvalidArgument_SkError,
humper@google.com75e3ca12013-04-08 21:44:11 +00001192 "I got %f and %f as radii to SkPath::AddRoundRect, "
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001193 "but negative radii are not allowed.",
humper@google.com75e3ca12013-04-08 21:44:11 +00001194 SkScalarToDouble(rx), SkScalarToDouble(ry) );
1195 return;
1196 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001197
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001198 SkRRect rrect;
1199 rrect.setRectXY(rect, rx, ry);
1200 this->addRRect(rrect, dir);
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001201}
1202
reed@android.com8a1c16f2008-12-17 15:59:43 +00001203void SkPath::addOval(const SkRect& oval, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -08001204 // legacy start index: 1
1205 this->addOval(oval, dir, 1);
1206}
1207
1208void SkPath::addOval(const SkRect &oval, Direction dir, unsigned startPointIndex) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +00001209 assert_known_direction(dir);
1210
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001211 /* If addOval() is called after previous moveTo(),
1212 this path is still marked as an oval. This is used to
1213 fit into WebKit's calling sequences.
1214 We can't simply check isEmpty() in this case, as additional
1215 moveTo() would mark the path non empty.
1216 */
robertphillips@google.com466310d2013-12-03 16:43:54 +00001217 bool isOval = hasOnlyMoveTos();
1218 if (isOval) {
reed026beb52015-06-10 14:23:15 -07001219 fFirstDirection = (SkPathPriv::FirstDirection)dir;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001220 } else {
reed026beb52015-06-10 14:23:15 -07001221 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001222 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001223
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001224 SkAutoDisableDirectionCheck addc(this);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001225 SkAutoPathBoundsUpdate apbu(this, oval);
1226
fmalitac08d53e2015-11-17 09:53:29 -08001227 SkDEBUGCODE(int initialVerbCount = this->countVerbs());
1228 const int kVerbs = 6; // moveTo + 4x conicTo + close
1229 this->incReserve(kVerbs);
1230
1231 OvalPointIterator ovalIter(oval, dir, startPointIndex);
1232 // The corner iterator pts are tracking "behind" the oval/radii pts.
1233 RectPointIterator rectIter(oval, dir, startPointIndex + (dir == kCW_Direction ? 0 : 1));
reed220f9262014-12-17 08:21:04 -08001234 const SkScalar weight = SK_ScalarRoot2Over2;
1235
fmalitac08d53e2015-11-17 09:53:29 -08001236 this->moveTo(ovalIter.current());
1237 for (unsigned i = 0; i < 4; ++i) {
1238 this->conicTo(rectIter.next(), ovalIter.next(), weight);
reed220f9262014-12-17 08:21:04 -08001239 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001240 this->close();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001241
fmalitac08d53e2015-11-17 09:53:29 -08001242 SkASSERT(this->countVerbs() == initialVerbCount + kVerbs);
1243
robertphillips@google.com466310d2013-12-03 16:43:54 +00001244 SkPathRef::Editor ed(&fPathRef);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001245
robertphillips@google.com466310d2013-12-03 16:43:54 +00001246 ed.setIsOval(isOval);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001247}
1248
reed@android.com8a1c16f2008-12-17 15:59:43 +00001249void SkPath::addCircle(SkScalar x, SkScalar y, SkScalar r, Direction dir) {
1250 if (r > 0) {
fmalitac08d53e2015-11-17 09:53:29 -08001251 this->addOval(SkRect::MakeLTRB(x - r, y - r, x + r, y + r), dir);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001252 }
1253}
1254
reed@android.com8a1c16f2008-12-17 15:59:43 +00001255void SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
1256 bool forceMoveTo) {
1257 if (oval.width() < 0 || oval.height() < 0) {
1258 return;
1259 }
1260
reedf90ea012015-01-29 12:03:58 -08001261 if (fPathRef->countVerbs() == 0) {
1262 forceMoveTo = true;
1263 }
1264
1265 SkPoint lonePt;
1266 if (arc_is_lone_point(oval, startAngle, sweepAngle, &lonePt)) {
1267 forceMoveTo ? this->moveTo(lonePt) : this->lineTo(lonePt);
1268 return;
1269 }
1270
reedd5d27d92015-02-09 13:54:43 -08001271 SkVector startV, stopV;
1272 SkRotationDirection dir;
1273 angles_to_unit_vectors(startAngle, sweepAngle, &startV, &stopV, &dir);
1274
reed9e779d42015-02-17 11:43:14 -08001275 SkPoint singlePt;
reedd5d27d92015-02-09 13:54:43 -08001276 SkConic conics[SkConic::kMaxConicsForArc];
reed9e779d42015-02-17 11:43:14 -08001277 int count = build_arc_conics(oval, startV, stopV, dir, conics, &singlePt);
reedd5d27d92015-02-09 13:54:43 -08001278 if (count) {
1279 this->incReserve(count * 2 + 1);
1280 const SkPoint& pt = conics[0].fPts[0];
1281 forceMoveTo ? this->moveTo(pt) : this->lineTo(pt);
1282 for (int i = 0; i < count; ++i) {
1283 this->conicTo(conics[i].fPts[1], conics[i].fPts[2], conics[i].fW);
1284 }
reed9e779d42015-02-17 11:43:14 -08001285 } else {
1286 forceMoveTo ? this->moveTo(singlePt) : this->lineTo(singlePt);
reedd5d27d92015-02-09 13:54:43 -08001287 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001288}
1289
caryclark55d49052016-01-23 05:07:04 -08001290// This converts the SVG arc to conics.
1291// Partly adapted from Niko's code in kdelibs/kdecore/svgicons.
1292// Then transcribed from webkit/chrome's SVGPathNormalizer::decomposeArcToCubic()
1293// See also SVG implementation notes:
1294// http://www.w3.org/TR/SVG/implnote.html#ArcConversionEndpointToCenter
1295// Note that arcSweep bool value is flipped from the original implementation.
1296void SkPath::arcTo(SkScalar rx, SkScalar ry, SkScalar angle, SkPath::ArcSize arcLarge,
1297 SkPath::Direction arcSweep, SkScalar x, SkScalar y) {
caryclarkf1d41512016-02-09 10:30:22 -08001298 this->injectMoveToIfNeeded();
caryclark55d49052016-01-23 05:07:04 -08001299 SkPoint srcPts[2];
1300 this->getLastPt(&srcPts[0]);
1301 // If rx = 0 or ry = 0 then this arc is treated as a straight line segment (a "lineto")
1302 // joining the endpoints.
1303 // http://www.w3.org/TR/SVG/implnote.html#ArcOutOfRangeParameters
1304 if (!rx || !ry) {
caryclarkfc752532016-01-25 05:39:04 -08001305 this->lineTo(x, y);
caryclark55d49052016-01-23 05:07:04 -08001306 return;
1307 }
1308 // If the current point and target point for the arc are identical, it should be treated as a
1309 // zero length path. This ensures continuity in animations.
1310 srcPts[1].set(x, y);
1311 if (srcPts[0] == srcPts[1]) {
caryclarkfc752532016-01-25 05:39:04 -08001312 this->lineTo(x, y);
caryclark55d49052016-01-23 05:07:04 -08001313 return;
1314 }
1315 rx = SkScalarAbs(rx);
1316 ry = SkScalarAbs(ry);
1317 SkVector midPointDistance = srcPts[0] - srcPts[1];
1318 midPointDistance *= 0.5f;
1319
1320 SkMatrix pointTransform;
1321 pointTransform.setRotate(-angle);
1322
1323 SkPoint transformedMidPoint;
1324 pointTransform.mapPoints(&transformedMidPoint, &midPointDistance, 1);
1325 SkScalar squareRx = rx * rx;
1326 SkScalar squareRy = ry * ry;
1327 SkScalar squareX = transformedMidPoint.fX * transformedMidPoint.fX;
1328 SkScalar squareY = transformedMidPoint.fY * transformedMidPoint.fY;
1329
1330 // Check if the radii are big enough to draw the arc, scale radii if not.
1331 // http://www.w3.org/TR/SVG/implnote.html#ArcCorrectionOutOfRangeRadii
1332 SkScalar radiiScale = squareX / squareRx + squareY / squareRy;
1333 if (radiiScale > 1) {
1334 radiiScale = SkScalarSqrt(radiiScale);
1335 rx *= radiiScale;
1336 ry *= radiiScale;
1337 }
1338
1339 pointTransform.setScale(1 / rx, 1 / ry);
1340 pointTransform.preRotate(-angle);
1341
1342 SkPoint unitPts[2];
1343 pointTransform.mapPoints(unitPts, srcPts, (int) SK_ARRAY_COUNT(unitPts));
1344 SkVector delta = unitPts[1] - unitPts[0];
1345
1346 SkScalar d = delta.fX * delta.fX + delta.fY * delta.fY;
1347 SkScalar scaleFactorSquared = SkTMax(1 / d - 0.25f, 0.f);
1348
1349 SkScalar scaleFactor = SkScalarSqrt(scaleFactorSquared);
1350 if (SkToBool(arcSweep) != SkToBool(arcLarge)) { // flipped from the original implementation
1351 scaleFactor = -scaleFactor;
1352 }
1353 delta.scale(scaleFactor);
1354 SkPoint centerPoint = unitPts[0] + unitPts[1];
1355 centerPoint *= 0.5f;
1356 centerPoint.offset(-delta.fY, delta.fX);
1357 unitPts[0] -= centerPoint;
1358 unitPts[1] -= centerPoint;
1359 SkScalar theta1 = SkScalarATan2(unitPts[0].fY, unitPts[0].fX);
1360 SkScalar theta2 = SkScalarATan2(unitPts[1].fY, unitPts[1].fX);
1361 SkScalar thetaArc = theta2 - theta1;
1362 if (thetaArc < 0 && !arcSweep) { // arcSweep flipped from the original implementation
1363 thetaArc += SK_ScalarPI * 2;
1364 } else if (thetaArc > 0 && arcSweep) { // arcSweep flipped from the original implementation
1365 thetaArc -= SK_ScalarPI * 2;
1366 }
1367 pointTransform.setRotate(angle);
1368 pointTransform.preScale(rx, ry);
1369
1370 int segments = SkScalarCeilToInt(SkScalarAbs(thetaArc / (SK_ScalarPI / 2)));
1371 SkScalar thetaWidth = thetaArc / segments;
1372 SkScalar t = SkScalarTan(0.5f * thetaWidth);
1373 if (!SkScalarIsFinite(t)) {
1374 return;
1375 }
1376 SkScalar startTheta = theta1;
1377 SkScalar w = SkScalarSqrt(SK_ScalarHalf + SkScalarCos(thetaWidth) * SK_ScalarHalf);
1378 for (int i = 0; i < segments; ++i) {
1379 SkScalar endTheta = startTheta + thetaWidth;
1380 SkScalar cosEndTheta, sinEndTheta = SkScalarSinCos(endTheta, &cosEndTheta);
1381
1382 unitPts[1].set(cosEndTheta, sinEndTheta);
1383 unitPts[1] += centerPoint;
1384 unitPts[0] = unitPts[1];
1385 unitPts[0].offset(t * sinEndTheta, -t * cosEndTheta);
1386 SkPoint mapped[2];
1387 pointTransform.mapPoints(mapped, unitPts, (int) SK_ARRAY_COUNT(unitPts));
1388 this->conicTo(mapped[0], mapped[1], w);
1389 startTheta = endTheta;
1390 }
1391}
1392
1393void SkPath::rArcTo(SkScalar rx, SkScalar ry, SkScalar xAxisRotate, SkPath::ArcSize largeArc,
1394 SkPath::Direction sweep, SkScalar dx, SkScalar dy) {
1395 SkPoint currentPoint;
1396 this->getLastPt(&currentPoint);
1397 this->arcTo(rx, ry, xAxisRotate, largeArc, sweep, currentPoint.fX + dx, currentPoint.fY + dy);
1398}
1399
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001400void SkPath::addArc(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001401 if (oval.isEmpty() || 0 == sweepAngle) {
1402 return;
1403 }
1404
1405 const SkScalar kFullCircleAngle = SkIntToScalar(360);
1406
1407 if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
1408 this->addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction);
reedc7789042015-01-29 12:59:11 -08001409 } else {
1410 this->arcTo(oval, startAngle, sweepAngle, true);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001411 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001412}
1413
1414/*
1415 Need to handle the case when the angle is sharp, and our computed end-points
1416 for the arc go behind pt1 and/or p2...
1417*/
reedc7789042015-01-29 12:59:11 -08001418void SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2, SkScalar radius) {
reeda8b326c2014-12-09 11:50:32 -08001419 if (radius == 0) {
1420 this->lineTo(x1, y1);
1421 return;
1422 }
1423
1424 SkVector before, after;
reed@google.comabf15c12011-01-18 20:35:51 +00001425
reed@android.com8a1c16f2008-12-17 15:59:43 +00001426 // need to know our prev pt so we can construct tangent vectors
1427 {
1428 SkPoint start;
1429 this->getLastPt(&start);
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001430 // Handle degenerate cases by adding a line to the first point and
1431 // bailing out.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001432 before.setNormalize(x1 - start.fX, y1 - start.fY);
1433 after.setNormalize(x2 - x1, y2 - y1);
1434 }
reed@google.comabf15c12011-01-18 20:35:51 +00001435
reed@android.com8a1c16f2008-12-17 15:59:43 +00001436 SkScalar cosh = SkPoint::DotProduct(before, after);
1437 SkScalar sinh = SkPoint::CrossProduct(before, after);
1438
1439 if (SkScalarNearlyZero(sinh)) { // angle is too tight
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001440 this->lineTo(x1, y1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001441 return;
1442 }
reed@google.comabf15c12011-01-18 20:35:51 +00001443
caryclark88651ae2016-01-20 11:55:11 -08001444 SkScalar dist = SkScalarAbs(SkScalarMulDiv(radius, SK_Scalar1 - cosh, sinh));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001445
1446 SkScalar xx = x1 - SkScalarMul(dist, before.fX);
1447 SkScalar yy = y1 - SkScalarMul(dist, before.fY);
caryclark88651ae2016-01-20 11:55:11 -08001448 after.setLength(dist);
1449 this->lineTo(xx, yy);
1450 SkScalar weight = SkScalarSqrt(SK_ScalarHalf + cosh * SK_ScalarHalf);
1451 this->conicTo(x1, y1, x1 + after.fX, y1 + after.fY, weight);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001452}
1453
1454///////////////////////////////////////////////////////////////////////////////
1455
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001456void SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy, AddPathMode mode) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001457 SkMatrix matrix;
1458
1459 matrix.setTranslate(dx, dy);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001460 this->addPath(path, matrix, mode);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001461}
1462
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001463void SkPath::addPath(const SkPath& path, const SkMatrix& matrix, AddPathMode mode) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001464 SkPathRef::Editor(&fPathRef, path.countVerbs(), path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001465
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001466 RawIter iter(path);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001467 SkPoint pts[4];
1468 Verb verb;
1469
1470 SkMatrix::MapPtsProc proc = matrix.getMapPtsProc();
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001471 bool firstVerb = true;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001472 while ((verb = iter.next(pts)) != kDone_Verb) {
1473 switch (verb) {
1474 case kMove_Verb:
1475 proc(matrix, &pts[0], &pts[0], 1);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001476 if (firstVerb && mode == kExtend_AddPathMode && !isEmpty()) {
1477 injectMoveToIfNeeded(); // In case last contour is closed
1478 this->lineTo(pts[0]);
1479 } else {
1480 this->moveTo(pts[0]);
1481 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001482 break;
1483 case kLine_Verb:
1484 proc(matrix, &pts[1], &pts[1], 1);
1485 this->lineTo(pts[1]);
1486 break;
1487 case kQuad_Verb:
1488 proc(matrix, &pts[1], &pts[1], 2);
1489 this->quadTo(pts[1], pts[2]);
1490 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001491 case kConic_Verb:
1492 proc(matrix, &pts[1], &pts[1], 2);
1493 this->conicTo(pts[1], pts[2], iter.conicWeight());
1494 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001495 case kCubic_Verb:
1496 proc(matrix, &pts[1], &pts[1], 3);
1497 this->cubicTo(pts[1], pts[2], pts[3]);
1498 break;
1499 case kClose_Verb:
1500 this->close();
1501 break;
1502 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001503 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001504 }
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001505 firstVerb = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001506 }
1507}
1508
1509///////////////////////////////////////////////////////////////////////////////
1510
reed@google.com277c3f82013-05-31 15:17:50 +00001511static int pts_in_verb(unsigned verb) {
1512 static const uint8_t gPtsInVerb[] = {
1513 1, // kMove
1514 1, // kLine
1515 2, // kQuad
1516 2, // kConic
1517 3, // kCubic
1518 0, // kClose
1519 0 // kDone
1520 };
1521
1522 SkASSERT(verb < SK_ARRAY_COUNT(gPtsInVerb));
1523 return gPtsInVerb[verb];
1524}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001525
reed@android.com8a1c16f2008-12-17 15:59:43 +00001526// ignore the last point of the 1st contour
1527void SkPath::reversePathTo(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001528 int i, vcount = path.fPathRef->countVerbs();
1529 // exit early if the path is empty, or just has a moveTo.
1530 if (vcount < 2) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001531 return;
1532 }
1533
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001534 SkPathRef::Editor(&fPathRef, vcount, path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001535
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001536 const uint8_t* verbs = path.fPathRef->verbs();
1537 const SkPoint* pts = path.fPathRef->points();
reed@google.com277c3f82013-05-31 15:17:50 +00001538 const SkScalar* conicWeights = path.fPathRef->conicWeights();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001539
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001540 SkASSERT(verbs[~0] == kMove_Verb);
1541 for (i = 1; i < vcount; ++i) {
reed@google.com277c3f82013-05-31 15:17:50 +00001542 unsigned v = verbs[~i];
1543 int n = pts_in_verb(v);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001544 if (n == 0) {
1545 break;
1546 }
1547 pts += n;
reed@google.com277c3f82013-05-31 15:17:50 +00001548 conicWeights += (SkPath::kConic_Verb == v);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001549 }
1550
1551 while (--i > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001552 switch (verbs[~i]) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001553 case kLine_Verb:
1554 this->lineTo(pts[-1].fX, pts[-1].fY);
1555 break;
1556 case kQuad_Verb:
1557 this->quadTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY);
1558 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001559 case kConic_Verb:
1560 this->conicTo(pts[-1], pts[-2], *--conicWeights);
1561 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001562 case kCubic_Verb:
1563 this->cubicTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY,
1564 pts[-3].fX, pts[-3].fY);
1565 break;
1566 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001567 SkDEBUGFAIL("bad verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001568 break;
1569 }
reed@google.com277c3f82013-05-31 15:17:50 +00001570 pts -= pts_in_verb(verbs[~i]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001571 }
1572}
1573
reed@google.com63d73742012-01-10 15:33:12 +00001574void SkPath::reverseAddPath(const SkPath& src) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001575 SkPathRef::Editor ed(&fPathRef, src.fPathRef->countPoints(), src.fPathRef->countVerbs());
reed@google.com63d73742012-01-10 15:33:12 +00001576
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001577 const SkPoint* pts = src.fPathRef->pointsEnd();
1578 // we will iterator through src's verbs backwards
1579 const uint8_t* verbs = src.fPathRef->verbsMemBegin(); // points at the last verb
1580 const uint8_t* verbsEnd = src.fPathRef->verbs(); // points just past the first verb
reed@google.com277c3f82013-05-31 15:17:50 +00001581 const SkScalar* conicWeights = src.fPathRef->conicWeightsEnd();
reed@google.com63d73742012-01-10 15:33:12 +00001582
1583 bool needMove = true;
1584 bool needClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001585 while (verbs < verbsEnd) {
1586 uint8_t v = *(verbs++);
reed@google.com277c3f82013-05-31 15:17:50 +00001587 int n = pts_in_verb(v);
reed@google.com63d73742012-01-10 15:33:12 +00001588
1589 if (needMove) {
1590 --pts;
1591 this->moveTo(pts->fX, pts->fY);
1592 needMove = false;
1593 }
1594 pts -= n;
1595 switch (v) {
1596 case kMove_Verb:
1597 if (needClose) {
1598 this->close();
1599 needClose = false;
1600 }
1601 needMove = true;
1602 pts += 1; // so we see the point in "if (needMove)" above
1603 break;
1604 case kLine_Verb:
1605 this->lineTo(pts[0]);
1606 break;
1607 case kQuad_Verb:
1608 this->quadTo(pts[1], pts[0]);
1609 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001610 case kConic_Verb:
1611 this->conicTo(pts[1], pts[0], *--conicWeights);
1612 break;
reed@google.com63d73742012-01-10 15:33:12 +00001613 case kCubic_Verb:
1614 this->cubicTo(pts[2], pts[1], pts[0]);
1615 break;
1616 case kClose_Verb:
1617 needClose = true;
1618 break;
1619 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00001620 SkDEBUGFAIL("unexpected verb");
reed@google.com63d73742012-01-10 15:33:12 +00001621 }
1622 }
1623}
1624
reed@android.com8a1c16f2008-12-17 15:59:43 +00001625///////////////////////////////////////////////////////////////////////////////
1626
1627void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
1628 SkMatrix matrix;
1629
1630 matrix.setTranslate(dx, dy);
1631 this->transform(matrix, dst);
1632}
1633
reed@android.com8a1c16f2008-12-17 15:59:43 +00001634static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
1635 int level = 2) {
1636 if (--level >= 0) {
1637 SkPoint tmp[7];
1638
1639 SkChopCubicAtHalf(pts, tmp);
1640 subdivide_cubic_to(path, &tmp[0], level);
1641 subdivide_cubic_to(path, &tmp[3], level);
1642 } else {
1643 path->cubicTo(pts[1], pts[2], pts[3]);
1644 }
1645}
1646
1647void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
1648 SkDEBUGCODE(this->validate();)
halcanary96fcdcc2015-08-27 07:41:13 -07001649 if (dst == nullptr) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001650 dst = (SkPath*)this;
1651 }
1652
tomhudson@google.com8d430182011-06-06 19:11:19 +00001653 if (matrix.hasPerspective()) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001654 SkPath tmp;
1655 tmp.fFillType = fFillType;
1656
1657 SkPath::Iter iter(*this, false);
1658 SkPoint pts[4];
1659 SkPath::Verb verb;
1660
reed@google.com4a3b7142012-05-16 17:16:46 +00001661 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001662 switch (verb) {
1663 case kMove_Verb:
1664 tmp.moveTo(pts[0]);
1665 break;
1666 case kLine_Verb:
1667 tmp.lineTo(pts[1]);
1668 break;
1669 case kQuad_Verb:
reed220f9262014-12-17 08:21:04 -08001670 // promote the quad to a conic
1671 tmp.conicTo(pts[1], pts[2],
1672 SkConic::TransformW(pts, SK_Scalar1, matrix));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001673 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001674 case kConic_Verb:
reed220f9262014-12-17 08:21:04 -08001675 tmp.conicTo(pts[1], pts[2],
1676 SkConic::TransformW(pts, iter.conicWeight(), matrix));
reed@google.com277c3f82013-05-31 15:17:50 +00001677 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001678 case kCubic_Verb:
1679 subdivide_cubic_to(&tmp, pts);
1680 break;
1681 case kClose_Verb:
1682 tmp.close();
1683 break;
1684 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001685 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001686 break;
1687 }
1688 }
1689
1690 dst->swap(tmp);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001691 SkPathRef::Editor ed(&dst->fPathRef);
1692 matrix.mapPoints(ed.points(), ed.pathRef()->countPoints());
reed026beb52015-06-10 14:23:15 -07001693 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001694 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001695 SkPathRef::CreateTransformedCopy(&dst->fPathRef, *fPathRef.get(), matrix);
1696
reed@android.com8a1c16f2008-12-17 15:59:43 +00001697 if (this != dst) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001698 dst->fFillType = fFillType;
reed@google.com2a6f8ab2011-10-25 18:41:23 +00001699 dst->fConvexity = fConvexity;
jvanverthb3eb6872014-10-24 07:12:51 -07001700 dst->fIsVolatile = fIsVolatile;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001701 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001702
reed026beb52015-06-10 14:23:15 -07001703 if (SkPathPriv::kUnknown_FirstDirection == fFirstDirection) {
1704 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001705 } else {
1706 SkScalar det2x2 =
1707 SkScalarMul(matrix.get(SkMatrix::kMScaleX), matrix.get(SkMatrix::kMScaleY)) -
1708 SkScalarMul(matrix.get(SkMatrix::kMSkewX), matrix.get(SkMatrix::kMSkewY));
1709 if (det2x2 < 0) {
herb9f4dbca2015-09-28 11:05:47 -07001710 dst->fFirstDirection = SkPathPriv::OppositeFirstDirection(
1711 (SkPathPriv::FirstDirection)fFirstDirection.load());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001712 } else if (det2x2 > 0) {
herb9f4dbca2015-09-28 11:05:47 -07001713 dst->fFirstDirection = fFirstDirection.load();
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001714 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001715 dst->fConvexity = kUnknown_Convexity;
reed026beb52015-06-10 14:23:15 -07001716 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001717 }
1718 }
1719
reed@android.com8a1c16f2008-12-17 15:59:43 +00001720 SkDEBUGCODE(dst->validate();)
1721 }
1722}
1723
reed@android.com8a1c16f2008-12-17 15:59:43 +00001724///////////////////////////////////////////////////////////////////////////////
1725///////////////////////////////////////////////////////////////////////////////
1726
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001727enum SegmentState {
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001728 kEmptyContour_SegmentState, // The current contour is empty. We may be
1729 // starting processing or we may have just
1730 // closed a contour.
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001731 kAfterMove_SegmentState, // We have seen a move, but nothing else.
1732 kAfterPrimitive_SegmentState // We have seen a primitive but not yet
1733 // closed the path. Also the initial state.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001734};
1735
1736SkPath::Iter::Iter() {
1737#ifdef SK_DEBUG
halcanary96fcdcc2015-08-27 07:41:13 -07001738 fPts = nullptr;
1739 fConicWeights = nullptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001740 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001741 fForceClose = fCloseLine = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001742 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001743#endif
1744 // need to init enough to make next() harmlessly return kDone_Verb
halcanary96fcdcc2015-08-27 07:41:13 -07001745 fVerbs = nullptr;
1746 fVerbStop = nullptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001747 fNeedClose = false;
1748}
1749
1750SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
1751 this->setPath(path, forceClose);
1752}
1753
1754void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001755 fPts = path.fPathRef->points();
1756 fVerbs = path.fPathRef->verbs();
1757 fVerbStop = path.fPathRef->verbsMemBegin();
reed@google.com277c3f82013-05-31 15:17:50 +00001758 fConicWeights = path.fPathRef->conicWeights() - 1; // begin one behind
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001759 fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org72785c42011-12-29 21:03:28 +00001760 fMoveTo.fX = fMoveTo.fY = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001761 fForceClose = SkToU8(forceClose);
1762 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001763 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001764}
1765
1766bool SkPath::Iter::isClosedContour() const {
halcanary96fcdcc2015-08-27 07:41:13 -07001767 if (fVerbs == nullptr || fVerbs == fVerbStop) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001768 return false;
1769 }
1770 if (fForceClose) {
1771 return true;
1772 }
1773
1774 const uint8_t* verbs = fVerbs;
1775 const uint8_t* stop = fVerbStop;
reed@google.comabf15c12011-01-18 20:35:51 +00001776
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001777 if (kMove_Verb == *(verbs - 1)) {
1778 verbs -= 1; // skip the initial moveto
reed@android.com8a1c16f2008-12-17 15:59:43 +00001779 }
1780
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001781 while (verbs > stop) {
1782 // verbs points one beyond the current verb, decrement first.
1783 unsigned v = *(--verbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001784 if (kMove_Verb == v) {
1785 break;
1786 }
1787 if (kClose_Verb == v) {
1788 return true;
1789 }
1790 }
1791 return false;
1792}
1793
1794SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001795 SkASSERT(pts);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001796 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001797 // A special case: if both points are NaN, SkPoint::operation== returns
1798 // false, but the iterator expects that they are treated as the same.
1799 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001800 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1801 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001802 return kClose_Verb;
1803 }
1804
reed@google.com9e25dbf2012-05-15 17:05:38 +00001805 pts[0] = fLastPt;
1806 pts[1] = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001807 fLastPt = fMoveTo;
1808 fCloseLine = true;
1809 return kLine_Verb;
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +00001810 } else {
1811 pts[0] = fMoveTo;
1812 return kClose_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001813 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001814}
1815
reed@google.com9e25dbf2012-05-15 17:05:38 +00001816const SkPoint& SkPath::Iter::cons_moveTo() {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001817 if (fSegmentState == kAfterMove_SegmentState) {
1818 // Set the first return pt to the move pt
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001819 fSegmentState = kAfterPrimitive_SegmentState;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001820 return fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001821 } else {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001822 SkASSERT(fSegmentState == kAfterPrimitive_SegmentState);
1823 // Set the first return pt to the last pt of the previous primitive.
reed@google.com9e25dbf2012-05-15 17:05:38 +00001824 return fPts[-1];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001825 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001826}
1827
caryclarke8c56662015-07-14 11:19:26 -07001828void SkPath::Iter::consumeDegenerateSegments(bool exact) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001829 // We need to step over anything that will not move the current draw point
1830 // forward before the next move is seen
1831 const uint8_t* lastMoveVerb = 0;
1832 const SkPoint* lastMovePt = 0;
halcanary96fcdcc2015-08-27 07:41:13 -07001833 const SkScalar* lastMoveWeight = nullptr;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001834 SkPoint lastPt = fLastPt;
1835 while (fVerbs != fVerbStop) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001836 unsigned verb = *(fVerbs - 1); // fVerbs is one beyond the current verb
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001837 switch (verb) {
1838 case kMove_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001839 // Keep a record of this most recent move
1840 lastMoveVerb = fVerbs;
1841 lastMovePt = fPts;
robertphillipsb8de1f42015-02-23 11:17:01 -08001842 lastMoveWeight = fConicWeights;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001843 lastPt = fPts[0];
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001844 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001845 fPts++;
1846 break;
1847
1848 case kClose_Verb:
schenney@chromium.org7e963602012-06-13 17:05:43 +00001849 // A close when we are in a segment is always valid except when it
1850 // follows a move which follows a segment.
1851 if (fSegmentState == kAfterPrimitive_SegmentState && !lastMoveVerb) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001852 return;
1853 }
1854 // A close at any other time must be ignored
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001855 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001856 break;
1857
1858 case kLine_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001859 if (!IsLineDegenerate(lastPt, fPts[0], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001860 if (lastMoveVerb) {
1861 fVerbs = lastMoveVerb;
1862 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001863 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001864 return;
1865 }
1866 return;
1867 }
1868 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001869 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001870 fPts++;
1871 break;
1872
reed@google.com277c3f82013-05-31 15:17:50 +00001873 case kConic_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001874 case kQuad_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001875 if (!IsQuadDegenerate(lastPt, fPts[0], fPts[1], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001876 if (lastMoveVerb) {
1877 fVerbs = lastMoveVerb;
1878 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001879 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001880 return;
1881 }
1882 return;
1883 }
1884 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001885 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001886 fPts += 2;
reed@google.com277c3f82013-05-31 15:17:50 +00001887 fConicWeights += (kConic_Verb == verb);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001888 break;
1889
1890 case kCubic_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001891 if (!IsCubicDegenerate(lastPt, fPts[0], fPts[1], fPts[2], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001892 if (lastMoveVerb) {
1893 fVerbs = lastMoveVerb;
1894 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001895 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001896 return;
1897 }
1898 return;
1899 }
1900 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001901 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001902 fPts += 3;
1903 break;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001904
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001905 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001906 SkDEBUGFAIL("Should never see kDone_Verb");
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001907 }
1908 }
1909}
1910
reed@google.com4a3b7142012-05-16 17:16:46 +00001911SkPath::Verb SkPath::Iter::doNext(SkPoint ptsParam[4]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001912 SkASSERT(ptsParam);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001913
reed@android.com8a1c16f2008-12-17 15:59:43 +00001914 if (fVerbs == fVerbStop) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001915 // Close the curve if requested and if there is some curve to close
1916 if (fNeedClose && fSegmentState == kAfterPrimitive_SegmentState) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001917 if (kLine_Verb == this->autoClose(ptsParam)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001918 return kLine_Verb;
1919 }
1920 fNeedClose = false;
1921 return kClose_Verb;
1922 }
1923 return kDone_Verb;
1924 }
1925
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001926 // fVerbs is one beyond the current verb, decrement first
1927 unsigned verb = *(--fVerbs);
reed@google.com9e25dbf2012-05-15 17:05:38 +00001928 const SkPoint* SK_RESTRICT srcPts = fPts;
1929 SkPoint* SK_RESTRICT pts = ptsParam;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001930
1931 switch (verb) {
1932 case kMove_Verb:
1933 if (fNeedClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001934 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001935 verb = this->autoClose(pts);
1936 if (verb == kClose_Verb) {
1937 fNeedClose = false;
1938 }
1939 return (Verb)verb;
1940 }
1941 if (fVerbs == fVerbStop) { // might be a trailing moveto
1942 return kDone_Verb;
1943 }
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001944 fMoveTo = *srcPts;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001945 pts[0] = *srcPts;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001946 srcPts += 1;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001947 fSegmentState = kAfterMove_SegmentState;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001948 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001949 fNeedClose = fForceClose;
1950 break;
1951 case kLine_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001952 pts[0] = this->cons_moveTo();
1953 pts[1] = srcPts[0];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001954 fLastPt = srcPts[0];
1955 fCloseLine = false;
1956 srcPts += 1;
1957 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001958 case kConic_Verb:
1959 fConicWeights += 1;
1960 // fall-through
reed@android.com8a1c16f2008-12-17 15:59:43 +00001961 case kQuad_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001962 pts[0] = this->cons_moveTo();
1963 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001964 fLastPt = srcPts[1];
1965 srcPts += 2;
1966 break;
1967 case kCubic_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001968 pts[0] = this->cons_moveTo();
1969 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001970 fLastPt = srcPts[2];
1971 srcPts += 3;
1972 break;
1973 case kClose_Verb:
1974 verb = this->autoClose(pts);
1975 if (verb == kLine_Verb) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001976 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001977 } else {
1978 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001979 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001980 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001981 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001982 break;
1983 }
1984 fPts = srcPts;
1985 return (Verb)verb;
1986}
1987
1988///////////////////////////////////////////////////////////////////////////////
1989
reed@android.com8a1c16f2008-12-17 15:59:43 +00001990/*
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001991 Format in compressed buffer: [ptCount, verbCount, pts[], verbs[]]
reed@android.com8a1c16f2008-12-17 15:59:43 +00001992*/
1993
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00001994size_t SkPath::writeToMemory(void* storage) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001995 SkDEBUGCODE(this->validate();)
1996
halcanary96fcdcc2015-08-27 07:41:13 -07001997 if (nullptr == storage) {
caryclark69006412016-02-17 12:16:27 -08001998 const int byteCount = sizeof(int32_t) * 2 + fPathRef->writeSize();
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001999 return SkAlign4(byteCount);
2000 }
2001
2002 SkWBuffer buffer(storage);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002003
robertphillips@google.com466310d2013-12-03 16:43:54 +00002004 int32_t packed = (fConvexity << kConvexity_SerializationShift) |
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002005 (fFillType << kFillType_SerializationShift) |
reed026beb52015-06-10 14:23:15 -07002006 (fFirstDirection << kDirection_SerializationShift) |
reed8f086022015-06-11 14:22:19 -07002007 (fIsVolatile << kIsVolatile_SerializationShift) |
2008 kCurrent_Version;
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002009
robertphillips@google.com2972bb52012-08-07 17:32:51 +00002010 buffer.write32(packed);
caryclark69006412016-02-17 12:16:27 -08002011 buffer.write32(fLastMoveToIndex);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002012
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002013 fPathRef->writeToBuffer(&buffer);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002014
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002015 buffer.padToAlign4();
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002016 return buffer.pos();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002017}
2018
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002019size_t SkPath::readFromMemory(const void* storage, size_t length) {
2020 SkRBufferWithSizeCheck buffer(storage, length);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002021
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00002022 int32_t packed;
2023 if (!buffer.readS32(&packed)) {
2024 return 0;
2025 }
2026
reed8f086022015-06-11 14:22:19 -07002027 unsigned version = packed & 0xFF;
caryclark69006412016-02-17 12:16:27 -08002028 if (version >= kPathPrivLastMoveToIndex_Version && !buffer.readS32(&fLastMoveToIndex)) {
2029 return 0;
2030 }
mtklein1b249332015-07-07 12:21:21 -07002031
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002032 fConvexity = (packed >> kConvexity_SerializationShift) & 0xFF;
2033 fFillType = (packed >> kFillType_SerializationShift) & 0xFF;
reed8f086022015-06-11 14:22:19 -07002034 uint8_t dir = (packed >> kDirection_SerializationShift) & 0x3;
jvanverthb3eb6872014-10-24 07:12:51 -07002035 fIsVolatile = (packed >> kIsVolatile_SerializationShift) & 0x1;
commit-bot@chromium.orgfed2ab62014-01-23 15:16:05 +00002036 SkPathRef* pathRef = SkPathRef::CreateFromBuffer(&buffer);
ajumaf8aec582016-01-13 13:46:31 -08002037 if (!pathRef) {
2038 return 0;
2039 }
2040
2041 fPathRef.reset(pathRef);
2042 SkDEBUGCODE(this->validate();)
2043 buffer.skipToAlign4();
reed@google.comabf15c12011-01-18 20:35:51 +00002044
reed8f086022015-06-11 14:22:19 -07002045 // compatibility check
2046 if (version < kPathPrivFirstDirection_Version) {
2047 switch (dir) { // old values
2048 case 0:
2049 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
2050 break;
2051 case 1:
2052 fFirstDirection = SkPathPriv::kCW_FirstDirection;
2053 break;
2054 case 2:
2055 fFirstDirection = SkPathPriv::kCCW_FirstDirection;
2056 break;
2057 default:
2058 SkASSERT(false);
2059 }
2060 } else {
2061 fFirstDirection = dir;
2062 }
2063
ajumaf8aec582016-01-13 13:46:31 -08002064 return buffer.pos();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002065}
2066
2067///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00002068
reede05fed02014-12-15 07:59:53 -08002069#include "SkStringUtils.h"
caryclark66a5d8b2014-06-24 08:30:15 -07002070#include "SkStream.h"
reed@google.com51bbe752013-01-17 22:07:50 +00002071
reed@google.com51bbe752013-01-17 22:07:50 +00002072static void append_params(SkString* str, const char label[], const SkPoint pts[],
reede05fed02014-12-15 07:59:53 -08002073 int count, SkScalarAsStringType strType, SkScalar conicWeight = -1) {
reed@google.com51bbe752013-01-17 22:07:50 +00002074 str->append(label);
2075 str->append("(");
skia.committer@gmail.com15dd3002013-01-18 07:07:28 +00002076
reed@google.com51bbe752013-01-17 22:07:50 +00002077 const SkScalar* values = &pts[0].fX;
2078 count *= 2;
2079
2080 for (int i = 0; i < count; ++i) {
reede05fed02014-12-15 07:59:53 -08002081 SkAppendScalar(str, values[i], strType);
reed@google.com51bbe752013-01-17 22:07:50 +00002082 if (i < count - 1) {
2083 str->append(", ");
2084 }
2085 }
reed@google.com277c3f82013-05-31 15:17:50 +00002086 if (conicWeight >= 0) {
2087 str->append(", ");
reede05fed02014-12-15 07:59:53 -08002088 SkAppendScalar(str, conicWeight, strType);
reed@google.com277c3f82013-05-31 15:17:50 +00002089 }
caryclark08fa28c2014-10-23 13:08:56 -07002090 str->append(");");
reede05fed02014-12-15 07:59:53 -08002091 if (kHex_SkScalarAsStringType == strType) {
caryclark08fa28c2014-10-23 13:08:56 -07002092 str->append(" // ");
2093 for (int i = 0; i < count; ++i) {
reede05fed02014-12-15 07:59:53 -08002094 SkAppendScalarDec(str, values[i]);
caryclark08fa28c2014-10-23 13:08:56 -07002095 if (i < count - 1) {
2096 str->append(", ");
2097 }
2098 }
2099 if (conicWeight >= 0) {
2100 str->append(", ");
reede05fed02014-12-15 07:59:53 -08002101 SkAppendScalarDec(str, conicWeight);
caryclark08fa28c2014-10-23 13:08:56 -07002102 }
2103 }
2104 str->append("\n");
reed@google.com51bbe752013-01-17 22:07:50 +00002105}
2106
caryclarke9562592014-09-15 09:26:09 -07002107void SkPath::dump(SkWStream* wStream, bool forceClose, bool dumpAsHex) const {
reede05fed02014-12-15 07:59:53 -08002108 SkScalarAsStringType asType = dumpAsHex ? kHex_SkScalarAsStringType : kDec_SkScalarAsStringType;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002109 Iter iter(*this, forceClose);
2110 SkPoint pts[4];
2111 Verb verb;
2112
caryclark66a5d8b2014-06-24 08:30:15 -07002113 if (!wStream) {
2114 SkDebugf("path: forceClose=%s\n", forceClose ? "true" : "false");
2115 }
reed@google.com51bbe752013-01-17 22:07:50 +00002116 SkString builder;
2117
reed@google.com4a3b7142012-05-16 17:16:46 +00002118 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002119 switch (verb) {
2120 case kMove_Verb:
reede05fed02014-12-15 07:59:53 -08002121 append_params(&builder, "path.moveTo", &pts[0], 1, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002122 break;
2123 case kLine_Verb:
reede05fed02014-12-15 07:59:53 -08002124 append_params(&builder, "path.lineTo", &pts[1], 1, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002125 break;
2126 case kQuad_Verb:
reede05fed02014-12-15 07:59:53 -08002127 append_params(&builder, "path.quadTo", &pts[1], 2, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002128 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002129 case kConic_Verb:
reede05fed02014-12-15 07:59:53 -08002130 append_params(&builder, "path.conicTo", &pts[1], 2, asType, iter.conicWeight());
reed@google.com277c3f82013-05-31 15:17:50 +00002131 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002132 case kCubic_Verb:
reede05fed02014-12-15 07:59:53 -08002133 append_params(&builder, "path.cubicTo", &pts[1], 3, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002134 break;
2135 case kClose_Verb:
caryclark66a5d8b2014-06-24 08:30:15 -07002136 builder.append("path.close();\n");
reed@android.com8a1c16f2008-12-17 15:59:43 +00002137 break;
2138 default:
2139 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
2140 verb = kDone_Verb; // stop the loop
2141 break;
2142 }
caryclark1049f122015-04-20 08:31:59 -07002143 if (!wStream && builder.size()) {
2144 SkDebugf("%s", builder.c_str());
2145 builder.reset();
2146 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002147 }
caryclark66a5d8b2014-06-24 08:30:15 -07002148 if (wStream) {
2149 wStream->writeText(builder.c_str());
caryclark66a5d8b2014-06-24 08:30:15 -07002150 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002151}
2152
reed@android.come522ca52009-11-23 20:10:41 +00002153void SkPath::dump() const {
halcanary96fcdcc2015-08-27 07:41:13 -07002154 this->dump(nullptr, false, false);
caryclarke9562592014-09-15 09:26:09 -07002155}
2156
2157void SkPath::dumpHex() const {
halcanary96fcdcc2015-08-27 07:41:13 -07002158 this->dump(nullptr, false, true);
reed@android.come522ca52009-11-23 20:10:41 +00002159}
2160
2161#ifdef SK_DEBUG
2162void SkPath::validate() const {
reed@android.come522ca52009-11-23 20:10:41 +00002163 SkASSERT((fFillType & ~3) == 0);
reed@google.comabf15c12011-01-18 20:35:51 +00002164
djsollen@google.com077348c2012-10-22 20:23:32 +00002165#ifdef SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002166 if (!fBoundsIsDirty) {
2167 SkRect bounds;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002168
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002169 bool isFinite = compute_pt_bounds(&bounds, *fPathRef.get());
robertphillips@google.com5d8d1862012-08-15 14:36:41 +00002170 SkASSERT(SkToBool(fIsFinite) == isFinite);
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002171
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002172 if (fPathRef->countPoints() <= 1) {
reed@android.come522ca52009-11-23 20:10:41 +00002173 // if we're empty, fBounds may be empty but translated, so we can't
2174 // necessarily compare to bounds directly
2175 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
2176 // be [2, 2, 2, 2]
2177 SkASSERT(bounds.isEmpty());
2178 SkASSERT(fBounds.isEmpty());
2179 } else {
reed@google.comeac52bd2011-11-14 18:13:59 +00002180 if (bounds.isEmpty()) {
2181 SkASSERT(fBounds.isEmpty());
2182 } else {
reed@google.com3563c9e2011-11-14 19:34:57 +00002183 if (!fBounds.isEmpty()) {
2184 SkASSERT(fBounds.contains(bounds));
2185 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002186 }
reed@android.come522ca52009-11-23 20:10:41 +00002187 }
2188 }
djsollen@google.com077348c2012-10-22 20:23:32 +00002189#endif // SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002190}
djsollen@google.com077348c2012-10-22 20:23:32 +00002191#endif // SK_DEBUG
reed@android.come522ca52009-11-23 20:10:41 +00002192
reed@google.com04863fa2011-05-15 04:08:24 +00002193///////////////////////////////////////////////////////////////////////////////
2194
reed@google.com0b7b9822011-05-16 12:29:27 +00002195static int sign(SkScalar x) { return x < 0; }
2196#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00002197
robertphillipsc506e302014-09-16 09:43:31 -07002198enum DirChange {
2199 kLeft_DirChange,
2200 kRight_DirChange,
2201 kStraight_DirChange,
2202 kBackwards_DirChange,
2203
2204 kInvalid_DirChange
2205};
2206
2207
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002208static bool almost_equal(SkScalar compA, SkScalar compB) {
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002209 // The error epsilon was empirically derived; worse case round rects
2210 // with a mid point outset by 2x float epsilon in tests had an error
2211 // of 12.
2212 const int epsilon = 16;
2213 if (!SkScalarIsFinite(compA) || !SkScalarIsFinite(compB)) {
2214 return false;
2215 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002216 // no need to check for small numbers because SkPath::Iter has removed degenerate values
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002217 int aBits = SkFloatAs2sCompliment(compA);
2218 int bBits = SkFloatAs2sCompliment(compB);
2219 return aBits < bBits + epsilon && bBits < aBits + epsilon;
reed@google.com04863fa2011-05-15 04:08:24 +00002220}
2221
caryclarkb4216502015-03-02 13:02:34 -08002222static bool approximately_zero_when_compared_to(double x, double y) {
2223 return x == 0 || fabs(x) < fabs(y * FLT_EPSILON);
robertphillipsc506e302014-09-16 09:43:31 -07002224}
2225
caryclarkb4216502015-03-02 13:02:34 -08002226
reed@google.com04863fa2011-05-15 04:08:24 +00002227// only valid for a single contour
2228struct Convexicator {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002229 Convexicator()
2230 : fPtCount(0)
2231 , fConvexity(SkPath::kConvex_Convexity)
reed026beb52015-06-10 14:23:15 -07002232 , fFirstDirection(SkPathPriv::kUnknown_FirstDirection)
caryclark5ccef572015-03-02 10:07:56 -08002233 , fIsFinite(true)
2234 , fIsCurve(false) {
robertphillipsc506e302014-09-16 09:43:31 -07002235 fExpectedDir = kInvalid_DirChange;
reed@google.com04863fa2011-05-15 04:08:24 +00002236 // warnings
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002237 fLastPt.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002238 fCurrPt.set(0, 0);
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002239 fLastVec.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002240 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00002241
2242 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00002243 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00002244 }
2245
2246 SkPath::Convexity getConvexity() const { return fConvexity; }
2247
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002248 /** The direction returned is only valid if the path is determined convex */
reed026beb52015-06-10 14:23:15 -07002249 SkPathPriv::FirstDirection getFirstDirection() const { return fFirstDirection; }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002250
reed@google.com04863fa2011-05-15 04:08:24 +00002251 void addPt(const SkPoint& pt) {
caryclarkd3d1a982014-12-08 04:57:38 -08002252 if (SkPath::kConcave_Convexity == fConvexity || !fIsFinite) {
reed@google.com04863fa2011-05-15 04:08:24 +00002253 return;
2254 }
2255
2256 if (0 == fPtCount) {
2257 fCurrPt = pt;
2258 ++fPtCount;
2259 } else {
2260 SkVector vec = pt - fCurrPt;
caryclarkd3d1a982014-12-08 04:57:38 -08002261 SkScalar lengthSqd = vec.lengthSqd();
2262 if (!SkScalarIsFinite(lengthSqd)) {
2263 fIsFinite = false;
caryclarke8c56662015-07-14 11:19:26 -07002264 } else if (lengthSqd) {
caryclarkb4216502015-03-02 13:02:34 -08002265 fPriorPt = fLastPt;
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002266 fLastPt = fCurrPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002267 fCurrPt = pt;
2268 if (++fPtCount == 2) {
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002269 fFirstVec = fLastVec = vec;
reed@google.com04863fa2011-05-15 04:08:24 +00002270 } else {
2271 SkASSERT(fPtCount > 2);
2272 this->addVec(vec);
2273 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002274
reed@google.com85b6e392011-05-15 20:25:17 +00002275 int sx = sign(vec.fX);
2276 int sy = sign(vec.fY);
2277 fDx += (sx != fSx);
2278 fDy += (sy != fSy);
2279 fSx = sx;
2280 fSy = sy;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002281
reed@google.com85b6e392011-05-15 20:25:17 +00002282 if (fDx > 3 || fDy > 3) {
2283 fConvexity = SkPath::kConcave_Convexity;
2284 }
reed@google.com04863fa2011-05-15 04:08:24 +00002285 }
2286 }
2287 }
2288
2289 void close() {
2290 if (fPtCount > 2) {
2291 this->addVec(fFirstVec);
2292 }
2293 }
2294
caryclarkb4216502015-03-02 13:02:34 -08002295 DirChange directionChange(const SkVector& curVec) {
2296 SkScalar cross = SkPoint::CrossProduct(fLastVec, curVec);
2297
2298 SkScalar smallest = SkTMin(fCurrPt.fX, SkTMin(fCurrPt.fY, SkTMin(fLastPt.fX, fLastPt.fY)));
2299 SkScalar largest = SkTMax(fCurrPt.fX, SkTMax(fCurrPt.fY, SkTMax(fLastPt.fX, fLastPt.fY)));
2300 largest = SkTMax(largest, -smallest);
2301
2302 if (!almost_equal(largest, largest + cross)) {
2303 int sign = SkScalarSignAsInt(cross);
2304 if (sign) {
2305 return (1 == sign) ? kRight_DirChange : kLeft_DirChange;
2306 }
2307 }
2308
2309 if (cross) {
2310 double dLastVecX = SkScalarToDouble(fLastPt.fX) - SkScalarToDouble(fPriorPt.fX);
2311 double dLastVecY = SkScalarToDouble(fLastPt.fY) - SkScalarToDouble(fPriorPt.fY);
2312 double dCurrVecX = SkScalarToDouble(fCurrPt.fX) - SkScalarToDouble(fLastPt.fX);
2313 double dCurrVecY = SkScalarToDouble(fCurrPt.fY) - SkScalarToDouble(fLastPt.fY);
2314 double dCross = dLastVecX * dCurrVecY - dLastVecY * dCurrVecX;
2315 if (!approximately_zero_when_compared_to(dCross, SkScalarToDouble(largest))) {
2316 int sign = SkScalarSignAsInt(SkDoubleToScalar(dCross));
2317 if (sign) {
2318 return (1 == sign) ? kRight_DirChange : kLeft_DirChange;
2319 }
2320 }
2321 }
2322
2323 if (!SkScalarNearlyZero(fLastVec.lengthSqd(), SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
2324 !SkScalarNearlyZero(curVec.lengthSqd(), SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
2325 fLastVec.dot(curVec) < 0.0f) {
2326 return kBackwards_DirChange;
2327 }
2328
2329 return kStraight_DirChange;
2330 }
2331
2332
caryclarkd3d1a982014-12-08 04:57:38 -08002333 bool isFinite() const {
2334 return fIsFinite;
2335 }
2336
caryclark5ccef572015-03-02 10:07:56 -08002337 void setCurve(bool isCurve) {
2338 fIsCurve = isCurve;
2339 }
2340
reed@google.com04863fa2011-05-15 04:08:24 +00002341private:
2342 void addVec(const SkVector& vec) {
2343 SkASSERT(vec.fX || vec.fY);
caryclarkb4216502015-03-02 13:02:34 -08002344 DirChange dir = this->directionChange(vec);
robertphillipsc506e302014-09-16 09:43:31 -07002345 switch (dir) {
2346 case kLeft_DirChange: // fall through
2347 case kRight_DirChange:
2348 if (kInvalid_DirChange == fExpectedDir) {
2349 fExpectedDir = dir;
reed026beb52015-06-10 14:23:15 -07002350 fFirstDirection = (kRight_DirChange == dir) ? SkPathPriv::kCW_FirstDirection
2351 : SkPathPriv::kCCW_FirstDirection;
robertphillipsc506e302014-09-16 09:43:31 -07002352 } else if (dir != fExpectedDir) {
2353 fConvexity = SkPath::kConcave_Convexity;
reed026beb52015-06-10 14:23:15 -07002354 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
robertphillipsc506e302014-09-16 09:43:31 -07002355 }
2356 fLastVec = vec;
2357 break;
2358 case kStraight_DirChange:
2359 break;
2360 case kBackwards_DirChange:
caryclark5ccef572015-03-02 10:07:56 -08002361 if (fIsCurve) {
2362 fConvexity = SkPath::kConcave_Convexity;
reed026beb52015-06-10 14:23:15 -07002363 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
caryclark5ccef572015-03-02 10:07:56 -08002364 }
robertphillipsc506e302014-09-16 09:43:31 -07002365 fLastVec = vec;
2366 break;
2367 case kInvalid_DirChange:
2368 SkFAIL("Use of invalid direction change flag");
2369 break;
reed@google.com04863fa2011-05-15 04:08:24 +00002370 }
2371 }
2372
caryclarkb4216502015-03-02 13:02:34 -08002373 SkPoint fPriorPt;
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002374 SkPoint fLastPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002375 SkPoint fCurrPt;
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002376 // fLastVec does not necessarily start at fLastPt. We only advance it when the cross product
2377 // value with the current vec is deemed to be of a significant value.
2378 SkVector fLastVec, fFirstVec;
reed@google.com04863fa2011-05-15 04:08:24 +00002379 int fPtCount; // non-degenerate points
robertphillipsc506e302014-09-16 09:43:31 -07002380 DirChange fExpectedDir;
reed@google.com04863fa2011-05-15 04:08:24 +00002381 SkPath::Convexity fConvexity;
reed026beb52015-06-10 14:23:15 -07002382 SkPathPriv::FirstDirection fFirstDirection;
reed@google.com0b7b9822011-05-16 12:29:27 +00002383 int fDx, fDy, fSx, fSy;
caryclarkd3d1a982014-12-08 04:57:38 -08002384 bool fIsFinite;
caryclark5ccef572015-03-02 10:07:56 -08002385 bool fIsCurve;
reed@google.com04863fa2011-05-15 04:08:24 +00002386};
2387
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002388SkPath::Convexity SkPath::internalGetConvexity() const {
2389 SkASSERT(kUnknown_Convexity == fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002390 SkPoint pts[4];
2391 SkPath::Verb verb;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002392 SkPath::Iter iter(*this, true);
reed@google.com04863fa2011-05-15 04:08:24 +00002393
2394 int contourCount = 0;
2395 int count;
2396 Convexicator state;
2397
caryclarkd3d1a982014-12-08 04:57:38 -08002398 if (!isFinite()) {
2399 return kUnknown_Convexity;
2400 }
caryclarke8c56662015-07-14 11:19:26 -07002401 while ((verb = iter.next(pts, true, true)) != SkPath::kDone_Verb) {
reed@google.com04863fa2011-05-15 04:08:24 +00002402 switch (verb) {
2403 case kMove_Verb:
2404 if (++contourCount > 1) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002405 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002406 return kConcave_Convexity;
2407 }
2408 pts[1] = pts[0];
caryclark5ccef572015-03-02 10:07:56 -08002409 // fall through
2410 case kLine_Verb:
reed@google.com04863fa2011-05-15 04:08:24 +00002411 count = 1;
caryclark5ccef572015-03-02 10:07:56 -08002412 state.setCurve(false);
reed@google.com04863fa2011-05-15 04:08:24 +00002413 break;
caryclark5ccef572015-03-02 10:07:56 -08002414 case kQuad_Verb:
2415 // fall through
2416 case kConic_Verb:
2417 // fall through
2418 case kCubic_Verb:
2419 count = 2 + (kCubic_Verb == verb);
2420 // As an additional enhancement, this could set curve true only
2421 // if the curve is nonlinear
2422 state.setCurve(true);
2423 break;
reed@google.com04863fa2011-05-15 04:08:24 +00002424 case kClose_Verb:
caryclark5ccef572015-03-02 10:07:56 -08002425 state.setCurve(false);
reed@google.com04863fa2011-05-15 04:08:24 +00002426 state.close();
2427 count = 0;
2428 break;
2429 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00002430 SkDEBUGFAIL("bad verb");
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002431 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002432 return kConcave_Convexity;
2433 }
2434
2435 for (int i = 1; i <= count; i++) {
2436 state.addPt(pts[i]);
2437 }
2438 // early exit
caryclarkd3d1a982014-12-08 04:57:38 -08002439 if (!state.isFinite()) {
2440 return kUnknown_Convexity;
2441 }
reed@google.com04863fa2011-05-15 04:08:24 +00002442 if (kConcave_Convexity == state.getConvexity()) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002443 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002444 return kConcave_Convexity;
2445 }
2446 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002447 fConvexity = state.getConvexity();
reed026beb52015-06-10 14:23:15 -07002448 if (kConvex_Convexity == fConvexity && SkPathPriv::kUnknown_FirstDirection == fFirstDirection) {
2449 fFirstDirection = state.getFirstDirection();
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002450 }
2451 return static_cast<Convexity>(fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002452}
reed@google.com69a99432012-01-10 18:00:10 +00002453
2454///////////////////////////////////////////////////////////////////////////////
2455
2456class ContourIter {
2457public:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002458 ContourIter(const SkPathRef& pathRef);
reed@google.com69a99432012-01-10 18:00:10 +00002459
2460 bool done() const { return fDone; }
2461 // if !done() then these may be called
2462 int count() const { return fCurrPtCount; }
2463 const SkPoint* pts() const { return fCurrPt; }
2464 void next();
2465
2466private:
2467 int fCurrPtCount;
2468 const SkPoint* fCurrPt;
2469 const uint8_t* fCurrVerb;
2470 const uint8_t* fStopVerbs;
reed@google.com277c3f82013-05-31 15:17:50 +00002471 const SkScalar* fCurrConicWeight;
reed@google.com69a99432012-01-10 18:00:10 +00002472 bool fDone;
reed@google.comd1ab9322012-01-10 18:40:03 +00002473 SkDEBUGCODE(int fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002474};
2475
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002476ContourIter::ContourIter(const SkPathRef& pathRef) {
2477 fStopVerbs = pathRef.verbsMemBegin();
reed@google.com69a99432012-01-10 18:00:10 +00002478 fDone = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002479 fCurrPt = pathRef.points();
2480 fCurrVerb = pathRef.verbs();
reed@google.com277c3f82013-05-31 15:17:50 +00002481 fCurrConicWeight = pathRef.conicWeights();
reed@google.com69a99432012-01-10 18:00:10 +00002482 fCurrPtCount = 0;
reed@google.comd1ab9322012-01-10 18:40:03 +00002483 SkDEBUGCODE(fContourCounter = 0;)
reed@google.com69a99432012-01-10 18:00:10 +00002484 this->next();
2485}
2486
2487void ContourIter::next() {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002488 if (fCurrVerb <= fStopVerbs) {
reed@google.com69a99432012-01-10 18:00:10 +00002489 fDone = true;
2490 }
2491 if (fDone) {
2492 return;
2493 }
2494
2495 // skip pts of prev contour
2496 fCurrPt += fCurrPtCount;
2497
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002498 SkASSERT(SkPath::kMove_Verb == fCurrVerb[~0]);
reed@google.com69a99432012-01-10 18:00:10 +00002499 int ptCount = 1; // moveTo
2500 const uint8_t* verbs = fCurrVerb;
2501
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002502 for (--verbs; verbs > fStopVerbs; --verbs) {
2503 switch (verbs[~0]) {
reed@google.com69a99432012-01-10 18:00:10 +00002504 case SkPath::kMove_Verb:
reed@google.com69a99432012-01-10 18:00:10 +00002505 goto CONTOUR_END;
2506 case SkPath::kLine_Verb:
2507 ptCount += 1;
2508 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002509 case SkPath::kConic_Verb:
2510 fCurrConicWeight += 1;
2511 // fall-through
reed@google.com69a99432012-01-10 18:00:10 +00002512 case SkPath::kQuad_Verb:
2513 ptCount += 2;
2514 break;
2515 case SkPath::kCubic_Verb:
2516 ptCount += 3;
2517 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002518 case SkPath::kClose_Verb:
2519 break;
2520 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00002521 SkDEBUGFAIL("unexpected verb");
reed@google.com69a99432012-01-10 18:00:10 +00002522 break;
2523 }
2524 }
2525CONTOUR_END:
2526 fCurrPtCount = ptCount;
2527 fCurrVerb = verbs;
reed@google.comd1ab9322012-01-10 18:40:03 +00002528 SkDEBUGCODE(++fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002529}
2530
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002531// returns cross product of (p1 - p0) and (p2 - p0)
reed@google.com69a99432012-01-10 18:00:10 +00002532static SkScalar cross_prod(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002533 SkScalar cross = SkPoint::CrossProduct(p1 - p0, p2 - p0);
2534 // We may get 0 when the above subtracts underflow. We expect this to be
2535 // very rare and lazily promote to double.
2536 if (0 == cross) {
2537 double p0x = SkScalarToDouble(p0.fX);
2538 double p0y = SkScalarToDouble(p0.fY);
2539
2540 double p1x = SkScalarToDouble(p1.fX);
2541 double p1y = SkScalarToDouble(p1.fY);
2542
2543 double p2x = SkScalarToDouble(p2.fX);
2544 double p2y = SkScalarToDouble(p2.fY);
2545
2546 cross = SkDoubleToScalar((p1x - p0x) * (p2y - p0y) -
2547 (p1y - p0y) * (p2x - p0x));
2548
2549 }
2550 return cross;
reed@google.com69a99432012-01-10 18:00:10 +00002551}
2552
reed@google.comc1ea60a2012-01-31 15:15:36 +00002553// Returns the first pt with the maximum Y coordinate
reed@google.com69a99432012-01-10 18:00:10 +00002554static int find_max_y(const SkPoint pts[], int count) {
2555 SkASSERT(count > 0);
2556 SkScalar max = pts[0].fY;
reed@google.comc1ea60a2012-01-31 15:15:36 +00002557 int firstIndex = 0;
reed@google.com69a99432012-01-10 18:00:10 +00002558 for (int i = 1; i < count; ++i) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002559 SkScalar y = pts[i].fY;
2560 if (y > max) {
2561 max = y;
2562 firstIndex = i;
reed@google.com69a99432012-01-10 18:00:10 +00002563 }
2564 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002565 return firstIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002566}
2567
reed@google.comcabaf1d2012-01-11 21:03:05 +00002568static int find_diff_pt(const SkPoint pts[], int index, int n, int inc) {
2569 int i = index;
2570 for (;;) {
2571 i = (i + inc) % n;
2572 if (i == index) { // we wrapped around, so abort
2573 break;
2574 }
2575 if (pts[index] != pts[i]) { // found a different point, success!
2576 break;
2577 }
2578 }
2579 return i;
2580}
2581
reed@google.comc1ea60a2012-01-31 15:15:36 +00002582/**
2583 * Starting at index, and moving forward (incrementing), find the xmin and
2584 * xmax of the contiguous points that have the same Y.
2585 */
2586static int find_min_max_x_at_y(const SkPoint pts[], int index, int n,
2587 int* maxIndexPtr) {
2588 const SkScalar y = pts[index].fY;
2589 SkScalar min = pts[index].fX;
2590 SkScalar max = min;
2591 int minIndex = index;
2592 int maxIndex = index;
2593 for (int i = index + 1; i < n; ++i) {
2594 if (pts[i].fY != y) {
2595 break;
2596 }
2597 SkScalar x = pts[i].fX;
2598 if (x < min) {
2599 min = x;
2600 minIndex = i;
2601 } else if (x > max) {
2602 max = x;
2603 maxIndex = i;
2604 }
2605 }
2606 *maxIndexPtr = maxIndex;
2607 return minIndex;
2608}
2609
reed026beb52015-06-10 14:23:15 -07002610static void crossToDir(SkScalar cross, SkPathPriv::FirstDirection* dir) {
2611 *dir = cross > 0 ? SkPathPriv::kCW_FirstDirection : SkPathPriv::kCCW_FirstDirection;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002612}
2613
reed@google.comac8543f2012-01-30 20:51:25 +00002614/*
2615 * We loop through all contours, and keep the computed cross-product of the
2616 * contour that contained the global y-max. If we just look at the first
2617 * contour, we may find one that is wound the opposite way (correctly) since
2618 * it is the interior of a hole (e.g. 'o'). Thus we must find the contour
2619 * that is outer most (or at least has the global y-max) before we can consider
2620 * its cross product.
2621 */
reed026beb52015-06-10 14:23:15 -07002622bool SkPathPriv::CheapComputeFirstDirection(const SkPath& path, FirstDirection* dir) {
herb9f4dbca2015-09-28 11:05:47 -07002623 if (kUnknown_FirstDirection != path.fFirstDirection.load()) {
2624 *dir = static_cast<FirstDirection>(path.fFirstDirection.load());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002625 return true;
2626 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002627
2628 // don't want to pay the cost for computing this if it
2629 // is unknown, so we don't call isConvex()
reed026beb52015-06-10 14:23:15 -07002630 if (SkPath::kConvex_Convexity == path.getConvexityOrUnknown()) {
2631 SkASSERT(kUnknown_FirstDirection == path.fFirstDirection);
herb9f4dbca2015-09-28 11:05:47 -07002632 *dir = static_cast<FirstDirection>(path.fFirstDirection.load());
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002633 return false;
2634 }
reed@google.com69a99432012-01-10 18:00:10 +00002635
reed026beb52015-06-10 14:23:15 -07002636 ContourIter iter(*path.fPathRef.get());
reed@google.com69a99432012-01-10 18:00:10 +00002637
reed@google.comac8543f2012-01-30 20:51:25 +00002638 // initialize with our logical y-min
reed026beb52015-06-10 14:23:15 -07002639 SkScalar ymax = path.getBounds().fTop;
reed@google.comac8543f2012-01-30 20:51:25 +00002640 SkScalar ymaxCross = 0;
2641
reed@google.com69a99432012-01-10 18:00:10 +00002642 for (; !iter.done(); iter.next()) {
2643 int n = iter.count();
reed@google.comcabaf1d2012-01-11 21:03:05 +00002644 if (n < 3) {
2645 continue;
2646 }
djsollen@google.come63793a2012-03-21 15:39:03 +00002647
reed@google.comcabaf1d2012-01-11 21:03:05 +00002648 const SkPoint* pts = iter.pts();
reed@google.com69a99432012-01-10 18:00:10 +00002649 SkScalar cross = 0;
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002650 int index = find_max_y(pts, n);
2651 if (pts[index].fY < ymax) {
2652 continue;
2653 }
2654
2655 // If there is more than 1 distinct point at the y-max, we take the
2656 // x-min and x-max of them and just subtract to compute the dir.
2657 if (pts[(index + 1) % n].fY == pts[index].fY) {
2658 int maxIndex;
2659 int minIndex = find_min_max_x_at_y(pts, index, n, &maxIndex);
2660 if (minIndex == maxIndex) {
2661 goto TRY_CROSSPROD;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002662 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002663 SkASSERT(pts[minIndex].fY == pts[index].fY);
2664 SkASSERT(pts[maxIndex].fY == pts[index].fY);
2665 SkASSERT(pts[minIndex].fX <= pts[maxIndex].fX);
2666 // we just subtract the indices, and let that auto-convert to
2667 // SkScalar, since we just want - or + to signal the direction.
2668 cross = minIndex - maxIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002669 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002670 TRY_CROSSPROD:
2671 // Find a next and prev index to use for the cross-product test,
2672 // but we try to find pts that form non-zero vectors from pts[index]
2673 //
2674 // Its possible that we can't find two non-degenerate vectors, so
2675 // we have to guard our search (e.g. all the pts could be in the
2676 // same place).
2677
2678 // we pass n - 1 instead of -1 so we don't foul up % operator by
2679 // passing it a negative LH argument.
2680 int prev = find_diff_pt(pts, index, n, n - 1);
2681 if (prev == index) {
2682 // completely degenerate, skip to next contour
reed@google.comac8543f2012-01-30 20:51:25 +00002683 continue;
2684 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002685 int next = find_diff_pt(pts, index, n, 1);
2686 SkASSERT(next != index);
2687 cross = cross_prod(pts[prev], pts[index], pts[next]);
2688 // if we get a zero and the points are horizontal, then we look at the spread in
2689 // x-direction. We really should continue to walk away from the degeneracy until
2690 // there is a divergence.
2691 if (0 == cross && pts[prev].fY == pts[index].fY && pts[next].fY == pts[index].fY) {
2692 // construct the subtract so we get the correct Direction below
2693 cross = pts[index].fX - pts[next].fX;
reed@google.com188bfcf2012-01-17 18:26:38 +00002694 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002695 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002696
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002697 if (cross) {
2698 // record our best guess so far
2699 ymax = pts[index].fY;
2700 ymaxCross = cross;
reed@google.com69a99432012-01-10 18:00:10 +00002701 }
2702 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002703 if (ymaxCross) {
2704 crossToDir(ymaxCross, dir);
reed026beb52015-06-10 14:23:15 -07002705 path.fFirstDirection = *dir;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002706 return true;
2707 } else {
2708 return false;
2709 }
reed@google.comac8543f2012-01-30 20:51:25 +00002710}
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002711
2712///////////////////////////////////////////////////////////////////////////////
2713
caryclark9aacd902015-12-14 08:38:09 -08002714static bool between(SkScalar a, SkScalar b, SkScalar c) {
2715 SkASSERT(((a <= b && b <= c) || (a >= b && b >= c)) == ((a - b) * (c - b) <= 0)
2716 || (SkScalarNearlyZero(a) && SkScalarNearlyZero(b) && SkScalarNearlyZero(c)));
2717 return (a - b) * (c - b) <= 0;
2718}
2719
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002720static SkScalar eval_cubic_coeff(SkScalar A, SkScalar B, SkScalar C,
2721 SkScalar D, SkScalar t) {
2722 return SkScalarMulAdd(SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C), t, D);
2723}
2724
2725static SkScalar eval_cubic_pts(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2726 SkScalar t) {
2727 SkScalar A = c3 + 3*(c1 - c2) - c0;
2728 SkScalar B = 3*(c2 - c1 - c1 + c0);
2729 SkScalar C = 3*(c1 - c0);
2730 SkScalar D = c0;
2731 return eval_cubic_coeff(A, B, C, D, t);
2732}
2733
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002734template <size_t N> static void find_minmax(const SkPoint pts[],
2735 SkScalar* minPtr, SkScalar* maxPtr) {
2736 SkScalar min, max;
2737 min = max = pts[0].fX;
2738 for (size_t i = 1; i < N; ++i) {
2739 min = SkMinScalar(min, pts[i].fX);
2740 max = SkMaxScalar(max, pts[i].fX);
2741 }
2742 *minPtr = min;
2743 *maxPtr = max;
2744}
2745
caryclark9cb5d752015-12-18 04:35:24 -08002746static bool checkOnCurve(SkScalar x, SkScalar y, const SkPoint& start, const SkPoint& end) {
2747 if (start.fY == end.fY) {
2748 return between(start.fX, x, end.fX) && x != end.fX;
2749 } else {
2750 return x == start.fX && y == start.fY;
2751 }
2752}
2753
caryclark9aacd902015-12-14 08:38:09 -08002754static int winding_mono_cubic(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
caryclark9cb5d752015-12-18 04:35:24 -08002755 SkScalar y0 = pts[0].fY;
2756 SkScalar y3 = pts[3].fY;
2757
2758 int dir = 1;
2759 if (y0 > y3) {
2760 SkTSwap(y0, y3);
2761 dir = -1;
2762 }
2763 if (y < y0 || y > y3) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002764 return 0;
2765 }
caryclark9cb5d752015-12-18 04:35:24 -08002766 if (checkOnCurve(x, y, pts[0], pts[3])) {
2767 *onCurveCount += 1;
caryclark9aacd902015-12-14 08:38:09 -08002768 return 0;
2769 }
caryclark9cb5d752015-12-18 04:35:24 -08002770 if (y == y3) {
2771 return 0;
2772 }
2773
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002774 // quickreject or quickaccept
2775 SkScalar min, max;
2776 find_minmax<4>(pts, &min, &max);
2777 if (x < min) {
2778 return 0;
2779 }
2780 if (x > max) {
2781 return dir;
2782 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002783
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002784 // compute the actual x(t) value
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002785 SkScalar t;
caryclark9aacd902015-12-14 08:38:09 -08002786 SkAssertResult(SkCubicClipper::ChopMonoAtY(pts, y, &t));
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002787 SkScalar xt = eval_cubic_pts(pts[0].fX, pts[1].fX, pts[2].fX, pts[3].fX, t);
caryclark9aacd902015-12-14 08:38:09 -08002788 if (SkScalarNearlyEqual(xt, x)) {
2789 if (x != pts[3].fX || y != pts[3].fY) { // don't test end points; they're start points
2790 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08002791 return 0;
caryclark9aacd902015-12-14 08:38:09 -08002792 }
2793 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002794 return xt < x ? dir : 0;
2795}
2796
caryclark9aacd902015-12-14 08:38:09 -08002797static int winding_cubic(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002798 SkPoint dst[10];
2799 int n = SkChopCubicAtYExtrema(pts, dst);
2800 int w = 0;
2801 for (int i = 0; i <= n; ++i) {
caryclark9aacd902015-12-14 08:38:09 -08002802 w += winding_mono_cubic(&dst[i * 3], x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002803 }
2804 return w;
2805}
2806
caryclark9aacd902015-12-14 08:38:09 -08002807static double conic_eval_numerator(const SkScalar src[], SkScalar w, SkScalar t) {
2808 SkASSERT(src);
2809 SkASSERT(t >= 0 && t <= 1);
2810 SkScalar src2w = src[2] * w;
2811 SkScalar C = src[0];
2812 SkScalar A = src[4] - 2 * src2w + C;
2813 SkScalar B = 2 * (src2w - C);
2814 return (A * t + B) * t + C;
2815}
2816
2817
2818static double conic_eval_denominator(SkScalar w, SkScalar t) {
2819 SkScalar B = 2 * (w - 1);
2820 SkScalar C = 1;
2821 SkScalar A = -B;
2822 return (A * t + B) * t + C;
2823}
2824
2825static int winding_mono_conic(const SkConic& conic, SkScalar x, SkScalar y, int* onCurveCount) {
2826 const SkPoint* pts = conic.fPts;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002827 SkScalar y0 = pts[0].fY;
2828 SkScalar y2 = pts[2].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002829
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002830 int dir = 1;
2831 if (y0 > y2) {
2832 SkTSwap(y0, y2);
2833 dir = -1;
2834 }
caryclark9aacd902015-12-14 08:38:09 -08002835 if (y < y0 || y > y2) {
2836 return 0;
2837 }
caryclark9cb5d752015-12-18 04:35:24 -08002838 if (checkOnCurve(x, y, pts[0], pts[2])) {
2839 *onCurveCount += 1;
2840 return 0;
2841 }
caryclark9aacd902015-12-14 08:38:09 -08002842 if (y == y2) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002843 return 0;
2844 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002845
caryclark9aacd902015-12-14 08:38:09 -08002846 SkScalar roots[2];
2847 SkScalar A = pts[2].fY;
2848 SkScalar B = pts[1].fY * conic.fW - y * conic.fW + y;
2849 SkScalar C = pts[0].fY;
2850 A += C - 2 * B; // A = a + c - 2*(b*w - yCept*w + yCept)
2851 B -= C; // B = b*w - w * yCept + yCept - a
2852 C -= y;
2853 int n = SkFindUnitQuadRoots(A, 2 * B, C, roots);
2854 SkASSERT(n <= 1);
2855 SkScalar xt;
2856 if (0 == n) {
caryclark9cb5d752015-12-18 04:35:24 -08002857 // zero roots are returned only when y0 == y
2858 // Need [0] if dir == 1
2859 // and [2] if dir == -1
2860 xt = pts[1 - dir].fX;
caryclark9aacd902015-12-14 08:38:09 -08002861 } else {
2862 SkScalar t = roots[0];
2863 xt = conic_eval_numerator(&pts[0].fX, conic.fW, t) / conic_eval_denominator(conic.fW, t);
2864 }
2865 if (SkScalarNearlyEqual(xt, x)) {
2866 if (x != pts[2].fX || y != pts[2].fY) { // don't test end points; they're start points
2867 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08002868 return 0;
caryclark9aacd902015-12-14 08:38:09 -08002869 }
2870 }
2871 return xt < x ? dir : 0;
2872}
2873
2874static bool is_mono_quad(SkScalar y0, SkScalar y1, SkScalar y2) {
2875 // return SkScalarSignAsInt(y0 - y1) + SkScalarSignAsInt(y1 - y2) != 0;
2876 if (y0 == y1) {
2877 return true;
2878 }
2879 if (y0 < y1) {
2880 return y1 <= y2;
2881 } else {
2882 return y1 >= y2;
2883 }
2884}
2885
2886static int winding_conic(const SkPoint pts[], SkScalar x, SkScalar y, SkScalar weight,
2887 int* onCurveCount) {
2888 SkConic conic(pts, weight);
caryclark9aacd902015-12-14 08:38:09 -08002889 SkConic chopped[2];
caryclark1e1e5092016-01-06 08:27:44 -08002890 // If the data points are very large, the conic may not be monotonic but may also
2891 // fail to chop. Then, the chopper does not split the original conic in two.
caryclarke114dd62016-01-06 08:15:44 -08002892 bool isMono = is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY) || !conic.chopAtYExtrema(chopped);
2893 int w = winding_mono_conic(isMono ? conic : chopped[0], x, y, onCurveCount);
2894 if (!isMono) {
caryclark9aacd902015-12-14 08:38:09 -08002895 w += winding_mono_conic(chopped[1], x, y, onCurveCount);
2896 }
2897 return w;
2898}
2899
2900static int winding_mono_quad(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
2901 SkScalar y0 = pts[0].fY;
2902 SkScalar y2 = pts[2].fY;
2903
2904 int dir = 1;
2905 if (y0 > y2) {
2906 SkTSwap(y0, y2);
2907 dir = -1;
2908 }
2909 if (y < y0 || y > y2) {
2910 return 0;
2911 }
caryclark9cb5d752015-12-18 04:35:24 -08002912 if (checkOnCurve(x, y, pts[0], pts[2])) {
2913 *onCurveCount += 1;
2914 return 0;
2915 }
caryclark9aacd902015-12-14 08:38:09 -08002916 if (y == y2) {
caryclark9aacd902015-12-14 08:38:09 -08002917 return 0;
2918 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002919 // bounds check on X (not required. is it faster?)
2920#if 0
2921 if (pts[0].fX > x && pts[1].fX > x && pts[2].fX > x) {
2922 return 0;
2923 }
2924#endif
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002925
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002926 SkScalar roots[2];
2927 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
2928 2 * (pts[1].fY - pts[0].fY),
2929 pts[0].fY - y,
2930 roots);
2931 SkASSERT(n <= 1);
2932 SkScalar xt;
2933 if (0 == n) {
caryclark9cb5d752015-12-18 04:35:24 -08002934 // zero roots are returned only when y0 == y
2935 // Need [0] if dir == 1
2936 // and [2] if dir == -1
2937 xt = pts[1 - dir].fX;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002938 } else {
2939 SkScalar t = roots[0];
2940 SkScalar C = pts[0].fX;
2941 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
2942 SkScalar B = 2 * (pts[1].fX - C);
2943 xt = SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C);
2944 }
caryclark9aacd902015-12-14 08:38:09 -08002945 if (SkScalarNearlyEqual(xt, x)) {
2946 if (x != pts[2].fX || y != pts[2].fY) { // don't test end points; they're start points
2947 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08002948 return 0;
caryclark9aacd902015-12-14 08:38:09 -08002949 }
2950 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002951 return xt < x ? dir : 0;
2952}
2953
caryclark9aacd902015-12-14 08:38:09 -08002954static int winding_quad(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002955 SkPoint dst[5];
2956 int n = 0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002957
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002958 if (!is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY)) {
2959 n = SkChopQuadAtYExtrema(pts, dst);
2960 pts = dst;
2961 }
caryclark9aacd902015-12-14 08:38:09 -08002962 int w = winding_mono_quad(pts, x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002963 if (n > 0) {
caryclark9aacd902015-12-14 08:38:09 -08002964 w += winding_mono_quad(&pts[2], x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002965 }
2966 return w;
2967}
2968
caryclark9aacd902015-12-14 08:38:09 -08002969static int winding_line(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002970 SkScalar x0 = pts[0].fX;
2971 SkScalar y0 = pts[0].fY;
2972 SkScalar x1 = pts[1].fX;
2973 SkScalar y1 = pts[1].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002974
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002975 SkScalar dy = y1 - y0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002976
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002977 int dir = 1;
2978 if (y0 > y1) {
2979 SkTSwap(y0, y1);
2980 dir = -1;
2981 }
caryclark9aacd902015-12-14 08:38:09 -08002982 if (y < y0 || y > y1) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002983 return 0;
2984 }
caryclark9cb5d752015-12-18 04:35:24 -08002985 if (checkOnCurve(x, y, pts[0], pts[1])) {
2986 *onCurveCount += 1;
2987 return 0;
2988 }
2989 if (y == y1) {
caryclark9aacd902015-12-14 08:38:09 -08002990 return 0;
2991 }
2992 SkScalar cross = SkScalarMul(x1 - x0, y - pts[0].fY) - SkScalarMul(dy, x - x0);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002993
caryclark9aacd902015-12-14 08:38:09 -08002994 if (!cross) {
fsc91065d2015-12-17 09:03:27 -08002995 // zero cross means the point is on the line, and since the case where
2996 // y of the query point is at the end point is handled above, we can be
2997 // sure that we're on the line (excluding the end point) here
caryclark9cb5d752015-12-18 04:35:24 -08002998 if (x != x1 || y != pts[1].fY) {
2999 *onCurveCount += 1;
3000 }
caryclark9aacd902015-12-14 08:38:09 -08003001 dir = 0;
3002 } else if (SkScalarSignAsInt(cross) == dir) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003003 dir = 0;
3004 }
3005 return dir;
3006}
3007
caryclark9aacd902015-12-14 08:38:09 -08003008static void tangent_cubic(const SkPoint pts[], SkScalar x, SkScalar y,
3009 SkTDArray<SkVector>* tangents) {
3010 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)
3011 && !between(pts[2].fY, y, pts[3].fY)) {
3012 return;
3013 }
3014 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)
3015 && !between(pts[2].fX, x, pts[3].fX)) {
3016 return;
3017 }
3018 SkPoint dst[10];
3019 int n = SkChopCubicAtYExtrema(pts, dst);
3020 for (int i = 0; i <= n; ++i) {
3021 SkPoint* c = &dst[i * 3];
3022 SkScalar t;
3023 SkAssertResult(SkCubicClipper::ChopMonoAtY(c, y, &t));
3024 SkScalar xt = eval_cubic_pts(c[0].fX, c[1].fX, c[2].fX, c[3].fX, t);
3025 if (!SkScalarNearlyEqual(x, xt)) {
3026 continue;
3027 }
3028 SkVector tangent;
3029 SkEvalCubicAt(c, t, nullptr, &tangent, nullptr);
3030 tangents->push(tangent);
3031 }
3032}
3033
3034static void tangent_conic(const SkPoint pts[], SkScalar x, SkScalar y, SkScalar w,
3035 SkTDArray<SkVector>* tangents) {
3036 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)) {
3037 return;
3038 }
3039 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)) {
3040 return;
3041 }
3042 SkScalar roots[2];
3043 SkScalar A = pts[2].fY;
3044 SkScalar B = pts[1].fY * w - y * w + y;
3045 SkScalar C = pts[0].fY;
3046 A += C - 2 * B; // A = a + c - 2*(b*w - yCept*w + yCept)
3047 B -= C; // B = b*w - w * yCept + yCept - a
3048 C -= y;
3049 int n = SkFindUnitQuadRoots(A, 2 * B, C, roots);
3050 for (int index = 0; index < n; ++index) {
3051 SkScalar t = roots[index];
3052 SkScalar xt = conic_eval_numerator(&pts[0].fX, w, t) / conic_eval_denominator(w, t);
3053 if (!SkScalarNearlyEqual(x, xt)) {
3054 continue;
3055 }
3056 SkConic conic(pts, w);
3057 tangents->push(conic.evalTangentAt(t));
3058 }
3059}
3060
3061static void tangent_quad(const SkPoint pts[], SkScalar x, SkScalar y,
3062 SkTDArray<SkVector>* tangents) {
3063 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)) {
3064 return;
3065 }
3066 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)) {
3067 return;
3068 }
3069 SkScalar roots[2];
3070 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
3071 2 * (pts[1].fY - pts[0].fY),
3072 pts[0].fY - y,
3073 roots);
3074 for (int index = 0; index < n; ++index) {
3075 SkScalar t = roots[index];
3076 SkScalar C = pts[0].fX;
3077 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
3078 SkScalar B = 2 * (pts[1].fX - C);
3079 SkScalar xt = (A * t + B) * t + C;
3080 if (!SkScalarNearlyEqual(x, xt)) {
3081 continue;
3082 }
3083 tangents->push(SkEvalQuadTangentAt(pts, t));
3084 }
3085}
3086
3087static void tangent_line(const SkPoint pts[], SkScalar x, SkScalar y,
3088 SkTDArray<SkVector>* tangents) {
3089 SkScalar y0 = pts[0].fY;
3090 SkScalar y1 = pts[1].fY;
3091 if (!between(y0, y, y1)) {
3092 return;
3093 }
3094 SkScalar x0 = pts[0].fX;
3095 SkScalar x1 = pts[1].fX;
3096 if (!between(x0, x, x1)) {
3097 return;
3098 }
3099 SkScalar dx = x1 - x0;
3100 SkScalar dy = y1 - y0;
3101 if (!SkScalarNearlyEqual((x - x0) * dy, dx * (y - y0))) {
3102 return;
3103 }
3104 SkVector v;
3105 v.set(dx, dy);
3106 tangents->push(v);
3107}
3108
reed@google.com4db592c2013-10-30 17:39:43 +00003109static bool contains_inclusive(const SkRect& r, SkScalar x, SkScalar y) {
3110 return r.fLeft <= x && x <= r.fRight && r.fTop <= y && y <= r.fBottom;
3111}
3112
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003113bool SkPath::contains(SkScalar x, SkScalar y) const {
3114 bool isInverse = this->isInverseFillType();
3115 if (this->isEmpty()) {
3116 return isInverse;
3117 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003118
reed@google.com4db592c2013-10-30 17:39:43 +00003119 if (!contains_inclusive(this->getBounds(), x, y)) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003120 return isInverse;
3121 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003122
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003123 SkPath::Iter iter(*this, true);
3124 bool done = false;
3125 int w = 0;
caryclark9aacd902015-12-14 08:38:09 -08003126 int onCurveCount = 0;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003127 do {
3128 SkPoint pts[4];
3129 switch (iter.next(pts, false)) {
3130 case SkPath::kMove_Verb:
3131 case SkPath::kClose_Verb:
3132 break;
3133 case SkPath::kLine_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003134 w += winding_line(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003135 break;
3136 case SkPath::kQuad_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003137 w += winding_quad(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003138 break;
reed@google.com277c3f82013-05-31 15:17:50 +00003139 case SkPath::kConic_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003140 w += winding_conic(pts, x, y, iter.conicWeight(), &onCurveCount);
reed@google.com277c3f82013-05-31 15:17:50 +00003141 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003142 case SkPath::kCubic_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003143 w += winding_cubic(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003144 break;
3145 case SkPath::kDone_Verb:
3146 done = true;
3147 break;
reed@google.com277c3f82013-05-31 15:17:50 +00003148 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003149 } while (!done);
caryclark9aacd902015-12-14 08:38:09 -08003150 bool evenOddFill = SkPath::kEvenOdd_FillType == this->getFillType()
3151 || SkPath::kInverseEvenOdd_FillType == this->getFillType();
3152 if (evenOddFill) {
3153 w &= 1;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003154 }
caryclark9aacd902015-12-14 08:38:09 -08003155 if (w) {
3156 return !isInverse;
3157 }
3158 if (onCurveCount <= 1) {
3159 return SkToBool(onCurveCount) ^ isInverse;
3160 }
3161 if ((onCurveCount & 1) || evenOddFill) {
3162 return SkToBool(onCurveCount & 1) ^ isInverse;
3163 }
3164 // If the point touches an even number of curves, and the fill is winding, check for
3165 // coincidence. Count coincidence as places where the on curve points have identical tangents.
3166 iter.setPath(*this, true);
caryclark9cb5d752015-12-18 04:35:24 -08003167 done = false;
caryclark9aacd902015-12-14 08:38:09 -08003168 SkTDArray<SkVector> tangents;
3169 do {
3170 SkPoint pts[4];
3171 int oldCount = tangents.count();
3172 switch (iter.next(pts, false)) {
3173 case SkPath::kMove_Verb:
3174 case SkPath::kClose_Verb:
3175 break;
3176 case SkPath::kLine_Verb:
3177 tangent_line(pts, x, y, &tangents);
3178 break;
3179 case SkPath::kQuad_Verb:
3180 tangent_quad(pts, x, y, &tangents);
3181 break;
3182 case SkPath::kConic_Verb:
3183 tangent_conic(pts, x, y, iter.conicWeight(), &tangents);
3184 break;
3185 case SkPath::kCubic_Verb:
3186 tangent_cubic(pts, x, y, &tangents);
3187 break;
3188 case SkPath::kDone_Verb:
3189 done = true;
3190 break;
3191 }
3192 if (tangents.count() > oldCount) {
3193 int last = tangents.count() - 1;
3194 const SkVector& tangent = tangents[last];
3195 if (SkScalarNearlyZero(tangent.lengthSqd())) {
3196 tangents.remove(last);
3197 } else {
3198 for (int index = 0; index < last; ++index) {
3199 const SkVector& test = tangents[index];
3200 if (SkScalarNearlyZero(test.cross(tangent))
caryclark9cb5d752015-12-18 04:35:24 -08003201 && SkScalarSignAsInt(tangent.fX * test.fX) <= 0
3202 && SkScalarSignAsInt(tangent.fY * test.fY) <= 0) {
caryclark9aacd902015-12-14 08:38:09 -08003203 tangents.remove(last);
3204 tangents.removeShuffle(index);
3205 break;
3206 }
3207 }
3208 }
3209 }
3210 } while (!done);
3211 return SkToBool(tangents.count()) ^ isInverse;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003212}
fmalitaaa0df4e2015-12-01 09:13:23 -08003213
3214int SkPath::ConvertConicToQuads(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2,
3215 SkScalar w, SkPoint pts[], int pow2) {
3216 const SkConic conic(p0, p1, p2, w);
3217 return conic.chopIntoQuadsPOW2(pts, pow2);
3218}