blob: fbda7e8772f6770ee71de6748869f1e049d21a18 [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 }
caryclarkdcd1fcc2016-02-18 09:00:01 -0800208 return !fPathRef->countWeights() ||
209 !SkToBool(memcmp(fPathRef->conicWeights(), compare.fPathRef->conicWeights(),
caryclark8e7b19d2016-02-18 04:11:48 -0800210 fPathRef->countWeights() * sizeof(*fPathRef->conicWeights())));
211}
212
213bool SkPath::interpolate(const SkPath& ending, SkScalar weight, SkPath* out) const {
214 int verbCount = fPathRef->countVerbs();
215 if (verbCount != ending.fPathRef->countVerbs()) {
216 return false;
217 }
caryclarka1105382016-02-18 06:13:25 -0800218 if (!verbCount) {
219 return true;
220 }
caryclark8e7b19d2016-02-18 04:11:48 -0800221 out->reset();
222 out->addPath(*this);
223 fPathRef->interpolate(*ending.fPathRef, weight, out->fPathRef);
224 return true;
225}
226
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000227static inline bool check_edge_against_rect(const SkPoint& p0,
228 const SkPoint& p1,
229 const SkRect& rect,
reed026beb52015-06-10 14:23:15 -0700230 SkPathPriv::FirstDirection dir) {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000231 const SkPoint* edgeBegin;
232 SkVector v;
reed026beb52015-06-10 14:23:15 -0700233 if (SkPathPriv::kCW_FirstDirection == dir) {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000234 v = p1 - p0;
235 edgeBegin = &p0;
236 } else {
237 v = p0 - p1;
238 edgeBegin = &p1;
239 }
240 if (v.fX || v.fY) {
241 // check the cross product of v with the vec from edgeBegin to each rect corner
242 SkScalar yL = SkScalarMul(v.fY, rect.fLeft - edgeBegin->fX);
243 SkScalar xT = SkScalarMul(v.fX, rect.fTop - edgeBegin->fY);
244 SkScalar yR = SkScalarMul(v.fY, rect.fRight - edgeBegin->fX);
245 SkScalar xB = SkScalarMul(v.fX, rect.fBottom - edgeBegin->fY);
246 if ((xT < yL) || (xT < yR) || (xB < yL) || (xB < yR)) {
247 return false;
248 }
249 }
250 return true;
251}
252
253bool SkPath::conservativelyContainsRect(const SkRect& rect) const {
254 // This only handles non-degenerate convex paths currently.
255 if (kConvex_Convexity != this->getConvexity()) {
256 return false;
257 }
skia.committer@gmail.comcec8de62012-11-14 02:01:22 +0000258
reed026beb52015-06-10 14:23:15 -0700259 SkPathPriv::FirstDirection direction;
260 if (!SkPathPriv::CheapComputeFirstDirection(*this, &direction)) {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000261 return false;
262 }
263
264 SkPoint firstPt;
265 SkPoint prevPt;
266 RawIter iter(*this);
267 SkPath::Verb verb;
268 SkPoint pts[4];
269 SkDEBUGCODE(int moveCnt = 0;)
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000270 SkDEBUGCODE(int segmentCount = 0;)
271 SkDEBUGCODE(int closeCount = 0;)
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000272
273 while ((verb = iter.next(pts)) != kDone_Verb) {
274 int nextPt = -1;
275 switch (verb) {
276 case kMove_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000277 SkASSERT(!segmentCount && !closeCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000278 SkDEBUGCODE(++moveCnt);
279 firstPt = prevPt = pts[0];
280 break;
281 case kLine_Verb:
282 nextPt = 1;
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000283 SkASSERT(moveCnt && !closeCount);
284 SkDEBUGCODE(++segmentCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000285 break;
286 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000287 case kConic_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000288 SkASSERT(moveCnt && !closeCount);
289 SkDEBUGCODE(++segmentCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000290 nextPt = 2;
291 break;
292 case kCubic_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000293 SkASSERT(moveCnt && !closeCount);
294 SkDEBUGCODE(++segmentCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000295 nextPt = 3;
296 break;
297 case kClose_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000298 SkDEBUGCODE(++closeCount;)
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000299 break;
300 default:
301 SkDEBUGFAIL("unknown verb");
302 }
303 if (-1 != nextPt) {
reed220f9262014-12-17 08:21:04 -0800304 if (SkPath::kConic_Verb == verb) {
305 SkConic orig;
306 orig.set(pts, iter.conicWeight());
307 SkPoint quadPts[5];
308 int count = orig.chopIntoQuadsPOW2(quadPts, 1);
djsollenf2b340f2016-01-29 08:51:04 -0800309 SkASSERT_RELEASE(2 == count);
reed220f9262014-12-17 08:21:04 -0800310
311 if (!check_edge_against_rect(quadPts[0], quadPts[2], rect, direction)) {
312 return false;
313 }
314 if (!check_edge_against_rect(quadPts[2], quadPts[4], rect, direction)) {
315 return false;
316 }
317 } else {
318 if (!check_edge_against_rect(prevPt, pts[nextPt], rect, direction)) {
319 return false;
320 }
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000321 }
322 prevPt = pts[nextPt];
323 }
324 }
325
326 return check_edge_against_rect(prevPt, firstPt, rect, direction);
327}
328
robertphillips@google.com7101abe2013-10-29 22:45:37 +0000329uint32_t SkPath::getGenerationID() const {
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000330 uint32_t genID = fPathRef->genID();
djsollen523cda32015-02-17 08:06:32 -0800331#ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000332 SkASSERT((unsigned)fFillType < (1 << (32 - kPathRefGenIDBitCnt)));
333 genID |= static_cast<uint32_t>(fFillType) << kPathRefGenIDBitCnt;
334#endif
335 return genID;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000336}
djsollen@google.come63793a2012-03-21 15:39:03 +0000337
reed@android.com8a1c16f2008-12-17 15:59:43 +0000338void SkPath::reset() {
339 SkDEBUGCODE(this->validate();)
340
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000341 fPathRef.reset(SkPathRef::CreateEmpty());
bungeman@google.coma5809a32013-06-21 15:13:34 +0000342 this->resetFields();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000343}
344
345void SkPath::rewind() {
346 SkDEBUGCODE(this->validate();)
347
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000348 SkPathRef::Rewind(&fPathRef);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000349 this->resetFields();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000350}
351
fsb1475b02016-01-20 09:51:07 -0800352bool SkPath::isLastContourClosed() const {
353 int verbCount = fPathRef->countVerbs();
354 if (0 == verbCount) {
355 return false;
356 }
357 return kClose_Verb == fPathRef->atVerb(verbCount - 1);
358}
359
reed@google.com7e6c4d12012-05-10 14:05:43 +0000360bool SkPath::isLine(SkPoint line[2]) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000361 int verbCount = fPathRef->countVerbs();
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000362
commit-bot@chromium.orga62efcc2013-08-05 13:23:13 +0000363 if (2 == verbCount) {
364 SkASSERT(kMove_Verb == fPathRef->atVerb(0));
365 if (kLine_Verb == fPathRef->atVerb(1)) {
366 SkASSERT(2 == fPathRef->countPoints());
reed@google.com7e6c4d12012-05-10 14:05:43 +0000367 if (line) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000368 const SkPoint* pts = fPathRef->points();
reed@google.com7e6c4d12012-05-10 14:05:43 +0000369 line[0] = pts[0];
370 line[1] = pts[1];
371 }
372 return true;
373 }
374 }
375 return false;
376}
377
caryclark@google.comf1316942011-07-26 19:54:45 +0000378/*
379 Determines if path is a rect by keeping track of changes in direction
380 and looking for a loop either clockwise or counterclockwise.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000381
caryclark@google.comf1316942011-07-26 19:54:45 +0000382 The direction is computed such that:
383 0: vertical up
caryclark@google.comf68154a2012-11-21 15:18:06 +0000384 1: horizontal left
caryclark@google.comf1316942011-07-26 19:54:45 +0000385 2: vertical down
caryclark@google.comf68154a2012-11-21 15:18:06 +0000386 3: horizontal right
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000387
caryclark@google.comf1316942011-07-26 19:54:45 +0000388A rectangle cycles up/right/down/left or up/left/down/right.
389
390The test fails if:
391 The path is closed, and followed by a line.
392 A second move creates a new endpoint.
393 A diagonal line is parsed.
394 There's more than four changes of direction.
395 There's a discontinuity on the line (e.g., a move in the middle)
396 The line reverses direction.
caryclark@google.comf1316942011-07-26 19:54:45 +0000397 The path contains a quadratic or cubic.
398 The path contains fewer than four points.
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000399 *The rectangle doesn't complete a cycle.
400 *The final point isn't equal to the first point.
401
402 *These last two conditions we relax if we have a 3-edge path that would
403 form a rectangle if it were closed (as we do when we fill a path)
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000404
caryclark@google.comf1316942011-07-26 19:54:45 +0000405It's OK if the path has:
406 Several colinear line segments composing a rectangle side.
407 Single points on the rectangle side.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000408
caryclark@google.comf1316942011-07-26 19:54:45 +0000409The direction takes advantage of the corners found since opposite sides
410must travel in opposite directions.
411
412FIXME: Allow colinear quads and cubics to be treated like lines.
413FIXME: If the API passes fill-only, return true if the filled stroke
414 is a rectangle, though the caller failed to close the path.
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000415
416 first,last,next direction state-machine:
417 0x1 is set if the segment is horizontal
418 0x2 is set if the segment is moving to the right or down
419 thus:
420 two directions are opposites iff (dirA ^ dirB) == 0x2
421 two directions are perpendicular iff (dirA ^ dirB) == 0x1
skia.committer@gmail.comf5e67c12014-01-16 07:01:48 +0000422
caryclark@google.comf1316942011-07-26 19:54:45 +0000423 */
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000424static int rect_make_dir(SkScalar dx, SkScalar dy) {
425 return ((0 != dx) << 0) | ((dx > 0 || dy > 0) << 1);
426}
caryclark@google.comf68154a2012-11-21 15:18:06 +0000427bool SkPath::isRectContour(bool allowPartial, int* currVerb, const SkPoint** ptsPtr,
428 bool* isClosed, Direction* direction) const {
caryclark@google.comf1316942011-07-26 19:54:45 +0000429 int corners = 0;
430 SkPoint first, last;
caryclark@google.com56f233a2012-11-19 13:06:06 +0000431 const SkPoint* pts = *ptsPtr;
halcanary96fcdcc2015-08-27 07:41:13 -0700432 const SkPoint* savePts = nullptr;
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000433 first.set(0, 0);
434 last.set(0, 0);
435 int firstDirection = 0;
436 int lastDirection = 0;
437 int nextDirection = 0;
438 bool closedOrMoved = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000439 bool autoClose = false;
caryclark95bc5f32015-04-08 08:34:15 -0700440 bool insertClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000441 int verbCnt = fPathRef->countVerbs();
caryclark@google.com56f233a2012-11-19 13:06:06 +0000442 while (*currVerb < verbCnt && (!allowPartial || !autoClose)) {
caryclark95bc5f32015-04-08 08:34:15 -0700443 uint8_t verb = insertClose ? (uint8_t) kClose_Verb : fPathRef->atVerb(*currVerb);
444 switch (verb) {
caryclark@google.comf1316942011-07-26 19:54:45 +0000445 case kClose_Verb:
caryclark@google.com56f233a2012-11-19 13:06:06 +0000446 savePts = pts;
447 pts = *ptsPtr;
caryclark@google.comf1316942011-07-26 19:54:45 +0000448 autoClose = true;
caryclark95bc5f32015-04-08 08:34:15 -0700449 insertClose = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000450 case kLine_Verb: {
451 SkScalar left = last.fX;
452 SkScalar top = last.fY;
453 SkScalar right = pts->fX;
454 SkScalar bottom = pts->fY;
455 ++pts;
456 if (left != right && top != bottom) {
457 return false; // diagonal
458 }
459 if (left == right && top == bottom) {
460 break; // single point on side OK
461 }
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000462 nextDirection = rect_make_dir(right - left, bottom - top);
caryclark@google.comf1316942011-07-26 19:54:45 +0000463 if (0 == corners) {
464 firstDirection = nextDirection;
465 first = last;
466 last = pts[-1];
467 corners = 1;
468 closedOrMoved = false;
469 break;
470 }
471 if (closedOrMoved) {
472 return false; // closed followed by a line
473 }
caryclark@google.combfe90372012-11-21 13:56:20 +0000474 if (autoClose && nextDirection == firstDirection) {
475 break; // colinear with first
476 }
caryclark@google.comf1316942011-07-26 19:54:45 +0000477 closedOrMoved = autoClose;
478 if (lastDirection != nextDirection) {
479 if (++corners > 4) {
480 return false; // too many direction changes
481 }
482 }
483 last = pts[-1];
484 if (lastDirection == nextDirection) {
485 break; // colinear segment
486 }
487 // Possible values for corners are 2, 3, and 4.
488 // When corners == 3, nextDirection opposes firstDirection.
489 // Otherwise, nextDirection at corner 2 opposes corner 4.
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000490 int turn = firstDirection ^ (corners - 1);
caryclark@google.comf1316942011-07-26 19:54:45 +0000491 int directionCycle = 3 == corners ? 0 : nextDirection ^ turn;
492 if ((directionCycle ^ turn) != nextDirection) {
493 return false; // direction didn't follow cycle
494 }
495 break;
496 }
497 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000498 case kConic_Verb:
caryclark@google.comf1316942011-07-26 19:54:45 +0000499 case kCubic_Verb:
500 return false; // quadratic, cubic not allowed
501 case kMove_Verb:
caryclark95bc5f32015-04-08 08:34:15 -0700502 if (allowPartial && !autoClose && firstDirection) {
503 insertClose = true;
504 *currVerb -= 1; // try move again afterwards
505 goto addMissingClose;
506 }
caryclark@google.comf1316942011-07-26 19:54:45 +0000507 last = *pts++;
508 closedOrMoved = true;
509 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000510 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000511 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000512 break;
caryclark@google.comf1316942011-07-26 19:54:45 +0000513 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000514 *currVerb += 1;
caryclark@google.comf1316942011-07-26 19:54:45 +0000515 lastDirection = nextDirection;
caryclark95bc5f32015-04-08 08:34:15 -0700516addMissingClose:
517 ;
caryclark@google.comf1316942011-07-26 19:54:45 +0000518 }
519 // Success if 4 corners and first point equals last
caryclark@google.combfe90372012-11-21 13:56:20 +0000520 bool result = 4 == corners && (first == last || autoClose);
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000521 if (!result) {
522 // check if we are just an incomplete rectangle, in which case we can
523 // return true, but not claim to be closed.
524 // e.g.
525 // 3 sided rectangle
526 // 4 sided but the last edge is not long enough to reach the start
527 //
528 SkScalar closeX = first.x() - last.x();
529 SkScalar closeY = first.y() - last.y();
530 if (closeX && closeY) {
531 return false; // we're diagonal, abort (can we ever reach this?)
532 }
533 int closeDirection = rect_make_dir(closeX, closeY);
534 // make sure the close-segment doesn't double-back on itself
535 if (3 == corners || (4 == corners && closeDirection == lastDirection)) {
536 result = true;
537 autoClose = false; // we are not closed
538 }
539 }
caryclark@google.combfe90372012-11-21 13:56:20 +0000540 if (savePts) {
541 *ptsPtr = savePts;
542 }
caryclark@google.comf68154a2012-11-21 15:18:06 +0000543 if (result && isClosed) {
544 *isClosed = autoClose;
545 }
546 if (result && direction) {
sugoi@google.com12b4e272012-12-06 20:13:11 +0000547 *direction = firstDirection == ((lastDirection + 1) & 3) ? kCCW_Direction : kCW_Direction;
caryclark@google.comf68154a2012-11-21 15:18:06 +0000548 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000549 return result;
550}
551
robertphillips4f662e62014-12-29 14:06:51 -0800552bool SkPath::isRect(SkRect* rect, bool* isClosed, Direction* direction) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000553 SkDEBUGCODE(this->validate();)
554 int currVerb = 0;
555 const SkPoint* pts = fPathRef->points();
robertphillipsfe7c4272014-12-29 11:36:39 -0800556 const SkPoint* first = pts;
robertphillips4f662e62014-12-29 14:06:51 -0800557 if (!this->isRectContour(false, &currVerb, &pts, isClosed, direction)) {
robertphillipsfe7c4272014-12-29 11:36:39 -0800558 return false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000559 }
robertphillipsfe7c4272014-12-29 11:36:39 -0800560 if (rect) {
robertphillips4f662e62014-12-29 14:06:51 -0800561 int32_t num = SkToS32(pts - first);
562 if (num) {
563 rect->set(first, num);
robertphillipsfe7c4272014-12-29 11:36:39 -0800564 } else {
565 // 'pts' isn't updated for open rects
566 *rect = this->getBounds();
567 }
568 }
569 return true;
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000570}
skia.committer@gmail.com020b25b2013-06-22 07:00:58 +0000571
caryclark95bc5f32015-04-08 08:34:15 -0700572bool SkPath::isNestedFillRects(SkRect rects[2], Direction dirs[2]) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000573 SkDEBUGCODE(this->validate();)
574 int currVerb = 0;
575 const SkPoint* pts = fPathRef->points();
576 const SkPoint* first = pts;
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000577 Direction testDirs[2];
halcanary96fcdcc2015-08-27 07:41:13 -0700578 if (!isRectContour(true, &currVerb, &pts, nullptr, &testDirs[0])) {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000579 return false;
580 }
581 const SkPoint* last = pts;
582 SkRect testRects[2];
caryclark95bc5f32015-04-08 08:34:15 -0700583 bool isClosed;
584 if (isRectContour(false, &currVerb, &pts, &isClosed, &testDirs[1])) {
scroggo@google.com614f9e32013-05-09 18:05:32 +0000585 testRects[0].set(first, SkToS32(last - first));
caryclark95bc5f32015-04-08 08:34:15 -0700586 if (!isClosed) {
587 pts = fPathRef->points() + fPathRef->countPoints();
588 }
scroggo@google.com614f9e32013-05-09 18:05:32 +0000589 testRects[1].set(last, SkToS32(pts - last));
caryclark@google.com56f233a2012-11-19 13:06:06 +0000590 if (testRects[0].contains(testRects[1])) {
591 if (rects) {
592 rects[0] = testRects[0];
593 rects[1] = testRects[1];
594 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000595 if (dirs) {
596 dirs[0] = testDirs[0];
597 dirs[1] = testDirs[1];
598 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000599 return true;
600 }
601 if (testRects[1].contains(testRects[0])) {
602 if (rects) {
603 rects[0] = testRects[1];
604 rects[1] = testRects[0];
605 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000606 if (dirs) {
607 dirs[0] = testDirs[1];
608 dirs[1] = testDirs[0];
609 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000610 return true;
611 }
612 }
613 return false;
614}
615
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000616int SkPath::countPoints() const {
617 return fPathRef->countPoints();
618}
619
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000620int SkPath::getPoints(SkPoint dst[], int max) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000621 SkDEBUGCODE(this->validate();)
622
623 SkASSERT(max >= 0);
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000624 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000625 int count = SkMin32(max, fPathRef->countPoints());
mtklein8bf5d172015-12-09 13:15:07 -0800626 sk_careful_memcpy(dst, fPathRef->points(), count * sizeof(SkPoint));
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000627 return fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000628}
629
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000630SkPoint SkPath::getPoint(int index) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000631 if ((unsigned)index < (unsigned)fPathRef->countPoints()) {
632 return fPathRef->atPoint(index);
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000633 }
634 return SkPoint::Make(0, 0);
635}
636
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000637int SkPath::countVerbs() const {
638 return fPathRef->countVerbs();
639}
640
641static inline void copy_verbs_reverse(uint8_t* inorderDst,
642 const uint8_t* reversedSrc,
643 int count) {
644 for (int i = 0; i < count; ++i) {
645 inorderDst[i] = reversedSrc[~i];
646 }
647}
648
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000649int SkPath::getVerbs(uint8_t dst[], int max) const {
650 SkDEBUGCODE(this->validate();)
651
652 SkASSERT(max >= 0);
653 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000654 int count = SkMin32(max, fPathRef->countVerbs());
655 copy_verbs_reverse(dst, fPathRef->verbs(), count);
656 return fPathRef->countVerbs();
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000657}
658
reed@google.com294dd7b2011-10-11 11:58:32 +0000659bool SkPath::getLastPt(SkPoint* lastPt) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000660 SkDEBUGCODE(this->validate();)
661
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000662 int count = fPathRef->countPoints();
reed@google.com294dd7b2011-10-11 11:58:32 +0000663 if (count > 0) {
664 if (lastPt) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000665 *lastPt = fPathRef->atPoint(count - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000666 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000667 return true;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000668 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000669 if (lastPt) {
670 lastPt->set(0, 0);
671 }
672 return false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000673}
674
caryclarkaec25102015-04-29 08:28:30 -0700675void SkPath::setPt(int index, SkScalar x, SkScalar y) {
676 SkDEBUGCODE(this->validate();)
677
678 int count = fPathRef->countPoints();
679 if (count <= index) {
680 return;
681 } else {
682 SkPathRef::Editor ed(&fPathRef);
683 ed.atPoint(index)->set(x, y);
684 }
685}
686
reed@android.com8a1c16f2008-12-17 15:59:43 +0000687void SkPath::setLastPt(SkScalar x, SkScalar y) {
688 SkDEBUGCODE(this->validate();)
689
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000690 int count = fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000691 if (count == 0) {
692 this->moveTo(x, y);
693 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000694 SkPathRef::Editor ed(&fPathRef);
695 ed.atPoint(count-1)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000696 }
697}
698
reed@google.com04863fa2011-05-15 04:08:24 +0000699void SkPath::setConvexity(Convexity c) {
700 if (fConvexity != c) {
701 fConvexity = c;
reed@google.com04863fa2011-05-15 04:08:24 +0000702 }
703}
704
reed@android.com8a1c16f2008-12-17 15:59:43 +0000705//////////////////////////////////////////////////////////////////////////////
706// Construction methods
707
reed026beb52015-06-10 14:23:15 -0700708#define DIRTY_AFTER_EDIT \
709 do { \
710 fConvexity = kUnknown_Convexity; \
711 fFirstDirection = SkPathPriv::kUnknown_FirstDirection; \
reed@google.comb54455e2011-05-16 14:16:04 +0000712 } while (0)
713
reed@android.com8a1c16f2008-12-17 15:59:43 +0000714void SkPath::incReserve(U16CPU inc) {
715 SkDEBUGCODE(this->validate();)
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000716 SkPathRef::Editor(&fPathRef, inc, inc);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000717 SkDEBUGCODE(this->validate();)
718}
719
720void SkPath::moveTo(SkScalar x, SkScalar y) {
721 SkDEBUGCODE(this->validate();)
722
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000723 SkPathRef::Editor ed(&fPathRef);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000724
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000725 // remember our index
726 fLastMoveToIndex = fPathRef->countPoints();
727
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000728 ed.growForVerb(kMove_Verb)->set(x, y);
bsalomonb17c1292014-08-28 14:04:55 -0700729
730 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000731}
732
733void SkPath::rMoveTo(SkScalar x, SkScalar y) {
734 SkPoint pt;
735 this->getLastPt(&pt);
736 this->moveTo(pt.fX + x, pt.fY + y);
737}
738
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000739void SkPath::injectMoveToIfNeeded() {
740 if (fLastMoveToIndex < 0) {
741 SkScalar x, y;
742 if (fPathRef->countVerbs() == 0) {
743 x = y = 0;
744 } else {
745 const SkPoint& pt = fPathRef->atPoint(~fLastMoveToIndex);
746 x = pt.fX;
747 y = pt.fY;
748 }
749 this->moveTo(x, y);
750 }
751}
752
reed@android.com8a1c16f2008-12-17 15:59:43 +0000753void SkPath::lineTo(SkScalar x, SkScalar y) {
754 SkDEBUGCODE(this->validate();)
755
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000756 this->injectMoveToIfNeeded();
757
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000758 SkPathRef::Editor ed(&fPathRef);
759 ed.growForVerb(kLine_Verb)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000760
reed@google.comb54455e2011-05-16 14:16:04 +0000761 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000762}
763
764void SkPath::rLineTo(SkScalar x, SkScalar y) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000765 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000766 SkPoint pt;
767 this->getLastPt(&pt);
768 this->lineTo(pt.fX + x, pt.fY + y);
769}
770
771void SkPath::quadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
772 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000773
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000774 this->injectMoveToIfNeeded();
775
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000776 SkPathRef::Editor ed(&fPathRef);
777 SkPoint* pts = ed.growForVerb(kQuad_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000778 pts[0].set(x1, y1);
779 pts[1].set(x2, y2);
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000780
reed@google.comb54455e2011-05-16 14:16:04 +0000781 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000782}
783
784void SkPath::rQuadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000785 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000786 SkPoint pt;
787 this->getLastPt(&pt);
788 this->quadTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2);
789}
790
reed@google.com277c3f82013-05-31 15:17:50 +0000791void SkPath::conicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
792 SkScalar w) {
793 // check for <= 0 or NaN with this test
794 if (!(w > 0)) {
795 this->lineTo(x2, y2);
796 } else if (!SkScalarIsFinite(w)) {
797 this->lineTo(x1, y1);
798 this->lineTo(x2, y2);
799 } else if (SK_Scalar1 == w) {
800 this->quadTo(x1, y1, x2, y2);
801 } else {
802 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000803
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000804 this->injectMoveToIfNeeded();
805
reed@google.com277c3f82013-05-31 15:17:50 +0000806 SkPathRef::Editor ed(&fPathRef);
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000807 SkPoint* pts = ed.growForVerb(kConic_Verb, w);
reed@google.com277c3f82013-05-31 15:17:50 +0000808 pts[0].set(x1, y1);
809 pts[1].set(x2, y2);
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000810
reed@google.com277c3f82013-05-31 15:17:50 +0000811 DIRTY_AFTER_EDIT;
812 }
813}
814
815void SkPath::rConicTo(SkScalar dx1, SkScalar dy1, SkScalar dx2, SkScalar dy2,
816 SkScalar w) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000817 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@google.com277c3f82013-05-31 15:17:50 +0000818 SkPoint pt;
819 this->getLastPt(&pt);
820 this->conicTo(pt.fX + dx1, pt.fY + dy1, pt.fX + dx2, pt.fY + dy2, w);
821}
822
reed@android.com8a1c16f2008-12-17 15:59:43 +0000823void SkPath::cubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
824 SkScalar x3, SkScalar y3) {
825 SkDEBUGCODE(this->validate();)
826
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000827 this->injectMoveToIfNeeded();
828
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000829 SkPathRef::Editor ed(&fPathRef);
830 SkPoint* pts = ed.growForVerb(kCubic_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000831 pts[0].set(x1, y1);
832 pts[1].set(x2, y2);
833 pts[2].set(x3, y3);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000834
reed@google.comb54455e2011-05-16 14:16:04 +0000835 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000836}
837
838void SkPath::rCubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
839 SkScalar x3, SkScalar y3) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000840 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000841 SkPoint pt;
842 this->getLastPt(&pt);
843 this->cubicTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2,
844 pt.fX + x3, pt.fY + y3);
845}
846
847void SkPath::close() {
848 SkDEBUGCODE(this->validate();)
849
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000850 int count = fPathRef->countVerbs();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000851 if (count > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000852 switch (fPathRef->atVerb(count - 1)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000853 case kLine_Verb:
854 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000855 case kConic_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +0000856 case kCubic_Verb:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000857 case kMove_Verb: {
858 SkPathRef::Editor ed(&fPathRef);
859 ed.growForVerb(kClose_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000860 break;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000861 }
reed@google.com277c3f82013-05-31 15:17:50 +0000862 case kClose_Verb:
reed@google.comfa2f2a42013-05-30 15:29:48 +0000863 // don't add a close if it's the first verb or a repeat
reed@google.com7950a9e2013-05-30 14:57:55 +0000864 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000865 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000866 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000867 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000868 }
869 }
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000870
871 // signal that we need a moveTo to follow us (unless we're done)
872#if 0
873 if (fLastMoveToIndex >= 0) {
874 fLastMoveToIndex = ~fLastMoveToIndex;
875 }
876#else
877 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
878#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000879}
880
881///////////////////////////////////////////////////////////////////////////////
reed@google.comabf15c12011-01-18 20:35:51 +0000882
fmalitac08d53e2015-11-17 09:53:29 -0800883namespace {
884
885template <unsigned N>
886class PointIterator {
887public:
888 PointIterator(SkPath::Direction dir, unsigned startIndex)
889 : fCurrent(startIndex % N)
890 , fAdvance(dir == SkPath::kCW_Direction ? 1 : N - 1) { }
891
892 const SkPoint& current() const {
893 SkASSERT(fCurrent < N);
894 return fPts[fCurrent];
895 }
896
897 const SkPoint& next() {
898 fCurrent = (fCurrent + fAdvance) % N;
899 return this->current();
900 }
901
902protected:
903 SkPoint fPts[N];
904
905private:
906 unsigned fCurrent;
907 unsigned fAdvance;
908};
909
910class RectPointIterator : public PointIterator<4> {
911public:
912 RectPointIterator(const SkRect& rect, SkPath::Direction dir, unsigned startIndex)
913 : PointIterator(dir, startIndex) {
914
915 fPts[0] = SkPoint::Make(rect.fLeft, rect.fTop);
916 fPts[1] = SkPoint::Make(rect.fRight, rect.fTop);
917 fPts[2] = SkPoint::Make(rect.fRight, rect.fBottom);
918 fPts[3] = SkPoint::Make(rect.fLeft, rect.fBottom);
919 }
920};
921
922class OvalPointIterator : public PointIterator<4> {
923public:
924 OvalPointIterator(const SkRect& oval, SkPath::Direction dir, unsigned startIndex)
925 : PointIterator(dir, startIndex) {
926
927 const SkScalar cx = oval.centerX();
928 const SkScalar cy = oval.centerY();
929
930 fPts[0] = SkPoint::Make(cx, oval.fTop);
931 fPts[1] = SkPoint::Make(oval.fRight, cy);
932 fPts[2] = SkPoint::Make(cx, oval.fBottom);
933 fPts[3] = SkPoint::Make(oval.fLeft, cy);
934 }
935};
936
937class RRectPointIterator : public PointIterator<8> {
938public:
939 RRectPointIterator(const SkRRect& rrect, SkPath::Direction dir, unsigned startIndex)
940 : PointIterator(dir, startIndex) {
941
942 const SkRect& bounds = rrect.getBounds();
943 const SkScalar L = bounds.fLeft;
944 const SkScalar T = bounds.fTop;
945 const SkScalar R = bounds.fRight;
946 const SkScalar B = bounds.fBottom;
947
948 fPts[0] = SkPoint::Make(L + rrect.radii(SkRRect::kUpperLeft_Corner).fX, T);
949 fPts[1] = SkPoint::Make(R - rrect.radii(SkRRect::kUpperRight_Corner).fX, T);
950 fPts[2] = SkPoint::Make(R, T + rrect.radii(SkRRect::kUpperRight_Corner).fY);
951 fPts[3] = SkPoint::Make(R, B - rrect.radii(SkRRect::kLowerRight_Corner).fY);
952 fPts[4] = SkPoint::Make(R - rrect.radii(SkRRect::kLowerRight_Corner).fX, B);
953 fPts[5] = SkPoint::Make(L + rrect.radii(SkRRect::kLowerLeft_Corner).fX, B);
954 fPts[6] = SkPoint::Make(L, B - rrect.radii(SkRRect::kLowerLeft_Corner).fY);
955 fPts[7] = SkPoint::Make(L, T + rrect.radii(SkRRect::kUpperLeft_Corner).fY);
956 }
957};
958
959} // anonymous namespace
960
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000961static void assert_known_direction(int dir) {
962 SkASSERT(SkPath::kCW_Direction == dir || SkPath::kCCW_Direction == dir);
963}
964
reed@android.com8a1c16f2008-12-17 15:59:43 +0000965void SkPath::addRect(const SkRect& rect, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -0800966 this->addRect(rect, dir, 0);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000967}
968
969void SkPath::addRect(SkScalar left, SkScalar top, SkScalar right,
970 SkScalar bottom, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -0800971 this->addRect(SkRect::MakeLTRB(left, top, right, bottom), dir, 0);
972}
973
974void SkPath::addRect(const SkRect &rect, Direction dir, unsigned startIndex) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000975 assert_known_direction(dir);
reed026beb52015-06-10 14:23:15 -0700976 fFirstDirection = this->hasOnlyMoveTos() ?
fmalitac08d53e2015-11-17 09:53:29 -0800977 (SkPathPriv::FirstDirection)dir : SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000978 SkAutoDisableDirectionCheck addc(this);
fmalitac08d53e2015-11-17 09:53:29 -0800979 SkAutoPathBoundsUpdate apbu(this, rect);
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000980
fmalitac08d53e2015-11-17 09:53:29 -0800981 SkDEBUGCODE(int initialVerbCount = this->countVerbs());
reed@android.com8a1c16f2008-12-17 15:59:43 +0000982
fmalitac08d53e2015-11-17 09:53:29 -0800983 const int kVerbs = 5; // moveTo + 3x lineTo + close
984 this->incReserve(kVerbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000985
fmalitac08d53e2015-11-17 09:53:29 -0800986 RectPointIterator iter(rect, dir, startIndex);
987
988 this->moveTo(iter.current());
989 this->lineTo(iter.next());
990 this->lineTo(iter.next());
991 this->lineTo(iter.next());
reed@android.com8a1c16f2008-12-17 15:59:43 +0000992 this->close();
fmalitac08d53e2015-11-17 09:53:29 -0800993
994 SkASSERT(this->countVerbs() == initialVerbCount + kVerbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000995}
996
reed@google.com744faba2012-05-29 19:54:52 +0000997void SkPath::addPoly(const SkPoint pts[], int count, bool close) {
998 SkDEBUGCODE(this->validate();)
999 if (count <= 0) {
1000 return;
1001 }
1002
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +00001003 fLastMoveToIndex = fPathRef->countPoints();
1004
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001005 // +close makes room for the extra kClose_Verb
1006 SkPathRef::Editor ed(&fPathRef, count+close, count);
1007
1008 ed.growForVerb(kMove_Verb)->set(pts[0].fX, pts[0].fY);
reed@google.com744faba2012-05-29 19:54:52 +00001009 if (count > 1) {
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001010 SkPoint* p = ed.growForRepeatedVerb(kLine_Verb, count - 1);
1011 memcpy(p, &pts[1], (count-1) * sizeof(SkPoint));
reed@google.com744faba2012-05-29 19:54:52 +00001012 }
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001013
reed@google.com744faba2012-05-29 19:54:52 +00001014 if (close) {
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001015 ed.growForVerb(kClose_Verb);
caryclark63c684a2015-02-25 09:04:04 -08001016 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
reed@google.com744faba2012-05-29 19:54:52 +00001017 }
1018
reed@google.com744faba2012-05-29 19:54:52 +00001019 DIRTY_AFTER_EDIT;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001020 SkDEBUGCODE(this->validate();)
reed@google.com744faba2012-05-29 19:54:52 +00001021}
1022
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001023#include "SkGeometry.h"
1024
reedf90ea012015-01-29 12:03:58 -08001025static bool arc_is_lone_point(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
1026 SkPoint* pt) {
1027 if (0 == sweepAngle && (0 == startAngle || SkIntToScalar(360) == startAngle)) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001028 // Chrome uses this path to move into and out of ovals. If not
1029 // treated as a special case the moves can distort the oval's
1030 // bounding box (and break the circle special case).
reedf90ea012015-01-29 12:03:58 -08001031 pt->set(oval.fRight, oval.centerY());
1032 return true;
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001033 } else if (0 == oval.width() && 0 == oval.height()) {
1034 // Chrome will sometimes create 0 radius round rects. Having degenerate
1035 // quad segments in the path prevents the path from being recognized as
1036 // a rect.
1037 // TODO: optimizing the case where only one of width or height is zero
1038 // should also be considered. This case, however, doesn't seem to be
1039 // as common as the single point case.
reedf90ea012015-01-29 12:03:58 -08001040 pt->set(oval.fRight, oval.fTop);
1041 return true;
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001042 }
reedf90ea012015-01-29 12:03:58 -08001043 return false;
1044}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001045
reedd5d27d92015-02-09 13:54:43 -08001046// Return the unit vectors pointing at the start/stop points for the given start/sweep angles
1047//
1048static void angles_to_unit_vectors(SkScalar startAngle, SkScalar sweepAngle,
1049 SkVector* startV, SkVector* stopV, SkRotationDirection* dir) {
1050 startV->fY = SkScalarSinCos(SkDegreesToRadians(startAngle), &startV->fX);
1051 stopV->fY = SkScalarSinCos(SkDegreesToRadians(startAngle + sweepAngle), &stopV->fX);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001052
1053 /* If the sweep angle is nearly (but less than) 360, then due to precision
reedd5d27d92015-02-09 13:54:43 -08001054 loss in radians-conversion and/or sin/cos, we may end up with coincident
1055 vectors, which will fool SkBuildQuadArc into doing nothing (bad) instead
1056 of drawing a nearly complete circle (good).
1057 e.g. canvas.drawArc(0, 359.99, ...)
1058 -vs- canvas.drawArc(0, 359.9, ...)
1059 We try to detect this edge case, and tweak the stop vector
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001060 */
reedd5d27d92015-02-09 13:54:43 -08001061 if (*startV == *stopV) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001062 SkScalar sw = SkScalarAbs(sweepAngle);
1063 if (sw < SkIntToScalar(360) && sw > SkIntToScalar(359)) {
1064 SkScalar stopRad = SkDegreesToRadians(startAngle + sweepAngle);
1065 // make a guess at a tiny angle (in radians) to tweak by
1066 SkScalar deltaRad = SkScalarCopySign(SK_Scalar1/512, sweepAngle);
1067 // not sure how much will be enough, so we use a loop
1068 do {
1069 stopRad -= deltaRad;
reedd5d27d92015-02-09 13:54:43 -08001070 stopV->fY = SkScalarSinCos(stopRad, &stopV->fX);
1071 } while (*startV == *stopV);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001072 }
1073 }
reedd5d27d92015-02-09 13:54:43 -08001074 *dir = sweepAngle > 0 ? kCW_SkRotationDirection : kCCW_SkRotationDirection;
1075}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001076
reed9e779d42015-02-17 11:43:14 -08001077/**
1078 * If this returns 0, then the caller should just line-to the singlePt, else it should
1079 * ignore singlePt and append the specified number of conics.
1080 */
reedd5d27d92015-02-09 13:54:43 -08001081static int build_arc_conics(const SkRect& oval, const SkVector& start, const SkVector& stop,
reed9e779d42015-02-17 11:43:14 -08001082 SkRotationDirection dir, SkConic conics[SkConic::kMaxConicsForArc],
1083 SkPoint* singlePt) {
reedd5d27d92015-02-09 13:54:43 -08001084 SkMatrix matrix;
1085
1086 matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height()));
1087 matrix.postTranslate(oval.centerX(), oval.centerY());
1088
reed9e779d42015-02-17 11:43:14 -08001089 int count = SkConic::BuildUnitArc(start, stop, dir, &matrix, conics);
1090 if (0 == count) {
1091 matrix.mapXY(start.x(), start.y(), singlePt);
1092 }
1093 return count;
reedd5d27d92015-02-09 13:54:43 -08001094}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001095
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001096void SkPath::addRoundRect(const SkRect& rect, const SkScalar radii[],
reed@android.com8a1c16f2008-12-17 15:59:43 +00001097 Direction dir) {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001098 SkRRect rrect;
1099 rrect.setRectRadii(rect, (const SkVector*) radii);
1100 this->addRRect(rrect, dir);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001101}
1102
reed@google.com4ed0fb72012-12-12 20:48:18 +00001103void SkPath::addRRect(const SkRRect& rrect, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -08001104 // legacy start indices: 6 (CW) and 7(CCW)
1105 this->addRRect(rrect, dir, dir == kCW_Direction ? 6 : 7);
1106}
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001107
fmalitac08d53e2015-11-17 09:53:29 -08001108void SkPath::addRRect(const SkRRect &rrect, Direction dir, unsigned startIndex) {
1109 assert_known_direction(dir);
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001110
fmalitac08d53e2015-11-17 09:53:29 -08001111 if (rrect.isEmpty()) {
1112 return;
reed1b28a3a2014-12-17 14:42:09 -08001113 }
fmalitac08d53e2015-11-17 09:53:29 -08001114
caryclarkda707bf2015-11-19 14:47:43 -08001115 bool isRRect = hasOnlyMoveTos();
fmalitac08d53e2015-11-17 09:53:29 -08001116 const SkRect& bounds = rrect.getBounds();
1117
1118 if (rrect.isRect()) {
1119 // degenerate(rect) => radii points are collapsing
1120 this->addRect(bounds, dir, (startIndex + 1) / 2);
1121 } else if (rrect.isOval()) {
1122 // degenerate(oval) => line points are collapsing
1123 this->addOval(bounds, dir, startIndex / 2);
1124 } else {
1125 fFirstDirection = this->hasOnlyMoveTos() ?
1126 (SkPathPriv::FirstDirection)dir : SkPathPriv::kUnknown_FirstDirection;
1127
1128 SkAutoPathBoundsUpdate apbu(this, bounds);
1129 SkAutoDisableDirectionCheck addc(this);
1130
1131 // we start with a conic on odd indices when moving CW vs. even indices when moving CCW
1132 const bool startsWithConic = ((startIndex & 1) == (dir == kCW_Direction));
1133 const SkScalar weight = SK_ScalarRoot2Over2;
1134
1135 SkDEBUGCODE(int initialVerbCount = this->countVerbs());
1136 const int kVerbs = startsWithConic
1137 ? 9 // moveTo + 4x conicTo + 3x lineTo + close
1138 : 10; // moveTo + 4x lineTo + 4x conicTo + close
1139 this->incReserve(kVerbs);
1140
1141 RRectPointIterator rrectIter(rrect, dir, startIndex);
1142 // Corner iterator indices follow the collapsed radii model,
1143 // adjusted such that the start pt is "behind" the radii start pt.
1144 const unsigned rectStartIndex = startIndex / 2 + (dir == kCW_Direction ? 0 : 1);
1145 RectPointIterator rectIter(bounds, dir, rectStartIndex);
1146
1147 this->moveTo(rrectIter.current());
1148 if (startsWithConic) {
1149 for (unsigned i = 0; i < 3; ++i) {
1150 this->conicTo(rectIter.next(), rrectIter.next(), weight);
1151 this->lineTo(rrectIter.next());
1152 }
1153 this->conicTo(rectIter.next(), rrectIter.next(), weight);
1154 // final lineTo handled by close().
1155 } else {
1156 for (unsigned i = 0; i < 4; ++i) {
1157 this->lineTo(rrectIter.next());
1158 this->conicTo(rectIter.next(), rrectIter.next(), weight);
1159 }
1160 }
1161 this->close();
1162
caryclarkda707bf2015-11-19 14:47:43 -08001163 SkPathRef::Editor ed(&fPathRef);
1164 ed.setIsRRect(isRRect);
1165
fmalitac08d53e2015-11-17 09:53:29 -08001166 SkASSERT(this->countVerbs() == initialVerbCount + kVerbs);
1167 }
1168
1169 SkDEBUGCODE(fPathRef->validate();)
reed@google.com4ed0fb72012-12-12 20:48:18 +00001170}
1171
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001172bool SkPath::hasOnlyMoveTos() const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001173 int count = fPathRef->countVerbs();
1174 const uint8_t* verbs = const_cast<const SkPathRef*>(fPathRef.get())->verbsMemBegin();
1175 for (int i = 0; i < count; ++i) {
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001176 if (*verbs == kLine_Verb ||
1177 *verbs == kQuad_Verb ||
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001178 *verbs == kConic_Verb ||
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001179 *verbs == kCubic_Verb) {
1180 return false;
1181 }
1182 ++verbs;
1183 }
1184 return true;
1185}
1186
caryclarkd49a86a2016-02-22 12:44:54 -08001187bool SkPath::isZeroLength() const {
1188 int count = fPathRef->countPoints();
1189 if (count < 2) {
1190 return true;
1191 }
1192 const SkPoint* pts = fPathRef.get()->points();
1193 const SkPoint& first = *pts;
1194 for (int index = 1; index < count; ++index) {
1195 if (first != pts[index]) {
1196 return false;
1197 }
1198 }
1199 return true;
1200}
1201
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001202void SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
1203 Direction dir) {
1204 assert_known_direction(dir);
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001205
humper@google.com75e3ca12013-04-08 21:44:11 +00001206 if (rx < 0 || ry < 0) {
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001207 SkErrorInternals::SetError( kInvalidArgument_SkError,
humper@google.com75e3ca12013-04-08 21:44:11 +00001208 "I got %f and %f as radii to SkPath::AddRoundRect, "
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001209 "but negative radii are not allowed.",
humper@google.com75e3ca12013-04-08 21:44:11 +00001210 SkScalarToDouble(rx), SkScalarToDouble(ry) );
1211 return;
1212 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001213
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001214 SkRRect rrect;
1215 rrect.setRectXY(rect, rx, ry);
1216 this->addRRect(rrect, dir);
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001217}
1218
reed@android.com8a1c16f2008-12-17 15:59:43 +00001219void SkPath::addOval(const SkRect& oval, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -08001220 // legacy start index: 1
1221 this->addOval(oval, dir, 1);
1222}
1223
1224void SkPath::addOval(const SkRect &oval, Direction dir, unsigned startPointIndex) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +00001225 assert_known_direction(dir);
1226
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001227 /* If addOval() is called after previous moveTo(),
1228 this path is still marked as an oval. This is used to
1229 fit into WebKit's calling sequences.
1230 We can't simply check isEmpty() in this case, as additional
1231 moveTo() would mark the path non empty.
1232 */
robertphillips@google.com466310d2013-12-03 16:43:54 +00001233 bool isOval = hasOnlyMoveTos();
1234 if (isOval) {
reed026beb52015-06-10 14:23:15 -07001235 fFirstDirection = (SkPathPriv::FirstDirection)dir;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001236 } else {
reed026beb52015-06-10 14:23:15 -07001237 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001238 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001239
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001240 SkAutoDisableDirectionCheck addc(this);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001241 SkAutoPathBoundsUpdate apbu(this, oval);
1242
fmalitac08d53e2015-11-17 09:53:29 -08001243 SkDEBUGCODE(int initialVerbCount = this->countVerbs());
1244 const int kVerbs = 6; // moveTo + 4x conicTo + close
1245 this->incReserve(kVerbs);
1246
1247 OvalPointIterator ovalIter(oval, dir, startPointIndex);
1248 // The corner iterator pts are tracking "behind" the oval/radii pts.
1249 RectPointIterator rectIter(oval, dir, startPointIndex + (dir == kCW_Direction ? 0 : 1));
reed220f9262014-12-17 08:21:04 -08001250 const SkScalar weight = SK_ScalarRoot2Over2;
1251
fmalitac08d53e2015-11-17 09:53:29 -08001252 this->moveTo(ovalIter.current());
1253 for (unsigned i = 0; i < 4; ++i) {
1254 this->conicTo(rectIter.next(), ovalIter.next(), weight);
reed220f9262014-12-17 08:21:04 -08001255 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001256 this->close();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001257
fmalitac08d53e2015-11-17 09:53:29 -08001258 SkASSERT(this->countVerbs() == initialVerbCount + kVerbs);
1259
robertphillips@google.com466310d2013-12-03 16:43:54 +00001260 SkPathRef::Editor ed(&fPathRef);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001261
robertphillips@google.com466310d2013-12-03 16:43:54 +00001262 ed.setIsOval(isOval);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001263}
1264
reed@android.com8a1c16f2008-12-17 15:59:43 +00001265void SkPath::addCircle(SkScalar x, SkScalar y, SkScalar r, Direction dir) {
1266 if (r > 0) {
fmalitac08d53e2015-11-17 09:53:29 -08001267 this->addOval(SkRect::MakeLTRB(x - r, y - r, x + r, y + r), dir);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001268 }
1269}
1270
reed@android.com8a1c16f2008-12-17 15:59:43 +00001271void SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
1272 bool forceMoveTo) {
1273 if (oval.width() < 0 || oval.height() < 0) {
1274 return;
1275 }
1276
reedf90ea012015-01-29 12:03:58 -08001277 if (fPathRef->countVerbs() == 0) {
1278 forceMoveTo = true;
1279 }
1280
1281 SkPoint lonePt;
1282 if (arc_is_lone_point(oval, startAngle, sweepAngle, &lonePt)) {
1283 forceMoveTo ? this->moveTo(lonePt) : this->lineTo(lonePt);
1284 return;
1285 }
1286
reedd5d27d92015-02-09 13:54:43 -08001287 SkVector startV, stopV;
1288 SkRotationDirection dir;
1289 angles_to_unit_vectors(startAngle, sweepAngle, &startV, &stopV, &dir);
1290
reed9e779d42015-02-17 11:43:14 -08001291 SkPoint singlePt;
reedd5d27d92015-02-09 13:54:43 -08001292 SkConic conics[SkConic::kMaxConicsForArc];
reed9e779d42015-02-17 11:43:14 -08001293 int count = build_arc_conics(oval, startV, stopV, dir, conics, &singlePt);
reedd5d27d92015-02-09 13:54:43 -08001294 if (count) {
1295 this->incReserve(count * 2 + 1);
1296 const SkPoint& pt = conics[0].fPts[0];
1297 forceMoveTo ? this->moveTo(pt) : this->lineTo(pt);
1298 for (int i = 0; i < count; ++i) {
1299 this->conicTo(conics[i].fPts[1], conics[i].fPts[2], conics[i].fW);
1300 }
reed9e779d42015-02-17 11:43:14 -08001301 } else {
1302 forceMoveTo ? this->moveTo(singlePt) : this->lineTo(singlePt);
reedd5d27d92015-02-09 13:54:43 -08001303 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001304}
1305
caryclark55d49052016-01-23 05:07:04 -08001306// This converts the SVG arc to conics.
1307// Partly adapted from Niko's code in kdelibs/kdecore/svgicons.
1308// Then transcribed from webkit/chrome's SVGPathNormalizer::decomposeArcToCubic()
1309// See also SVG implementation notes:
1310// http://www.w3.org/TR/SVG/implnote.html#ArcConversionEndpointToCenter
1311// Note that arcSweep bool value is flipped from the original implementation.
1312void SkPath::arcTo(SkScalar rx, SkScalar ry, SkScalar angle, SkPath::ArcSize arcLarge,
1313 SkPath::Direction arcSweep, SkScalar x, SkScalar y) {
caryclarkf1d41512016-02-09 10:30:22 -08001314 this->injectMoveToIfNeeded();
caryclark55d49052016-01-23 05:07:04 -08001315 SkPoint srcPts[2];
1316 this->getLastPt(&srcPts[0]);
1317 // If rx = 0 or ry = 0 then this arc is treated as a straight line segment (a "lineto")
1318 // joining the endpoints.
1319 // http://www.w3.org/TR/SVG/implnote.html#ArcOutOfRangeParameters
1320 if (!rx || !ry) {
caryclarkfc752532016-01-25 05:39:04 -08001321 this->lineTo(x, y);
caryclark55d49052016-01-23 05:07:04 -08001322 return;
1323 }
1324 // If the current point and target point for the arc are identical, it should be treated as a
1325 // zero length path. This ensures continuity in animations.
1326 srcPts[1].set(x, y);
1327 if (srcPts[0] == srcPts[1]) {
caryclarkfc752532016-01-25 05:39:04 -08001328 this->lineTo(x, y);
caryclark55d49052016-01-23 05:07:04 -08001329 return;
1330 }
1331 rx = SkScalarAbs(rx);
1332 ry = SkScalarAbs(ry);
1333 SkVector midPointDistance = srcPts[0] - srcPts[1];
1334 midPointDistance *= 0.5f;
1335
1336 SkMatrix pointTransform;
1337 pointTransform.setRotate(-angle);
1338
1339 SkPoint transformedMidPoint;
1340 pointTransform.mapPoints(&transformedMidPoint, &midPointDistance, 1);
1341 SkScalar squareRx = rx * rx;
1342 SkScalar squareRy = ry * ry;
1343 SkScalar squareX = transformedMidPoint.fX * transformedMidPoint.fX;
1344 SkScalar squareY = transformedMidPoint.fY * transformedMidPoint.fY;
1345
1346 // Check if the radii are big enough to draw the arc, scale radii if not.
1347 // http://www.w3.org/TR/SVG/implnote.html#ArcCorrectionOutOfRangeRadii
1348 SkScalar radiiScale = squareX / squareRx + squareY / squareRy;
1349 if (radiiScale > 1) {
1350 radiiScale = SkScalarSqrt(radiiScale);
1351 rx *= radiiScale;
1352 ry *= radiiScale;
1353 }
1354
1355 pointTransform.setScale(1 / rx, 1 / ry);
1356 pointTransform.preRotate(-angle);
1357
1358 SkPoint unitPts[2];
1359 pointTransform.mapPoints(unitPts, srcPts, (int) SK_ARRAY_COUNT(unitPts));
1360 SkVector delta = unitPts[1] - unitPts[0];
1361
1362 SkScalar d = delta.fX * delta.fX + delta.fY * delta.fY;
1363 SkScalar scaleFactorSquared = SkTMax(1 / d - 0.25f, 0.f);
1364
1365 SkScalar scaleFactor = SkScalarSqrt(scaleFactorSquared);
1366 if (SkToBool(arcSweep) != SkToBool(arcLarge)) { // flipped from the original implementation
1367 scaleFactor = -scaleFactor;
1368 }
1369 delta.scale(scaleFactor);
1370 SkPoint centerPoint = unitPts[0] + unitPts[1];
1371 centerPoint *= 0.5f;
1372 centerPoint.offset(-delta.fY, delta.fX);
1373 unitPts[0] -= centerPoint;
1374 unitPts[1] -= centerPoint;
1375 SkScalar theta1 = SkScalarATan2(unitPts[0].fY, unitPts[0].fX);
1376 SkScalar theta2 = SkScalarATan2(unitPts[1].fY, unitPts[1].fX);
1377 SkScalar thetaArc = theta2 - theta1;
1378 if (thetaArc < 0 && !arcSweep) { // arcSweep flipped from the original implementation
1379 thetaArc += SK_ScalarPI * 2;
1380 } else if (thetaArc > 0 && arcSweep) { // arcSweep flipped from the original implementation
1381 thetaArc -= SK_ScalarPI * 2;
1382 }
1383 pointTransform.setRotate(angle);
1384 pointTransform.preScale(rx, ry);
1385
1386 int segments = SkScalarCeilToInt(SkScalarAbs(thetaArc / (SK_ScalarPI / 2)));
1387 SkScalar thetaWidth = thetaArc / segments;
1388 SkScalar t = SkScalarTan(0.5f * thetaWidth);
1389 if (!SkScalarIsFinite(t)) {
1390 return;
1391 }
1392 SkScalar startTheta = theta1;
1393 SkScalar w = SkScalarSqrt(SK_ScalarHalf + SkScalarCos(thetaWidth) * SK_ScalarHalf);
1394 for (int i = 0; i < segments; ++i) {
1395 SkScalar endTheta = startTheta + thetaWidth;
1396 SkScalar cosEndTheta, sinEndTheta = SkScalarSinCos(endTheta, &cosEndTheta);
1397
1398 unitPts[1].set(cosEndTheta, sinEndTheta);
1399 unitPts[1] += centerPoint;
1400 unitPts[0] = unitPts[1];
1401 unitPts[0].offset(t * sinEndTheta, -t * cosEndTheta);
1402 SkPoint mapped[2];
1403 pointTransform.mapPoints(mapped, unitPts, (int) SK_ARRAY_COUNT(unitPts));
1404 this->conicTo(mapped[0], mapped[1], w);
1405 startTheta = endTheta;
1406 }
1407}
1408
1409void SkPath::rArcTo(SkScalar rx, SkScalar ry, SkScalar xAxisRotate, SkPath::ArcSize largeArc,
1410 SkPath::Direction sweep, SkScalar dx, SkScalar dy) {
1411 SkPoint currentPoint;
1412 this->getLastPt(&currentPoint);
1413 this->arcTo(rx, ry, xAxisRotate, largeArc, sweep, currentPoint.fX + dx, currentPoint.fY + dy);
1414}
1415
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001416void SkPath::addArc(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001417 if (oval.isEmpty() || 0 == sweepAngle) {
1418 return;
1419 }
1420
1421 const SkScalar kFullCircleAngle = SkIntToScalar(360);
1422
1423 if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
1424 this->addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction);
reedc7789042015-01-29 12:59:11 -08001425 } else {
1426 this->arcTo(oval, startAngle, sweepAngle, true);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001427 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001428}
1429
1430/*
1431 Need to handle the case when the angle is sharp, and our computed end-points
1432 for the arc go behind pt1 and/or p2...
1433*/
reedc7789042015-01-29 12:59:11 -08001434void SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2, SkScalar radius) {
reeda8b326c2014-12-09 11:50:32 -08001435 if (radius == 0) {
1436 this->lineTo(x1, y1);
1437 return;
1438 }
1439
1440 SkVector before, after;
reed@google.comabf15c12011-01-18 20:35:51 +00001441
reed@android.com8a1c16f2008-12-17 15:59:43 +00001442 // need to know our prev pt so we can construct tangent vectors
1443 {
1444 SkPoint start;
1445 this->getLastPt(&start);
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001446 // Handle degenerate cases by adding a line to the first point and
1447 // bailing out.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001448 before.setNormalize(x1 - start.fX, y1 - start.fY);
1449 after.setNormalize(x2 - x1, y2 - y1);
1450 }
reed@google.comabf15c12011-01-18 20:35:51 +00001451
reed@android.com8a1c16f2008-12-17 15:59:43 +00001452 SkScalar cosh = SkPoint::DotProduct(before, after);
1453 SkScalar sinh = SkPoint::CrossProduct(before, after);
1454
1455 if (SkScalarNearlyZero(sinh)) { // angle is too tight
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001456 this->lineTo(x1, y1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001457 return;
1458 }
reed@google.comabf15c12011-01-18 20:35:51 +00001459
caryclark88651ae2016-01-20 11:55:11 -08001460 SkScalar dist = SkScalarAbs(SkScalarMulDiv(radius, SK_Scalar1 - cosh, sinh));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001461
1462 SkScalar xx = x1 - SkScalarMul(dist, before.fX);
1463 SkScalar yy = y1 - SkScalarMul(dist, before.fY);
caryclark88651ae2016-01-20 11:55:11 -08001464 after.setLength(dist);
1465 this->lineTo(xx, yy);
1466 SkScalar weight = SkScalarSqrt(SK_ScalarHalf + cosh * SK_ScalarHalf);
1467 this->conicTo(x1, y1, x1 + after.fX, y1 + after.fY, weight);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001468}
1469
1470///////////////////////////////////////////////////////////////////////////////
1471
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001472void SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy, AddPathMode mode) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001473 SkMatrix matrix;
1474
1475 matrix.setTranslate(dx, dy);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001476 this->addPath(path, matrix, mode);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001477}
1478
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001479void SkPath::addPath(const SkPath& path, const SkMatrix& matrix, AddPathMode mode) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001480 SkPathRef::Editor(&fPathRef, path.countVerbs(), path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001481
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001482 RawIter iter(path);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001483 SkPoint pts[4];
1484 Verb verb;
1485
1486 SkMatrix::MapPtsProc proc = matrix.getMapPtsProc();
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001487 bool firstVerb = true;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001488 while ((verb = iter.next(pts)) != kDone_Verb) {
1489 switch (verb) {
1490 case kMove_Verb:
1491 proc(matrix, &pts[0], &pts[0], 1);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001492 if (firstVerb && mode == kExtend_AddPathMode && !isEmpty()) {
1493 injectMoveToIfNeeded(); // In case last contour is closed
1494 this->lineTo(pts[0]);
1495 } else {
1496 this->moveTo(pts[0]);
1497 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001498 break;
1499 case kLine_Verb:
1500 proc(matrix, &pts[1], &pts[1], 1);
1501 this->lineTo(pts[1]);
1502 break;
1503 case kQuad_Verb:
1504 proc(matrix, &pts[1], &pts[1], 2);
1505 this->quadTo(pts[1], pts[2]);
1506 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001507 case kConic_Verb:
1508 proc(matrix, &pts[1], &pts[1], 2);
1509 this->conicTo(pts[1], pts[2], iter.conicWeight());
1510 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001511 case kCubic_Verb:
1512 proc(matrix, &pts[1], &pts[1], 3);
1513 this->cubicTo(pts[1], pts[2], pts[3]);
1514 break;
1515 case kClose_Verb:
1516 this->close();
1517 break;
1518 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001519 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001520 }
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001521 firstVerb = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001522 }
1523}
1524
1525///////////////////////////////////////////////////////////////////////////////
1526
reed@google.com277c3f82013-05-31 15:17:50 +00001527static int pts_in_verb(unsigned verb) {
1528 static const uint8_t gPtsInVerb[] = {
1529 1, // kMove
1530 1, // kLine
1531 2, // kQuad
1532 2, // kConic
1533 3, // kCubic
1534 0, // kClose
1535 0 // kDone
1536 };
1537
1538 SkASSERT(verb < SK_ARRAY_COUNT(gPtsInVerb));
1539 return gPtsInVerb[verb];
1540}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001541
reed@android.com8a1c16f2008-12-17 15:59:43 +00001542// ignore the last point of the 1st contour
1543void SkPath::reversePathTo(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001544 int i, vcount = path.fPathRef->countVerbs();
1545 // exit early if the path is empty, or just has a moveTo.
1546 if (vcount < 2) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001547 return;
1548 }
1549
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001550 SkPathRef::Editor(&fPathRef, vcount, path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001551
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001552 const uint8_t* verbs = path.fPathRef->verbs();
1553 const SkPoint* pts = path.fPathRef->points();
reed@google.com277c3f82013-05-31 15:17:50 +00001554 const SkScalar* conicWeights = path.fPathRef->conicWeights();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001555
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001556 SkASSERT(verbs[~0] == kMove_Verb);
1557 for (i = 1; i < vcount; ++i) {
reed@google.com277c3f82013-05-31 15:17:50 +00001558 unsigned v = verbs[~i];
1559 int n = pts_in_verb(v);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001560 if (n == 0) {
1561 break;
1562 }
1563 pts += n;
reed@google.com277c3f82013-05-31 15:17:50 +00001564 conicWeights += (SkPath::kConic_Verb == v);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001565 }
1566
1567 while (--i > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001568 switch (verbs[~i]) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001569 case kLine_Verb:
1570 this->lineTo(pts[-1].fX, pts[-1].fY);
1571 break;
1572 case kQuad_Verb:
1573 this->quadTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY);
1574 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001575 case kConic_Verb:
1576 this->conicTo(pts[-1], pts[-2], *--conicWeights);
1577 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001578 case kCubic_Verb:
1579 this->cubicTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY,
1580 pts[-3].fX, pts[-3].fY);
1581 break;
1582 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001583 SkDEBUGFAIL("bad verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001584 break;
1585 }
reed@google.com277c3f82013-05-31 15:17:50 +00001586 pts -= pts_in_verb(verbs[~i]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001587 }
1588}
1589
reed@google.com63d73742012-01-10 15:33:12 +00001590void SkPath::reverseAddPath(const SkPath& src) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001591 SkPathRef::Editor ed(&fPathRef, src.fPathRef->countPoints(), src.fPathRef->countVerbs());
reed@google.com63d73742012-01-10 15:33:12 +00001592
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001593 const SkPoint* pts = src.fPathRef->pointsEnd();
1594 // we will iterator through src's verbs backwards
1595 const uint8_t* verbs = src.fPathRef->verbsMemBegin(); // points at the last verb
1596 const uint8_t* verbsEnd = src.fPathRef->verbs(); // points just past the first verb
reed@google.com277c3f82013-05-31 15:17:50 +00001597 const SkScalar* conicWeights = src.fPathRef->conicWeightsEnd();
reed@google.com63d73742012-01-10 15:33:12 +00001598
1599 bool needMove = true;
1600 bool needClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001601 while (verbs < verbsEnd) {
1602 uint8_t v = *(verbs++);
reed@google.com277c3f82013-05-31 15:17:50 +00001603 int n = pts_in_verb(v);
reed@google.com63d73742012-01-10 15:33:12 +00001604
1605 if (needMove) {
1606 --pts;
1607 this->moveTo(pts->fX, pts->fY);
1608 needMove = false;
1609 }
1610 pts -= n;
1611 switch (v) {
1612 case kMove_Verb:
1613 if (needClose) {
1614 this->close();
1615 needClose = false;
1616 }
1617 needMove = true;
1618 pts += 1; // so we see the point in "if (needMove)" above
1619 break;
1620 case kLine_Verb:
1621 this->lineTo(pts[0]);
1622 break;
1623 case kQuad_Verb:
1624 this->quadTo(pts[1], pts[0]);
1625 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001626 case kConic_Verb:
1627 this->conicTo(pts[1], pts[0], *--conicWeights);
1628 break;
reed@google.com63d73742012-01-10 15:33:12 +00001629 case kCubic_Verb:
1630 this->cubicTo(pts[2], pts[1], pts[0]);
1631 break;
1632 case kClose_Verb:
1633 needClose = true;
1634 break;
1635 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00001636 SkDEBUGFAIL("unexpected verb");
reed@google.com63d73742012-01-10 15:33:12 +00001637 }
1638 }
1639}
1640
reed@android.com8a1c16f2008-12-17 15:59:43 +00001641///////////////////////////////////////////////////////////////////////////////
1642
1643void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
1644 SkMatrix matrix;
1645
1646 matrix.setTranslate(dx, dy);
1647 this->transform(matrix, dst);
1648}
1649
reed@android.com8a1c16f2008-12-17 15:59:43 +00001650static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
1651 int level = 2) {
1652 if (--level >= 0) {
1653 SkPoint tmp[7];
1654
1655 SkChopCubicAtHalf(pts, tmp);
1656 subdivide_cubic_to(path, &tmp[0], level);
1657 subdivide_cubic_to(path, &tmp[3], level);
1658 } else {
1659 path->cubicTo(pts[1], pts[2], pts[3]);
1660 }
1661}
1662
1663void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
1664 SkDEBUGCODE(this->validate();)
halcanary96fcdcc2015-08-27 07:41:13 -07001665 if (dst == nullptr) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001666 dst = (SkPath*)this;
1667 }
1668
tomhudson@google.com8d430182011-06-06 19:11:19 +00001669 if (matrix.hasPerspective()) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001670 SkPath tmp;
1671 tmp.fFillType = fFillType;
1672
1673 SkPath::Iter iter(*this, false);
1674 SkPoint pts[4];
1675 SkPath::Verb verb;
1676
reed@google.com4a3b7142012-05-16 17:16:46 +00001677 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001678 switch (verb) {
1679 case kMove_Verb:
1680 tmp.moveTo(pts[0]);
1681 break;
1682 case kLine_Verb:
1683 tmp.lineTo(pts[1]);
1684 break;
1685 case kQuad_Verb:
reed220f9262014-12-17 08:21:04 -08001686 // promote the quad to a conic
1687 tmp.conicTo(pts[1], pts[2],
1688 SkConic::TransformW(pts, SK_Scalar1, matrix));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001689 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001690 case kConic_Verb:
reed220f9262014-12-17 08:21:04 -08001691 tmp.conicTo(pts[1], pts[2],
1692 SkConic::TransformW(pts, iter.conicWeight(), matrix));
reed@google.com277c3f82013-05-31 15:17:50 +00001693 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001694 case kCubic_Verb:
1695 subdivide_cubic_to(&tmp, pts);
1696 break;
1697 case kClose_Verb:
1698 tmp.close();
1699 break;
1700 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001701 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001702 break;
1703 }
1704 }
1705
1706 dst->swap(tmp);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001707 SkPathRef::Editor ed(&dst->fPathRef);
1708 matrix.mapPoints(ed.points(), ed.pathRef()->countPoints());
reed026beb52015-06-10 14:23:15 -07001709 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001710 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001711 SkPathRef::CreateTransformedCopy(&dst->fPathRef, *fPathRef.get(), matrix);
1712
reed@android.com8a1c16f2008-12-17 15:59:43 +00001713 if (this != dst) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001714 dst->fFillType = fFillType;
reed@google.com2a6f8ab2011-10-25 18:41:23 +00001715 dst->fConvexity = fConvexity;
jvanverthb3eb6872014-10-24 07:12:51 -07001716 dst->fIsVolatile = fIsVolatile;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001717 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001718
reed026beb52015-06-10 14:23:15 -07001719 if (SkPathPriv::kUnknown_FirstDirection == fFirstDirection) {
1720 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001721 } else {
1722 SkScalar det2x2 =
1723 SkScalarMul(matrix.get(SkMatrix::kMScaleX), matrix.get(SkMatrix::kMScaleY)) -
1724 SkScalarMul(matrix.get(SkMatrix::kMSkewX), matrix.get(SkMatrix::kMSkewY));
1725 if (det2x2 < 0) {
herb9f4dbca2015-09-28 11:05:47 -07001726 dst->fFirstDirection = SkPathPriv::OppositeFirstDirection(
1727 (SkPathPriv::FirstDirection)fFirstDirection.load());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001728 } else if (det2x2 > 0) {
herb9f4dbca2015-09-28 11:05:47 -07001729 dst->fFirstDirection = fFirstDirection.load();
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001730 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001731 dst->fConvexity = kUnknown_Convexity;
reed026beb52015-06-10 14:23:15 -07001732 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001733 }
1734 }
1735
reed@android.com8a1c16f2008-12-17 15:59:43 +00001736 SkDEBUGCODE(dst->validate();)
1737 }
1738}
1739
reed@android.com8a1c16f2008-12-17 15:59:43 +00001740///////////////////////////////////////////////////////////////////////////////
1741///////////////////////////////////////////////////////////////////////////////
1742
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001743enum SegmentState {
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001744 kEmptyContour_SegmentState, // The current contour is empty. We may be
1745 // starting processing or we may have just
1746 // closed a contour.
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001747 kAfterMove_SegmentState, // We have seen a move, but nothing else.
1748 kAfterPrimitive_SegmentState // We have seen a primitive but not yet
1749 // closed the path. Also the initial state.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001750};
1751
1752SkPath::Iter::Iter() {
1753#ifdef SK_DEBUG
halcanary96fcdcc2015-08-27 07:41:13 -07001754 fPts = nullptr;
1755 fConicWeights = nullptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001756 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001757 fForceClose = fCloseLine = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001758 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001759#endif
1760 // need to init enough to make next() harmlessly return kDone_Verb
halcanary96fcdcc2015-08-27 07:41:13 -07001761 fVerbs = nullptr;
1762 fVerbStop = nullptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001763 fNeedClose = false;
1764}
1765
1766SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
1767 this->setPath(path, forceClose);
1768}
1769
1770void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001771 fPts = path.fPathRef->points();
1772 fVerbs = path.fPathRef->verbs();
1773 fVerbStop = path.fPathRef->verbsMemBegin();
reed@google.com277c3f82013-05-31 15:17:50 +00001774 fConicWeights = path.fPathRef->conicWeights() - 1; // begin one behind
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001775 fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org72785c42011-12-29 21:03:28 +00001776 fMoveTo.fX = fMoveTo.fY = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001777 fForceClose = SkToU8(forceClose);
1778 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001779 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001780}
1781
1782bool SkPath::Iter::isClosedContour() const {
halcanary96fcdcc2015-08-27 07:41:13 -07001783 if (fVerbs == nullptr || fVerbs == fVerbStop) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001784 return false;
1785 }
1786 if (fForceClose) {
1787 return true;
1788 }
1789
1790 const uint8_t* verbs = fVerbs;
1791 const uint8_t* stop = fVerbStop;
reed@google.comabf15c12011-01-18 20:35:51 +00001792
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001793 if (kMove_Verb == *(verbs - 1)) {
1794 verbs -= 1; // skip the initial moveto
reed@android.com8a1c16f2008-12-17 15:59:43 +00001795 }
1796
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001797 while (verbs > stop) {
1798 // verbs points one beyond the current verb, decrement first.
1799 unsigned v = *(--verbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001800 if (kMove_Verb == v) {
1801 break;
1802 }
1803 if (kClose_Verb == v) {
1804 return true;
1805 }
1806 }
1807 return false;
1808}
1809
1810SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001811 SkASSERT(pts);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001812 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001813 // A special case: if both points are NaN, SkPoint::operation== returns
1814 // false, but the iterator expects that they are treated as the same.
1815 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001816 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1817 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001818 return kClose_Verb;
1819 }
1820
reed@google.com9e25dbf2012-05-15 17:05:38 +00001821 pts[0] = fLastPt;
1822 pts[1] = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001823 fLastPt = fMoveTo;
1824 fCloseLine = true;
1825 return kLine_Verb;
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +00001826 } else {
1827 pts[0] = fMoveTo;
1828 return kClose_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001829 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001830}
1831
reed@google.com9e25dbf2012-05-15 17:05:38 +00001832const SkPoint& SkPath::Iter::cons_moveTo() {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001833 if (fSegmentState == kAfterMove_SegmentState) {
1834 // Set the first return pt to the move pt
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001835 fSegmentState = kAfterPrimitive_SegmentState;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001836 return fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001837 } else {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001838 SkASSERT(fSegmentState == kAfterPrimitive_SegmentState);
1839 // Set the first return pt to the last pt of the previous primitive.
reed@google.com9e25dbf2012-05-15 17:05:38 +00001840 return fPts[-1];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001841 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001842}
1843
caryclarke8c56662015-07-14 11:19:26 -07001844void SkPath::Iter::consumeDegenerateSegments(bool exact) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001845 // We need to step over anything that will not move the current draw point
1846 // forward before the next move is seen
1847 const uint8_t* lastMoveVerb = 0;
1848 const SkPoint* lastMovePt = 0;
halcanary96fcdcc2015-08-27 07:41:13 -07001849 const SkScalar* lastMoveWeight = nullptr;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001850 SkPoint lastPt = fLastPt;
1851 while (fVerbs != fVerbStop) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001852 unsigned verb = *(fVerbs - 1); // fVerbs is one beyond the current verb
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001853 switch (verb) {
1854 case kMove_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001855 // Keep a record of this most recent move
1856 lastMoveVerb = fVerbs;
1857 lastMovePt = fPts;
robertphillipsb8de1f42015-02-23 11:17:01 -08001858 lastMoveWeight = fConicWeights;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001859 lastPt = fPts[0];
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001860 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001861 fPts++;
1862 break;
1863
1864 case kClose_Verb:
schenney@chromium.org7e963602012-06-13 17:05:43 +00001865 // A close when we are in a segment is always valid except when it
1866 // follows a move which follows a segment.
1867 if (fSegmentState == kAfterPrimitive_SegmentState && !lastMoveVerb) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001868 return;
1869 }
1870 // A close at any other time must be ignored
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001871 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001872 break;
1873
1874 case kLine_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001875 if (!IsLineDegenerate(lastPt, fPts[0], 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++;
1887 break;
1888
reed@google.com277c3f82013-05-31 15:17:50 +00001889 case kConic_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001890 case kQuad_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001891 if (!IsQuadDegenerate(lastPt, fPts[0], fPts[1], 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 += 2;
reed@google.com277c3f82013-05-31 15:17:50 +00001903 fConicWeights += (kConic_Verb == verb);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001904 break;
1905
1906 case kCubic_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001907 if (!IsCubicDegenerate(lastPt, fPts[0], fPts[1], fPts[2], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001908 if (lastMoveVerb) {
1909 fVerbs = lastMoveVerb;
1910 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001911 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001912 return;
1913 }
1914 return;
1915 }
1916 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001917 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001918 fPts += 3;
1919 break;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001920
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001921 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001922 SkDEBUGFAIL("Should never see kDone_Verb");
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001923 }
1924 }
1925}
1926
reed@google.com4a3b7142012-05-16 17:16:46 +00001927SkPath::Verb SkPath::Iter::doNext(SkPoint ptsParam[4]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001928 SkASSERT(ptsParam);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001929
reed@android.com8a1c16f2008-12-17 15:59:43 +00001930 if (fVerbs == fVerbStop) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001931 // Close the curve if requested and if there is some curve to close
1932 if (fNeedClose && fSegmentState == kAfterPrimitive_SegmentState) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001933 if (kLine_Verb == this->autoClose(ptsParam)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001934 return kLine_Verb;
1935 }
1936 fNeedClose = false;
1937 return kClose_Verb;
1938 }
1939 return kDone_Verb;
1940 }
1941
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001942 // fVerbs is one beyond the current verb, decrement first
1943 unsigned verb = *(--fVerbs);
reed@google.com9e25dbf2012-05-15 17:05:38 +00001944 const SkPoint* SK_RESTRICT srcPts = fPts;
1945 SkPoint* SK_RESTRICT pts = ptsParam;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001946
1947 switch (verb) {
1948 case kMove_Verb:
1949 if (fNeedClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001950 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001951 verb = this->autoClose(pts);
1952 if (verb == kClose_Verb) {
1953 fNeedClose = false;
1954 }
1955 return (Verb)verb;
1956 }
1957 if (fVerbs == fVerbStop) { // might be a trailing moveto
1958 return kDone_Verb;
1959 }
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001960 fMoveTo = *srcPts;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001961 pts[0] = *srcPts;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001962 srcPts += 1;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001963 fSegmentState = kAfterMove_SegmentState;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001964 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001965 fNeedClose = fForceClose;
1966 break;
1967 case kLine_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001968 pts[0] = this->cons_moveTo();
1969 pts[1] = srcPts[0];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001970 fLastPt = srcPts[0];
1971 fCloseLine = false;
1972 srcPts += 1;
1973 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001974 case kConic_Verb:
1975 fConicWeights += 1;
1976 // fall-through
reed@android.com8a1c16f2008-12-17 15:59:43 +00001977 case kQuad_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001978 pts[0] = this->cons_moveTo();
1979 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001980 fLastPt = srcPts[1];
1981 srcPts += 2;
1982 break;
1983 case kCubic_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001984 pts[0] = this->cons_moveTo();
1985 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001986 fLastPt = srcPts[2];
1987 srcPts += 3;
1988 break;
1989 case kClose_Verb:
1990 verb = this->autoClose(pts);
1991 if (verb == kLine_Verb) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001992 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001993 } else {
1994 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001995 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001996 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001997 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001998 break;
1999 }
2000 fPts = srcPts;
2001 return (Verb)verb;
2002}
2003
2004///////////////////////////////////////////////////////////////////////////////
2005
reed@android.com8a1c16f2008-12-17 15:59:43 +00002006/*
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002007 Format in compressed buffer: [ptCount, verbCount, pts[], verbs[]]
reed@android.com8a1c16f2008-12-17 15:59:43 +00002008*/
2009
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002010size_t SkPath::writeToMemory(void* storage) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002011 SkDEBUGCODE(this->validate();)
2012
halcanary96fcdcc2015-08-27 07:41:13 -07002013 if (nullptr == storage) {
caryclark69006412016-02-17 12:16:27 -08002014 const int byteCount = sizeof(int32_t) * 2 + fPathRef->writeSize();
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002015 return SkAlign4(byteCount);
2016 }
2017
2018 SkWBuffer buffer(storage);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002019
robertphillips@google.com466310d2013-12-03 16:43:54 +00002020 int32_t packed = (fConvexity << kConvexity_SerializationShift) |
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002021 (fFillType << kFillType_SerializationShift) |
reed026beb52015-06-10 14:23:15 -07002022 (fFirstDirection << kDirection_SerializationShift) |
reed8f086022015-06-11 14:22:19 -07002023 (fIsVolatile << kIsVolatile_SerializationShift) |
2024 kCurrent_Version;
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002025
robertphillips@google.com2972bb52012-08-07 17:32:51 +00002026 buffer.write32(packed);
caryclark69006412016-02-17 12:16:27 -08002027 buffer.write32(fLastMoveToIndex);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002028
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002029 fPathRef->writeToBuffer(&buffer);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002030
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002031 buffer.padToAlign4();
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002032 return buffer.pos();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002033}
2034
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002035size_t SkPath::readFromMemory(const void* storage, size_t length) {
2036 SkRBufferWithSizeCheck buffer(storage, length);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002037
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00002038 int32_t packed;
2039 if (!buffer.readS32(&packed)) {
2040 return 0;
2041 }
2042
reed8f086022015-06-11 14:22:19 -07002043 unsigned version = packed & 0xFF;
caryclark69006412016-02-17 12:16:27 -08002044 if (version >= kPathPrivLastMoveToIndex_Version && !buffer.readS32(&fLastMoveToIndex)) {
2045 return 0;
2046 }
mtklein1b249332015-07-07 12:21:21 -07002047
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002048 fConvexity = (packed >> kConvexity_SerializationShift) & 0xFF;
2049 fFillType = (packed >> kFillType_SerializationShift) & 0xFF;
reed8f086022015-06-11 14:22:19 -07002050 uint8_t dir = (packed >> kDirection_SerializationShift) & 0x3;
jvanverthb3eb6872014-10-24 07:12:51 -07002051 fIsVolatile = (packed >> kIsVolatile_SerializationShift) & 0x1;
commit-bot@chromium.orgfed2ab62014-01-23 15:16:05 +00002052 SkPathRef* pathRef = SkPathRef::CreateFromBuffer(&buffer);
ajumaf8aec582016-01-13 13:46:31 -08002053 if (!pathRef) {
2054 return 0;
2055 }
2056
2057 fPathRef.reset(pathRef);
2058 SkDEBUGCODE(this->validate();)
2059 buffer.skipToAlign4();
reed@google.comabf15c12011-01-18 20:35:51 +00002060
reed8f086022015-06-11 14:22:19 -07002061 // compatibility check
2062 if (version < kPathPrivFirstDirection_Version) {
2063 switch (dir) { // old values
2064 case 0:
2065 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
2066 break;
2067 case 1:
2068 fFirstDirection = SkPathPriv::kCW_FirstDirection;
2069 break;
2070 case 2:
2071 fFirstDirection = SkPathPriv::kCCW_FirstDirection;
2072 break;
2073 default:
2074 SkASSERT(false);
2075 }
2076 } else {
2077 fFirstDirection = dir;
2078 }
2079
ajumaf8aec582016-01-13 13:46:31 -08002080 return buffer.pos();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002081}
2082
2083///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00002084
reede05fed02014-12-15 07:59:53 -08002085#include "SkStringUtils.h"
caryclark66a5d8b2014-06-24 08:30:15 -07002086#include "SkStream.h"
reed@google.com51bbe752013-01-17 22:07:50 +00002087
reed@google.com51bbe752013-01-17 22:07:50 +00002088static void append_params(SkString* str, const char label[], const SkPoint pts[],
reede05fed02014-12-15 07:59:53 -08002089 int count, SkScalarAsStringType strType, SkScalar conicWeight = -1) {
reed@google.com51bbe752013-01-17 22:07:50 +00002090 str->append(label);
2091 str->append("(");
skia.committer@gmail.com15dd3002013-01-18 07:07:28 +00002092
reed@google.com51bbe752013-01-17 22:07:50 +00002093 const SkScalar* values = &pts[0].fX;
2094 count *= 2;
2095
2096 for (int i = 0; i < count; ++i) {
reede05fed02014-12-15 07:59:53 -08002097 SkAppendScalar(str, values[i], strType);
reed@google.com51bbe752013-01-17 22:07:50 +00002098 if (i < count - 1) {
2099 str->append(", ");
2100 }
2101 }
reed@google.com277c3f82013-05-31 15:17:50 +00002102 if (conicWeight >= 0) {
2103 str->append(", ");
reede05fed02014-12-15 07:59:53 -08002104 SkAppendScalar(str, conicWeight, strType);
reed@google.com277c3f82013-05-31 15:17:50 +00002105 }
caryclark08fa28c2014-10-23 13:08:56 -07002106 str->append(");");
reede05fed02014-12-15 07:59:53 -08002107 if (kHex_SkScalarAsStringType == strType) {
caryclark08fa28c2014-10-23 13:08:56 -07002108 str->append(" // ");
2109 for (int i = 0; i < count; ++i) {
reede05fed02014-12-15 07:59:53 -08002110 SkAppendScalarDec(str, values[i]);
caryclark08fa28c2014-10-23 13:08:56 -07002111 if (i < count - 1) {
2112 str->append(", ");
2113 }
2114 }
2115 if (conicWeight >= 0) {
2116 str->append(", ");
reede05fed02014-12-15 07:59:53 -08002117 SkAppendScalarDec(str, conicWeight);
caryclark08fa28c2014-10-23 13:08:56 -07002118 }
2119 }
2120 str->append("\n");
reed@google.com51bbe752013-01-17 22:07:50 +00002121}
2122
caryclarke9562592014-09-15 09:26:09 -07002123void SkPath::dump(SkWStream* wStream, bool forceClose, bool dumpAsHex) const {
reede05fed02014-12-15 07:59:53 -08002124 SkScalarAsStringType asType = dumpAsHex ? kHex_SkScalarAsStringType : kDec_SkScalarAsStringType;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002125 Iter iter(*this, forceClose);
2126 SkPoint pts[4];
2127 Verb verb;
2128
caryclark66a5d8b2014-06-24 08:30:15 -07002129 if (!wStream) {
2130 SkDebugf("path: forceClose=%s\n", forceClose ? "true" : "false");
2131 }
reed@google.com51bbe752013-01-17 22:07:50 +00002132 SkString builder;
2133
reed@google.com4a3b7142012-05-16 17:16:46 +00002134 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002135 switch (verb) {
2136 case kMove_Verb:
reede05fed02014-12-15 07:59:53 -08002137 append_params(&builder, "path.moveTo", &pts[0], 1, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002138 break;
2139 case kLine_Verb:
reede05fed02014-12-15 07:59:53 -08002140 append_params(&builder, "path.lineTo", &pts[1], 1, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002141 break;
2142 case kQuad_Verb:
reede05fed02014-12-15 07:59:53 -08002143 append_params(&builder, "path.quadTo", &pts[1], 2, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002144 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002145 case kConic_Verb:
reede05fed02014-12-15 07:59:53 -08002146 append_params(&builder, "path.conicTo", &pts[1], 2, asType, iter.conicWeight());
reed@google.com277c3f82013-05-31 15:17:50 +00002147 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002148 case kCubic_Verb:
reede05fed02014-12-15 07:59:53 -08002149 append_params(&builder, "path.cubicTo", &pts[1], 3, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002150 break;
2151 case kClose_Verb:
caryclark66a5d8b2014-06-24 08:30:15 -07002152 builder.append("path.close();\n");
reed@android.com8a1c16f2008-12-17 15:59:43 +00002153 break;
2154 default:
2155 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
2156 verb = kDone_Verb; // stop the loop
2157 break;
2158 }
caryclark1049f122015-04-20 08:31:59 -07002159 if (!wStream && builder.size()) {
2160 SkDebugf("%s", builder.c_str());
2161 builder.reset();
2162 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002163 }
caryclark66a5d8b2014-06-24 08:30:15 -07002164 if (wStream) {
2165 wStream->writeText(builder.c_str());
caryclark66a5d8b2014-06-24 08:30:15 -07002166 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002167}
2168
reed@android.come522ca52009-11-23 20:10:41 +00002169void SkPath::dump() const {
halcanary96fcdcc2015-08-27 07:41:13 -07002170 this->dump(nullptr, false, false);
caryclarke9562592014-09-15 09:26:09 -07002171}
2172
2173void SkPath::dumpHex() const {
halcanary96fcdcc2015-08-27 07:41:13 -07002174 this->dump(nullptr, false, true);
reed@android.come522ca52009-11-23 20:10:41 +00002175}
2176
2177#ifdef SK_DEBUG
2178void SkPath::validate() const {
reed@android.come522ca52009-11-23 20:10:41 +00002179 SkASSERT((fFillType & ~3) == 0);
reed@google.comabf15c12011-01-18 20:35:51 +00002180
djsollen@google.com077348c2012-10-22 20:23:32 +00002181#ifdef SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002182 if (!fBoundsIsDirty) {
2183 SkRect bounds;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002184
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002185 bool isFinite = compute_pt_bounds(&bounds, *fPathRef.get());
robertphillips@google.com5d8d1862012-08-15 14:36:41 +00002186 SkASSERT(SkToBool(fIsFinite) == isFinite);
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002187
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002188 if (fPathRef->countPoints() <= 1) {
reed@android.come522ca52009-11-23 20:10:41 +00002189 // if we're empty, fBounds may be empty but translated, so we can't
2190 // necessarily compare to bounds directly
2191 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
2192 // be [2, 2, 2, 2]
2193 SkASSERT(bounds.isEmpty());
2194 SkASSERT(fBounds.isEmpty());
2195 } else {
reed@google.comeac52bd2011-11-14 18:13:59 +00002196 if (bounds.isEmpty()) {
2197 SkASSERT(fBounds.isEmpty());
2198 } else {
reed@google.com3563c9e2011-11-14 19:34:57 +00002199 if (!fBounds.isEmpty()) {
2200 SkASSERT(fBounds.contains(bounds));
2201 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002202 }
reed@android.come522ca52009-11-23 20:10:41 +00002203 }
2204 }
djsollen@google.com077348c2012-10-22 20:23:32 +00002205#endif // SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002206}
djsollen@google.com077348c2012-10-22 20:23:32 +00002207#endif // SK_DEBUG
reed@android.come522ca52009-11-23 20:10:41 +00002208
reed@google.com04863fa2011-05-15 04:08:24 +00002209///////////////////////////////////////////////////////////////////////////////
2210
reed@google.com0b7b9822011-05-16 12:29:27 +00002211static int sign(SkScalar x) { return x < 0; }
2212#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00002213
robertphillipsc506e302014-09-16 09:43:31 -07002214enum DirChange {
2215 kLeft_DirChange,
2216 kRight_DirChange,
2217 kStraight_DirChange,
2218 kBackwards_DirChange,
2219
2220 kInvalid_DirChange
2221};
2222
2223
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002224static bool almost_equal(SkScalar compA, SkScalar compB) {
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002225 // The error epsilon was empirically derived; worse case round rects
2226 // with a mid point outset by 2x float epsilon in tests had an error
2227 // of 12.
2228 const int epsilon = 16;
2229 if (!SkScalarIsFinite(compA) || !SkScalarIsFinite(compB)) {
2230 return false;
2231 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002232 // no need to check for small numbers because SkPath::Iter has removed degenerate values
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002233 int aBits = SkFloatAs2sCompliment(compA);
2234 int bBits = SkFloatAs2sCompliment(compB);
2235 return aBits < bBits + epsilon && bBits < aBits + epsilon;
reed@google.com04863fa2011-05-15 04:08:24 +00002236}
2237
caryclarkb4216502015-03-02 13:02:34 -08002238static bool approximately_zero_when_compared_to(double x, double y) {
2239 return x == 0 || fabs(x) < fabs(y * FLT_EPSILON);
robertphillipsc506e302014-09-16 09:43:31 -07002240}
2241
caryclarkb4216502015-03-02 13:02:34 -08002242
reed@google.com04863fa2011-05-15 04:08:24 +00002243// only valid for a single contour
2244struct Convexicator {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002245 Convexicator()
2246 : fPtCount(0)
2247 , fConvexity(SkPath::kConvex_Convexity)
reed026beb52015-06-10 14:23:15 -07002248 , fFirstDirection(SkPathPriv::kUnknown_FirstDirection)
caryclark5ccef572015-03-02 10:07:56 -08002249 , fIsFinite(true)
2250 , fIsCurve(false) {
robertphillipsc506e302014-09-16 09:43:31 -07002251 fExpectedDir = kInvalid_DirChange;
reed@google.com04863fa2011-05-15 04:08:24 +00002252 // warnings
djsollen2f124632016-04-29 13:53:05 -07002253 fPriorPt.set(0,0);
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002254 fLastPt.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002255 fCurrPt.set(0, 0);
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002256 fLastVec.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002257 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00002258
2259 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00002260 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00002261 }
2262
2263 SkPath::Convexity getConvexity() const { return fConvexity; }
2264
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002265 /** The direction returned is only valid if the path is determined convex */
reed026beb52015-06-10 14:23:15 -07002266 SkPathPriv::FirstDirection getFirstDirection() const { return fFirstDirection; }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002267
reed@google.com04863fa2011-05-15 04:08:24 +00002268 void addPt(const SkPoint& pt) {
caryclarkd3d1a982014-12-08 04:57:38 -08002269 if (SkPath::kConcave_Convexity == fConvexity || !fIsFinite) {
reed@google.com04863fa2011-05-15 04:08:24 +00002270 return;
2271 }
2272
2273 if (0 == fPtCount) {
2274 fCurrPt = pt;
2275 ++fPtCount;
2276 } else {
2277 SkVector vec = pt - fCurrPt;
caryclarkd3d1a982014-12-08 04:57:38 -08002278 SkScalar lengthSqd = vec.lengthSqd();
2279 if (!SkScalarIsFinite(lengthSqd)) {
2280 fIsFinite = false;
caryclarke8c56662015-07-14 11:19:26 -07002281 } else if (lengthSqd) {
caryclarkb4216502015-03-02 13:02:34 -08002282 fPriorPt = fLastPt;
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002283 fLastPt = fCurrPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002284 fCurrPt = pt;
2285 if (++fPtCount == 2) {
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002286 fFirstVec = fLastVec = vec;
reed@google.com04863fa2011-05-15 04:08:24 +00002287 } else {
2288 SkASSERT(fPtCount > 2);
2289 this->addVec(vec);
2290 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002291
reed@google.com85b6e392011-05-15 20:25:17 +00002292 int sx = sign(vec.fX);
2293 int sy = sign(vec.fY);
2294 fDx += (sx != fSx);
2295 fDy += (sy != fSy);
2296 fSx = sx;
2297 fSy = sy;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002298
reed@google.com85b6e392011-05-15 20:25:17 +00002299 if (fDx > 3 || fDy > 3) {
2300 fConvexity = SkPath::kConcave_Convexity;
2301 }
reed@google.com04863fa2011-05-15 04:08:24 +00002302 }
2303 }
2304 }
2305
2306 void close() {
2307 if (fPtCount > 2) {
2308 this->addVec(fFirstVec);
2309 }
2310 }
2311
caryclarkb4216502015-03-02 13:02:34 -08002312 DirChange directionChange(const SkVector& curVec) {
2313 SkScalar cross = SkPoint::CrossProduct(fLastVec, curVec);
2314
2315 SkScalar smallest = SkTMin(fCurrPt.fX, SkTMin(fCurrPt.fY, SkTMin(fLastPt.fX, fLastPt.fY)));
2316 SkScalar largest = SkTMax(fCurrPt.fX, SkTMax(fCurrPt.fY, SkTMax(fLastPt.fX, fLastPt.fY)));
2317 largest = SkTMax(largest, -smallest);
2318
2319 if (!almost_equal(largest, largest + cross)) {
2320 int sign = SkScalarSignAsInt(cross);
2321 if (sign) {
2322 return (1 == sign) ? kRight_DirChange : kLeft_DirChange;
2323 }
2324 }
2325
2326 if (cross) {
2327 double dLastVecX = SkScalarToDouble(fLastPt.fX) - SkScalarToDouble(fPriorPt.fX);
2328 double dLastVecY = SkScalarToDouble(fLastPt.fY) - SkScalarToDouble(fPriorPt.fY);
2329 double dCurrVecX = SkScalarToDouble(fCurrPt.fX) - SkScalarToDouble(fLastPt.fX);
2330 double dCurrVecY = SkScalarToDouble(fCurrPt.fY) - SkScalarToDouble(fLastPt.fY);
2331 double dCross = dLastVecX * dCurrVecY - dLastVecY * dCurrVecX;
2332 if (!approximately_zero_when_compared_to(dCross, SkScalarToDouble(largest))) {
2333 int sign = SkScalarSignAsInt(SkDoubleToScalar(dCross));
2334 if (sign) {
2335 return (1 == sign) ? kRight_DirChange : kLeft_DirChange;
2336 }
2337 }
2338 }
2339
2340 if (!SkScalarNearlyZero(fLastVec.lengthSqd(), SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
2341 !SkScalarNearlyZero(curVec.lengthSqd(), SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
2342 fLastVec.dot(curVec) < 0.0f) {
2343 return kBackwards_DirChange;
2344 }
2345
2346 return kStraight_DirChange;
2347 }
2348
2349
caryclarkd3d1a982014-12-08 04:57:38 -08002350 bool isFinite() const {
2351 return fIsFinite;
2352 }
2353
caryclark5ccef572015-03-02 10:07:56 -08002354 void setCurve(bool isCurve) {
2355 fIsCurve = isCurve;
2356 }
2357
reed@google.com04863fa2011-05-15 04:08:24 +00002358private:
2359 void addVec(const SkVector& vec) {
2360 SkASSERT(vec.fX || vec.fY);
caryclarkb4216502015-03-02 13:02:34 -08002361 DirChange dir = this->directionChange(vec);
robertphillipsc506e302014-09-16 09:43:31 -07002362 switch (dir) {
2363 case kLeft_DirChange: // fall through
2364 case kRight_DirChange:
2365 if (kInvalid_DirChange == fExpectedDir) {
2366 fExpectedDir = dir;
reed026beb52015-06-10 14:23:15 -07002367 fFirstDirection = (kRight_DirChange == dir) ? SkPathPriv::kCW_FirstDirection
2368 : SkPathPriv::kCCW_FirstDirection;
robertphillipsc506e302014-09-16 09:43:31 -07002369 } else if (dir != fExpectedDir) {
2370 fConvexity = SkPath::kConcave_Convexity;
reed026beb52015-06-10 14:23:15 -07002371 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
robertphillipsc506e302014-09-16 09:43:31 -07002372 }
2373 fLastVec = vec;
2374 break;
2375 case kStraight_DirChange:
2376 break;
2377 case kBackwards_DirChange:
caryclark5ccef572015-03-02 10:07:56 -08002378 if (fIsCurve) {
2379 fConvexity = SkPath::kConcave_Convexity;
reed026beb52015-06-10 14:23:15 -07002380 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
caryclark5ccef572015-03-02 10:07:56 -08002381 }
robertphillipsc506e302014-09-16 09:43:31 -07002382 fLastVec = vec;
2383 break;
2384 case kInvalid_DirChange:
2385 SkFAIL("Use of invalid direction change flag");
2386 break;
reed@google.com04863fa2011-05-15 04:08:24 +00002387 }
2388 }
2389
caryclarkb4216502015-03-02 13:02:34 -08002390 SkPoint fPriorPt;
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002391 SkPoint fLastPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002392 SkPoint fCurrPt;
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002393 // fLastVec does not necessarily start at fLastPt. We only advance it when the cross product
2394 // value with the current vec is deemed to be of a significant value.
2395 SkVector fLastVec, fFirstVec;
reed@google.com04863fa2011-05-15 04:08:24 +00002396 int fPtCount; // non-degenerate points
robertphillipsc506e302014-09-16 09:43:31 -07002397 DirChange fExpectedDir;
reed@google.com04863fa2011-05-15 04:08:24 +00002398 SkPath::Convexity fConvexity;
reed026beb52015-06-10 14:23:15 -07002399 SkPathPriv::FirstDirection fFirstDirection;
reed@google.com0b7b9822011-05-16 12:29:27 +00002400 int fDx, fDy, fSx, fSy;
caryclarkd3d1a982014-12-08 04:57:38 -08002401 bool fIsFinite;
caryclark5ccef572015-03-02 10:07:56 -08002402 bool fIsCurve;
reed@google.com04863fa2011-05-15 04:08:24 +00002403};
2404
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002405SkPath::Convexity SkPath::internalGetConvexity() const {
2406 SkASSERT(kUnknown_Convexity == fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002407 SkPoint pts[4];
2408 SkPath::Verb verb;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002409 SkPath::Iter iter(*this, true);
reed@google.com04863fa2011-05-15 04:08:24 +00002410
2411 int contourCount = 0;
2412 int count;
2413 Convexicator state;
2414
caryclarkd3d1a982014-12-08 04:57:38 -08002415 if (!isFinite()) {
2416 return kUnknown_Convexity;
2417 }
caryclarke8c56662015-07-14 11:19:26 -07002418 while ((verb = iter.next(pts, true, true)) != SkPath::kDone_Verb) {
reed@google.com04863fa2011-05-15 04:08:24 +00002419 switch (verb) {
2420 case kMove_Verb:
2421 if (++contourCount > 1) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002422 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002423 return kConcave_Convexity;
2424 }
2425 pts[1] = pts[0];
caryclark5ccef572015-03-02 10:07:56 -08002426 // fall through
2427 case kLine_Verb:
reed@google.com04863fa2011-05-15 04:08:24 +00002428 count = 1;
caryclark5ccef572015-03-02 10:07:56 -08002429 state.setCurve(false);
reed@google.com04863fa2011-05-15 04:08:24 +00002430 break;
caryclark5ccef572015-03-02 10:07:56 -08002431 case kQuad_Verb:
2432 // fall through
2433 case kConic_Verb:
2434 // fall through
2435 case kCubic_Verb:
2436 count = 2 + (kCubic_Verb == verb);
2437 // As an additional enhancement, this could set curve true only
2438 // if the curve is nonlinear
2439 state.setCurve(true);
2440 break;
reed@google.com04863fa2011-05-15 04:08:24 +00002441 case kClose_Verb:
caryclark5ccef572015-03-02 10:07:56 -08002442 state.setCurve(false);
reed@google.com04863fa2011-05-15 04:08:24 +00002443 state.close();
2444 count = 0;
2445 break;
2446 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00002447 SkDEBUGFAIL("bad verb");
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002448 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002449 return kConcave_Convexity;
2450 }
2451
2452 for (int i = 1; i <= count; i++) {
2453 state.addPt(pts[i]);
2454 }
2455 // early exit
caryclarkd3d1a982014-12-08 04:57:38 -08002456 if (!state.isFinite()) {
2457 return kUnknown_Convexity;
2458 }
reed@google.com04863fa2011-05-15 04:08:24 +00002459 if (kConcave_Convexity == state.getConvexity()) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002460 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002461 return kConcave_Convexity;
2462 }
2463 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002464 fConvexity = state.getConvexity();
reed026beb52015-06-10 14:23:15 -07002465 if (kConvex_Convexity == fConvexity && SkPathPriv::kUnknown_FirstDirection == fFirstDirection) {
2466 fFirstDirection = state.getFirstDirection();
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002467 }
2468 return static_cast<Convexity>(fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002469}
reed@google.com69a99432012-01-10 18:00:10 +00002470
2471///////////////////////////////////////////////////////////////////////////////
2472
2473class ContourIter {
2474public:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002475 ContourIter(const SkPathRef& pathRef);
reed@google.com69a99432012-01-10 18:00:10 +00002476
2477 bool done() const { return fDone; }
2478 // if !done() then these may be called
2479 int count() const { return fCurrPtCount; }
2480 const SkPoint* pts() const { return fCurrPt; }
2481 void next();
2482
2483private:
2484 int fCurrPtCount;
2485 const SkPoint* fCurrPt;
2486 const uint8_t* fCurrVerb;
2487 const uint8_t* fStopVerbs;
reed@google.com277c3f82013-05-31 15:17:50 +00002488 const SkScalar* fCurrConicWeight;
reed@google.com69a99432012-01-10 18:00:10 +00002489 bool fDone;
reed@google.comd1ab9322012-01-10 18:40:03 +00002490 SkDEBUGCODE(int fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002491};
2492
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002493ContourIter::ContourIter(const SkPathRef& pathRef) {
2494 fStopVerbs = pathRef.verbsMemBegin();
reed@google.com69a99432012-01-10 18:00:10 +00002495 fDone = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002496 fCurrPt = pathRef.points();
2497 fCurrVerb = pathRef.verbs();
reed@google.com277c3f82013-05-31 15:17:50 +00002498 fCurrConicWeight = pathRef.conicWeights();
reed@google.com69a99432012-01-10 18:00:10 +00002499 fCurrPtCount = 0;
reed@google.comd1ab9322012-01-10 18:40:03 +00002500 SkDEBUGCODE(fContourCounter = 0;)
reed@google.com69a99432012-01-10 18:00:10 +00002501 this->next();
2502}
2503
2504void ContourIter::next() {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002505 if (fCurrVerb <= fStopVerbs) {
reed@google.com69a99432012-01-10 18:00:10 +00002506 fDone = true;
2507 }
2508 if (fDone) {
2509 return;
2510 }
2511
2512 // skip pts of prev contour
2513 fCurrPt += fCurrPtCount;
2514
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002515 SkASSERT(SkPath::kMove_Verb == fCurrVerb[~0]);
reed@google.com69a99432012-01-10 18:00:10 +00002516 int ptCount = 1; // moveTo
2517 const uint8_t* verbs = fCurrVerb;
2518
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002519 for (--verbs; verbs > fStopVerbs; --verbs) {
2520 switch (verbs[~0]) {
reed@google.com69a99432012-01-10 18:00:10 +00002521 case SkPath::kMove_Verb:
reed@google.com69a99432012-01-10 18:00:10 +00002522 goto CONTOUR_END;
2523 case SkPath::kLine_Verb:
2524 ptCount += 1;
2525 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002526 case SkPath::kConic_Verb:
2527 fCurrConicWeight += 1;
2528 // fall-through
reed@google.com69a99432012-01-10 18:00:10 +00002529 case SkPath::kQuad_Verb:
2530 ptCount += 2;
2531 break;
2532 case SkPath::kCubic_Verb:
2533 ptCount += 3;
2534 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002535 case SkPath::kClose_Verb:
2536 break;
2537 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00002538 SkDEBUGFAIL("unexpected verb");
reed@google.com69a99432012-01-10 18:00:10 +00002539 break;
2540 }
2541 }
2542CONTOUR_END:
2543 fCurrPtCount = ptCount;
2544 fCurrVerb = verbs;
reed@google.comd1ab9322012-01-10 18:40:03 +00002545 SkDEBUGCODE(++fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002546}
2547
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002548// returns cross product of (p1 - p0) and (p2 - p0)
reed@google.com69a99432012-01-10 18:00:10 +00002549static SkScalar cross_prod(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002550 SkScalar cross = SkPoint::CrossProduct(p1 - p0, p2 - p0);
2551 // We may get 0 when the above subtracts underflow. We expect this to be
2552 // very rare and lazily promote to double.
2553 if (0 == cross) {
2554 double p0x = SkScalarToDouble(p0.fX);
2555 double p0y = SkScalarToDouble(p0.fY);
2556
2557 double p1x = SkScalarToDouble(p1.fX);
2558 double p1y = SkScalarToDouble(p1.fY);
2559
2560 double p2x = SkScalarToDouble(p2.fX);
2561 double p2y = SkScalarToDouble(p2.fY);
2562
2563 cross = SkDoubleToScalar((p1x - p0x) * (p2y - p0y) -
2564 (p1y - p0y) * (p2x - p0x));
2565
2566 }
2567 return cross;
reed@google.com69a99432012-01-10 18:00:10 +00002568}
2569
reed@google.comc1ea60a2012-01-31 15:15:36 +00002570// Returns the first pt with the maximum Y coordinate
reed@google.com69a99432012-01-10 18:00:10 +00002571static int find_max_y(const SkPoint pts[], int count) {
2572 SkASSERT(count > 0);
2573 SkScalar max = pts[0].fY;
reed@google.comc1ea60a2012-01-31 15:15:36 +00002574 int firstIndex = 0;
reed@google.com69a99432012-01-10 18:00:10 +00002575 for (int i = 1; i < count; ++i) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002576 SkScalar y = pts[i].fY;
2577 if (y > max) {
2578 max = y;
2579 firstIndex = i;
reed@google.com69a99432012-01-10 18:00:10 +00002580 }
2581 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002582 return firstIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002583}
2584
reed@google.comcabaf1d2012-01-11 21:03:05 +00002585static int find_diff_pt(const SkPoint pts[], int index, int n, int inc) {
2586 int i = index;
2587 for (;;) {
2588 i = (i + inc) % n;
2589 if (i == index) { // we wrapped around, so abort
2590 break;
2591 }
2592 if (pts[index] != pts[i]) { // found a different point, success!
2593 break;
2594 }
2595 }
2596 return i;
2597}
2598
reed@google.comc1ea60a2012-01-31 15:15:36 +00002599/**
2600 * Starting at index, and moving forward (incrementing), find the xmin and
2601 * xmax of the contiguous points that have the same Y.
2602 */
2603static int find_min_max_x_at_y(const SkPoint pts[], int index, int n,
2604 int* maxIndexPtr) {
2605 const SkScalar y = pts[index].fY;
2606 SkScalar min = pts[index].fX;
2607 SkScalar max = min;
2608 int minIndex = index;
2609 int maxIndex = index;
2610 for (int i = index + 1; i < n; ++i) {
2611 if (pts[i].fY != y) {
2612 break;
2613 }
2614 SkScalar x = pts[i].fX;
2615 if (x < min) {
2616 min = x;
2617 minIndex = i;
2618 } else if (x > max) {
2619 max = x;
2620 maxIndex = i;
2621 }
2622 }
2623 *maxIndexPtr = maxIndex;
2624 return minIndex;
2625}
2626
reed026beb52015-06-10 14:23:15 -07002627static void crossToDir(SkScalar cross, SkPathPriv::FirstDirection* dir) {
2628 *dir = cross > 0 ? SkPathPriv::kCW_FirstDirection : SkPathPriv::kCCW_FirstDirection;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002629}
2630
reed@google.comac8543f2012-01-30 20:51:25 +00002631/*
2632 * We loop through all contours, and keep the computed cross-product of the
2633 * contour that contained the global y-max. If we just look at the first
2634 * contour, we may find one that is wound the opposite way (correctly) since
2635 * it is the interior of a hole (e.g. 'o'). Thus we must find the contour
2636 * that is outer most (or at least has the global y-max) before we can consider
2637 * its cross product.
2638 */
reed026beb52015-06-10 14:23:15 -07002639bool SkPathPriv::CheapComputeFirstDirection(const SkPath& path, FirstDirection* dir) {
herb9f4dbca2015-09-28 11:05:47 -07002640 if (kUnknown_FirstDirection != path.fFirstDirection.load()) {
2641 *dir = static_cast<FirstDirection>(path.fFirstDirection.load());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002642 return true;
2643 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002644
2645 // don't want to pay the cost for computing this if it
2646 // is unknown, so we don't call isConvex()
reed026beb52015-06-10 14:23:15 -07002647 if (SkPath::kConvex_Convexity == path.getConvexityOrUnknown()) {
2648 SkASSERT(kUnknown_FirstDirection == path.fFirstDirection);
herb9f4dbca2015-09-28 11:05:47 -07002649 *dir = static_cast<FirstDirection>(path.fFirstDirection.load());
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002650 return false;
2651 }
reed@google.com69a99432012-01-10 18:00:10 +00002652
reed026beb52015-06-10 14:23:15 -07002653 ContourIter iter(*path.fPathRef.get());
reed@google.com69a99432012-01-10 18:00:10 +00002654
reed@google.comac8543f2012-01-30 20:51:25 +00002655 // initialize with our logical y-min
reed026beb52015-06-10 14:23:15 -07002656 SkScalar ymax = path.getBounds().fTop;
reed@google.comac8543f2012-01-30 20:51:25 +00002657 SkScalar ymaxCross = 0;
2658
reed@google.com69a99432012-01-10 18:00:10 +00002659 for (; !iter.done(); iter.next()) {
2660 int n = iter.count();
reed@google.comcabaf1d2012-01-11 21:03:05 +00002661 if (n < 3) {
2662 continue;
2663 }
djsollen@google.come63793a2012-03-21 15:39:03 +00002664
reed@google.comcabaf1d2012-01-11 21:03:05 +00002665 const SkPoint* pts = iter.pts();
reed@google.com69a99432012-01-10 18:00:10 +00002666 SkScalar cross = 0;
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002667 int index = find_max_y(pts, n);
2668 if (pts[index].fY < ymax) {
2669 continue;
2670 }
2671
2672 // If there is more than 1 distinct point at the y-max, we take the
2673 // x-min and x-max of them and just subtract to compute the dir.
2674 if (pts[(index + 1) % n].fY == pts[index].fY) {
2675 int maxIndex;
2676 int minIndex = find_min_max_x_at_y(pts, index, n, &maxIndex);
2677 if (minIndex == maxIndex) {
2678 goto TRY_CROSSPROD;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002679 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002680 SkASSERT(pts[minIndex].fY == pts[index].fY);
2681 SkASSERT(pts[maxIndex].fY == pts[index].fY);
2682 SkASSERT(pts[minIndex].fX <= pts[maxIndex].fX);
2683 // we just subtract the indices, and let that auto-convert to
2684 // SkScalar, since we just want - or + to signal the direction.
2685 cross = minIndex - maxIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002686 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002687 TRY_CROSSPROD:
2688 // Find a next and prev index to use for the cross-product test,
2689 // but we try to find pts that form non-zero vectors from pts[index]
2690 //
2691 // Its possible that we can't find two non-degenerate vectors, so
2692 // we have to guard our search (e.g. all the pts could be in the
2693 // same place).
2694
2695 // we pass n - 1 instead of -1 so we don't foul up % operator by
2696 // passing it a negative LH argument.
2697 int prev = find_diff_pt(pts, index, n, n - 1);
2698 if (prev == index) {
2699 // completely degenerate, skip to next contour
reed@google.comac8543f2012-01-30 20:51:25 +00002700 continue;
2701 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002702 int next = find_diff_pt(pts, index, n, 1);
2703 SkASSERT(next != index);
2704 cross = cross_prod(pts[prev], pts[index], pts[next]);
2705 // if we get a zero and the points are horizontal, then we look at the spread in
2706 // x-direction. We really should continue to walk away from the degeneracy until
2707 // there is a divergence.
2708 if (0 == cross && pts[prev].fY == pts[index].fY && pts[next].fY == pts[index].fY) {
2709 // construct the subtract so we get the correct Direction below
2710 cross = pts[index].fX - pts[next].fX;
reed@google.com188bfcf2012-01-17 18:26:38 +00002711 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002712 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002713
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002714 if (cross) {
2715 // record our best guess so far
2716 ymax = pts[index].fY;
2717 ymaxCross = cross;
reed@google.com69a99432012-01-10 18:00:10 +00002718 }
2719 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002720 if (ymaxCross) {
2721 crossToDir(ymaxCross, dir);
reed026beb52015-06-10 14:23:15 -07002722 path.fFirstDirection = *dir;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002723 return true;
2724 } else {
2725 return false;
2726 }
reed@google.comac8543f2012-01-30 20:51:25 +00002727}
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002728
2729///////////////////////////////////////////////////////////////////////////////
2730
caryclark9aacd902015-12-14 08:38:09 -08002731static bool between(SkScalar a, SkScalar b, SkScalar c) {
2732 SkASSERT(((a <= b && b <= c) || (a >= b && b >= c)) == ((a - b) * (c - b) <= 0)
2733 || (SkScalarNearlyZero(a) && SkScalarNearlyZero(b) && SkScalarNearlyZero(c)));
2734 return (a - b) * (c - b) <= 0;
2735}
2736
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002737static SkScalar eval_cubic_coeff(SkScalar A, SkScalar B, SkScalar C,
2738 SkScalar D, SkScalar t) {
2739 return SkScalarMulAdd(SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C), t, D);
2740}
2741
2742static SkScalar eval_cubic_pts(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2743 SkScalar t) {
2744 SkScalar A = c3 + 3*(c1 - c2) - c0;
2745 SkScalar B = 3*(c2 - c1 - c1 + c0);
2746 SkScalar C = 3*(c1 - c0);
2747 SkScalar D = c0;
2748 return eval_cubic_coeff(A, B, C, D, t);
2749}
2750
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002751template <size_t N> static void find_minmax(const SkPoint pts[],
2752 SkScalar* minPtr, SkScalar* maxPtr) {
2753 SkScalar min, max;
2754 min = max = pts[0].fX;
2755 for (size_t i = 1; i < N; ++i) {
2756 min = SkMinScalar(min, pts[i].fX);
2757 max = SkMaxScalar(max, pts[i].fX);
2758 }
2759 *minPtr = min;
2760 *maxPtr = max;
2761}
2762
caryclark9cb5d752015-12-18 04:35:24 -08002763static bool checkOnCurve(SkScalar x, SkScalar y, const SkPoint& start, const SkPoint& end) {
2764 if (start.fY == end.fY) {
2765 return between(start.fX, x, end.fX) && x != end.fX;
2766 } else {
2767 return x == start.fX && y == start.fY;
2768 }
2769}
2770
caryclark9aacd902015-12-14 08:38:09 -08002771static int winding_mono_cubic(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
caryclark9cb5d752015-12-18 04:35:24 -08002772 SkScalar y0 = pts[0].fY;
2773 SkScalar y3 = pts[3].fY;
2774
2775 int dir = 1;
2776 if (y0 > y3) {
2777 SkTSwap(y0, y3);
2778 dir = -1;
2779 }
2780 if (y < y0 || y > y3) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002781 return 0;
2782 }
caryclark9cb5d752015-12-18 04:35:24 -08002783 if (checkOnCurve(x, y, pts[0], pts[3])) {
2784 *onCurveCount += 1;
caryclark9aacd902015-12-14 08:38:09 -08002785 return 0;
2786 }
caryclark9cb5d752015-12-18 04:35:24 -08002787 if (y == y3) {
2788 return 0;
2789 }
2790
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002791 // quickreject or quickaccept
2792 SkScalar min, max;
2793 find_minmax<4>(pts, &min, &max);
2794 if (x < min) {
2795 return 0;
2796 }
2797 if (x > max) {
2798 return dir;
2799 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002800
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002801 // compute the actual x(t) value
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002802 SkScalar t;
caryclark9aacd902015-12-14 08:38:09 -08002803 SkAssertResult(SkCubicClipper::ChopMonoAtY(pts, y, &t));
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002804 SkScalar xt = eval_cubic_pts(pts[0].fX, pts[1].fX, pts[2].fX, pts[3].fX, t);
caryclark9aacd902015-12-14 08:38:09 -08002805 if (SkScalarNearlyEqual(xt, x)) {
2806 if (x != pts[3].fX || y != pts[3].fY) { // don't test end points; they're start points
2807 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08002808 return 0;
caryclark9aacd902015-12-14 08:38:09 -08002809 }
2810 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002811 return xt < x ? dir : 0;
2812}
2813
caryclark9aacd902015-12-14 08:38:09 -08002814static int winding_cubic(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002815 SkPoint dst[10];
2816 int n = SkChopCubicAtYExtrema(pts, dst);
2817 int w = 0;
2818 for (int i = 0; i <= n; ++i) {
caryclark9aacd902015-12-14 08:38:09 -08002819 w += winding_mono_cubic(&dst[i * 3], x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002820 }
2821 return w;
2822}
2823
caryclark9aacd902015-12-14 08:38:09 -08002824static double conic_eval_numerator(const SkScalar src[], SkScalar w, SkScalar t) {
2825 SkASSERT(src);
2826 SkASSERT(t >= 0 && t <= 1);
2827 SkScalar src2w = src[2] * w;
2828 SkScalar C = src[0];
2829 SkScalar A = src[4] - 2 * src2w + C;
2830 SkScalar B = 2 * (src2w - C);
2831 return (A * t + B) * t + C;
2832}
2833
2834
2835static double conic_eval_denominator(SkScalar w, SkScalar t) {
2836 SkScalar B = 2 * (w - 1);
2837 SkScalar C = 1;
2838 SkScalar A = -B;
2839 return (A * t + B) * t + C;
2840}
2841
2842static int winding_mono_conic(const SkConic& conic, SkScalar x, SkScalar y, int* onCurveCount) {
2843 const SkPoint* pts = conic.fPts;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002844 SkScalar y0 = pts[0].fY;
2845 SkScalar y2 = pts[2].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002846
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002847 int dir = 1;
2848 if (y0 > y2) {
2849 SkTSwap(y0, y2);
2850 dir = -1;
2851 }
caryclark9aacd902015-12-14 08:38:09 -08002852 if (y < y0 || y > y2) {
2853 return 0;
2854 }
caryclark9cb5d752015-12-18 04:35:24 -08002855 if (checkOnCurve(x, y, pts[0], pts[2])) {
2856 *onCurveCount += 1;
2857 return 0;
2858 }
caryclark9aacd902015-12-14 08:38:09 -08002859 if (y == y2) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002860 return 0;
2861 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002862
caryclark9aacd902015-12-14 08:38:09 -08002863 SkScalar roots[2];
2864 SkScalar A = pts[2].fY;
2865 SkScalar B = pts[1].fY * conic.fW - y * conic.fW + y;
2866 SkScalar C = pts[0].fY;
2867 A += C - 2 * B; // A = a + c - 2*(b*w - yCept*w + yCept)
2868 B -= C; // B = b*w - w * yCept + yCept - a
2869 C -= y;
2870 int n = SkFindUnitQuadRoots(A, 2 * B, C, roots);
2871 SkASSERT(n <= 1);
2872 SkScalar xt;
2873 if (0 == n) {
caryclark9cb5d752015-12-18 04:35:24 -08002874 // zero roots are returned only when y0 == y
2875 // Need [0] if dir == 1
2876 // and [2] if dir == -1
2877 xt = pts[1 - dir].fX;
caryclark9aacd902015-12-14 08:38:09 -08002878 } else {
2879 SkScalar t = roots[0];
2880 xt = conic_eval_numerator(&pts[0].fX, conic.fW, t) / conic_eval_denominator(conic.fW, t);
2881 }
2882 if (SkScalarNearlyEqual(xt, x)) {
2883 if (x != pts[2].fX || y != pts[2].fY) { // don't test end points; they're start points
2884 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08002885 return 0;
caryclark9aacd902015-12-14 08:38:09 -08002886 }
2887 }
2888 return xt < x ? dir : 0;
2889}
2890
2891static bool is_mono_quad(SkScalar y0, SkScalar y1, SkScalar y2) {
2892 // return SkScalarSignAsInt(y0 - y1) + SkScalarSignAsInt(y1 - y2) != 0;
2893 if (y0 == y1) {
2894 return true;
2895 }
2896 if (y0 < y1) {
2897 return y1 <= y2;
2898 } else {
2899 return y1 >= y2;
2900 }
2901}
2902
2903static int winding_conic(const SkPoint pts[], SkScalar x, SkScalar y, SkScalar weight,
2904 int* onCurveCount) {
2905 SkConic conic(pts, weight);
caryclark9aacd902015-12-14 08:38:09 -08002906 SkConic chopped[2];
caryclark1e1e5092016-01-06 08:27:44 -08002907 // If the data points are very large, the conic may not be monotonic but may also
2908 // fail to chop. Then, the chopper does not split the original conic in two.
caryclarke114dd62016-01-06 08:15:44 -08002909 bool isMono = is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY) || !conic.chopAtYExtrema(chopped);
2910 int w = winding_mono_conic(isMono ? conic : chopped[0], x, y, onCurveCount);
2911 if (!isMono) {
caryclark9aacd902015-12-14 08:38:09 -08002912 w += winding_mono_conic(chopped[1], x, y, onCurveCount);
2913 }
2914 return w;
2915}
2916
2917static int winding_mono_quad(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
2918 SkScalar y0 = pts[0].fY;
2919 SkScalar y2 = pts[2].fY;
2920
2921 int dir = 1;
2922 if (y0 > y2) {
2923 SkTSwap(y0, y2);
2924 dir = -1;
2925 }
2926 if (y < y0 || y > y2) {
2927 return 0;
2928 }
caryclark9cb5d752015-12-18 04:35:24 -08002929 if (checkOnCurve(x, y, pts[0], pts[2])) {
2930 *onCurveCount += 1;
2931 return 0;
2932 }
caryclark9aacd902015-12-14 08:38:09 -08002933 if (y == y2) {
caryclark9aacd902015-12-14 08:38:09 -08002934 return 0;
2935 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002936 // bounds check on X (not required. is it faster?)
2937#if 0
2938 if (pts[0].fX > x && pts[1].fX > x && pts[2].fX > x) {
2939 return 0;
2940 }
2941#endif
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002942
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002943 SkScalar roots[2];
2944 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
2945 2 * (pts[1].fY - pts[0].fY),
2946 pts[0].fY - y,
2947 roots);
2948 SkASSERT(n <= 1);
2949 SkScalar xt;
2950 if (0 == n) {
caryclark9cb5d752015-12-18 04:35:24 -08002951 // zero roots are returned only when y0 == y
2952 // Need [0] if dir == 1
2953 // and [2] if dir == -1
2954 xt = pts[1 - dir].fX;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002955 } else {
2956 SkScalar t = roots[0];
2957 SkScalar C = pts[0].fX;
2958 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
2959 SkScalar B = 2 * (pts[1].fX - C);
2960 xt = SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C);
2961 }
caryclark9aacd902015-12-14 08:38:09 -08002962 if (SkScalarNearlyEqual(xt, x)) {
2963 if (x != pts[2].fX || y != pts[2].fY) { // don't test end points; they're start points
2964 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08002965 return 0;
caryclark9aacd902015-12-14 08:38:09 -08002966 }
2967 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002968 return xt < x ? dir : 0;
2969}
2970
caryclark9aacd902015-12-14 08:38:09 -08002971static int winding_quad(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002972 SkPoint dst[5];
2973 int n = 0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002974
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002975 if (!is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY)) {
2976 n = SkChopQuadAtYExtrema(pts, dst);
2977 pts = dst;
2978 }
caryclark9aacd902015-12-14 08:38:09 -08002979 int w = winding_mono_quad(pts, x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002980 if (n > 0) {
caryclark9aacd902015-12-14 08:38:09 -08002981 w += winding_mono_quad(&pts[2], x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002982 }
2983 return w;
2984}
2985
caryclark9aacd902015-12-14 08:38:09 -08002986static int winding_line(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002987 SkScalar x0 = pts[0].fX;
2988 SkScalar y0 = pts[0].fY;
2989 SkScalar x1 = pts[1].fX;
2990 SkScalar y1 = pts[1].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002991
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002992 SkScalar dy = y1 - y0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002993
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002994 int dir = 1;
2995 if (y0 > y1) {
2996 SkTSwap(y0, y1);
2997 dir = -1;
2998 }
caryclark9aacd902015-12-14 08:38:09 -08002999 if (y < y0 || y > y1) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003000 return 0;
3001 }
caryclark9cb5d752015-12-18 04:35:24 -08003002 if (checkOnCurve(x, y, pts[0], pts[1])) {
3003 *onCurveCount += 1;
3004 return 0;
3005 }
3006 if (y == y1) {
caryclark9aacd902015-12-14 08:38:09 -08003007 return 0;
3008 }
3009 SkScalar cross = SkScalarMul(x1 - x0, y - pts[0].fY) - SkScalarMul(dy, x - x0);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003010
caryclark9aacd902015-12-14 08:38:09 -08003011 if (!cross) {
fsc91065d2015-12-17 09:03:27 -08003012 // zero cross means the point is on the line, and since the case where
3013 // y of the query point is at the end point is handled above, we can be
3014 // sure that we're on the line (excluding the end point) here
caryclark9cb5d752015-12-18 04:35:24 -08003015 if (x != x1 || y != pts[1].fY) {
3016 *onCurveCount += 1;
3017 }
caryclark9aacd902015-12-14 08:38:09 -08003018 dir = 0;
3019 } else if (SkScalarSignAsInt(cross) == dir) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003020 dir = 0;
3021 }
3022 return dir;
3023}
3024
caryclark9aacd902015-12-14 08:38:09 -08003025static void tangent_cubic(const SkPoint pts[], SkScalar x, SkScalar y,
3026 SkTDArray<SkVector>* tangents) {
3027 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)
3028 && !between(pts[2].fY, y, pts[3].fY)) {
3029 return;
3030 }
3031 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)
3032 && !between(pts[2].fX, x, pts[3].fX)) {
3033 return;
3034 }
3035 SkPoint dst[10];
3036 int n = SkChopCubicAtYExtrema(pts, dst);
3037 for (int i = 0; i <= n; ++i) {
3038 SkPoint* c = &dst[i * 3];
3039 SkScalar t;
3040 SkAssertResult(SkCubicClipper::ChopMonoAtY(c, y, &t));
3041 SkScalar xt = eval_cubic_pts(c[0].fX, c[1].fX, c[2].fX, c[3].fX, t);
3042 if (!SkScalarNearlyEqual(x, xt)) {
3043 continue;
3044 }
3045 SkVector tangent;
3046 SkEvalCubicAt(c, t, nullptr, &tangent, nullptr);
3047 tangents->push(tangent);
3048 }
3049}
3050
3051static void tangent_conic(const SkPoint pts[], SkScalar x, SkScalar y, SkScalar w,
3052 SkTDArray<SkVector>* tangents) {
3053 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)) {
3054 return;
3055 }
3056 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)) {
3057 return;
3058 }
3059 SkScalar roots[2];
3060 SkScalar A = pts[2].fY;
3061 SkScalar B = pts[1].fY * w - y * w + y;
3062 SkScalar C = pts[0].fY;
3063 A += C - 2 * B; // A = a + c - 2*(b*w - yCept*w + yCept)
3064 B -= C; // B = b*w - w * yCept + yCept - a
3065 C -= y;
3066 int n = SkFindUnitQuadRoots(A, 2 * B, C, roots);
3067 for (int index = 0; index < n; ++index) {
3068 SkScalar t = roots[index];
3069 SkScalar xt = conic_eval_numerator(&pts[0].fX, w, t) / conic_eval_denominator(w, t);
3070 if (!SkScalarNearlyEqual(x, xt)) {
3071 continue;
3072 }
3073 SkConic conic(pts, w);
3074 tangents->push(conic.evalTangentAt(t));
3075 }
3076}
3077
3078static void tangent_quad(const SkPoint pts[], SkScalar x, SkScalar y,
3079 SkTDArray<SkVector>* tangents) {
3080 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)) {
3081 return;
3082 }
3083 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)) {
3084 return;
3085 }
3086 SkScalar roots[2];
3087 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
3088 2 * (pts[1].fY - pts[0].fY),
3089 pts[0].fY - y,
3090 roots);
3091 for (int index = 0; index < n; ++index) {
3092 SkScalar t = roots[index];
3093 SkScalar C = pts[0].fX;
3094 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
3095 SkScalar B = 2 * (pts[1].fX - C);
3096 SkScalar xt = (A * t + B) * t + C;
3097 if (!SkScalarNearlyEqual(x, xt)) {
3098 continue;
3099 }
3100 tangents->push(SkEvalQuadTangentAt(pts, t));
3101 }
3102}
3103
3104static void tangent_line(const SkPoint pts[], SkScalar x, SkScalar y,
3105 SkTDArray<SkVector>* tangents) {
3106 SkScalar y0 = pts[0].fY;
3107 SkScalar y1 = pts[1].fY;
3108 if (!between(y0, y, y1)) {
3109 return;
3110 }
3111 SkScalar x0 = pts[0].fX;
3112 SkScalar x1 = pts[1].fX;
3113 if (!between(x0, x, x1)) {
3114 return;
3115 }
3116 SkScalar dx = x1 - x0;
3117 SkScalar dy = y1 - y0;
3118 if (!SkScalarNearlyEqual((x - x0) * dy, dx * (y - y0))) {
3119 return;
3120 }
3121 SkVector v;
3122 v.set(dx, dy);
3123 tangents->push(v);
3124}
3125
reed@google.com4db592c2013-10-30 17:39:43 +00003126static bool contains_inclusive(const SkRect& r, SkScalar x, SkScalar y) {
3127 return r.fLeft <= x && x <= r.fRight && r.fTop <= y && y <= r.fBottom;
3128}
3129
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003130bool SkPath::contains(SkScalar x, SkScalar y) const {
3131 bool isInverse = this->isInverseFillType();
3132 if (this->isEmpty()) {
3133 return isInverse;
3134 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003135
reed@google.com4db592c2013-10-30 17:39:43 +00003136 if (!contains_inclusive(this->getBounds(), x, y)) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003137 return isInverse;
3138 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003139
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003140 SkPath::Iter iter(*this, true);
3141 bool done = false;
3142 int w = 0;
caryclark9aacd902015-12-14 08:38:09 -08003143 int onCurveCount = 0;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003144 do {
3145 SkPoint pts[4];
3146 switch (iter.next(pts, false)) {
3147 case SkPath::kMove_Verb:
3148 case SkPath::kClose_Verb:
3149 break;
3150 case SkPath::kLine_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003151 w += winding_line(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003152 break;
3153 case SkPath::kQuad_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003154 w += winding_quad(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003155 break;
reed@google.com277c3f82013-05-31 15:17:50 +00003156 case SkPath::kConic_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003157 w += winding_conic(pts, x, y, iter.conicWeight(), &onCurveCount);
reed@google.com277c3f82013-05-31 15:17:50 +00003158 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003159 case SkPath::kCubic_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003160 w += winding_cubic(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003161 break;
3162 case SkPath::kDone_Verb:
3163 done = true;
3164 break;
reed@google.com277c3f82013-05-31 15:17:50 +00003165 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003166 } while (!done);
caryclark9aacd902015-12-14 08:38:09 -08003167 bool evenOddFill = SkPath::kEvenOdd_FillType == this->getFillType()
3168 || SkPath::kInverseEvenOdd_FillType == this->getFillType();
3169 if (evenOddFill) {
3170 w &= 1;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003171 }
caryclark9aacd902015-12-14 08:38:09 -08003172 if (w) {
3173 return !isInverse;
3174 }
3175 if (onCurveCount <= 1) {
3176 return SkToBool(onCurveCount) ^ isInverse;
3177 }
3178 if ((onCurveCount & 1) || evenOddFill) {
3179 return SkToBool(onCurveCount & 1) ^ isInverse;
3180 }
halcanary9d524f22016-03-29 09:03:52 -07003181 // If the point touches an even number of curves, and the fill is winding, check for
caryclark9aacd902015-12-14 08:38:09 -08003182 // coincidence. Count coincidence as places where the on curve points have identical tangents.
3183 iter.setPath(*this, true);
caryclark9cb5d752015-12-18 04:35:24 -08003184 done = false;
caryclark9aacd902015-12-14 08:38:09 -08003185 SkTDArray<SkVector> tangents;
3186 do {
3187 SkPoint pts[4];
3188 int oldCount = tangents.count();
3189 switch (iter.next(pts, false)) {
3190 case SkPath::kMove_Verb:
3191 case SkPath::kClose_Verb:
3192 break;
3193 case SkPath::kLine_Verb:
3194 tangent_line(pts, x, y, &tangents);
3195 break;
3196 case SkPath::kQuad_Verb:
3197 tangent_quad(pts, x, y, &tangents);
3198 break;
3199 case SkPath::kConic_Verb:
3200 tangent_conic(pts, x, y, iter.conicWeight(), &tangents);
3201 break;
3202 case SkPath::kCubic_Verb:
3203 tangent_cubic(pts, x, y, &tangents);
3204 break;
3205 case SkPath::kDone_Verb:
3206 done = true;
3207 break;
3208 }
3209 if (tangents.count() > oldCount) {
3210 int last = tangents.count() - 1;
3211 const SkVector& tangent = tangents[last];
3212 if (SkScalarNearlyZero(tangent.lengthSqd())) {
3213 tangents.remove(last);
3214 } else {
3215 for (int index = 0; index < last; ++index) {
3216 const SkVector& test = tangents[index];
3217 if (SkScalarNearlyZero(test.cross(tangent))
caryclark9cb5d752015-12-18 04:35:24 -08003218 && SkScalarSignAsInt(tangent.fX * test.fX) <= 0
3219 && SkScalarSignAsInt(tangent.fY * test.fY) <= 0) {
caryclark9aacd902015-12-14 08:38:09 -08003220 tangents.remove(last);
3221 tangents.removeShuffle(index);
3222 break;
3223 }
3224 }
3225 }
3226 }
3227 } while (!done);
3228 return SkToBool(tangents.count()) ^ isInverse;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003229}
fmalitaaa0df4e2015-12-01 09:13:23 -08003230
3231int SkPath::ConvertConicToQuads(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2,
3232 SkScalar w, SkPoint pts[], int pow2) {
3233 const SkConic conic(p0, p1, p2, w);
3234 return conic.chopIntoQuadsPOW2(pts, pow2);
3235}