blob: 06bbeef096ba9734cb0a733c7d9b983893ad658a [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
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001187void SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
1188 Direction dir) {
1189 assert_known_direction(dir);
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001190
humper@google.com75e3ca12013-04-08 21:44:11 +00001191 if (rx < 0 || ry < 0) {
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001192 SkErrorInternals::SetError( kInvalidArgument_SkError,
humper@google.com75e3ca12013-04-08 21:44:11 +00001193 "I got %f and %f as radii to SkPath::AddRoundRect, "
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001194 "but negative radii are not allowed.",
humper@google.com75e3ca12013-04-08 21:44:11 +00001195 SkScalarToDouble(rx), SkScalarToDouble(ry) );
1196 return;
1197 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001198
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001199 SkRRect rrect;
1200 rrect.setRectXY(rect, rx, ry);
1201 this->addRRect(rrect, dir);
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001202}
1203
reed@android.com8a1c16f2008-12-17 15:59:43 +00001204void SkPath::addOval(const SkRect& oval, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -08001205 // legacy start index: 1
1206 this->addOval(oval, dir, 1);
1207}
1208
1209void SkPath::addOval(const SkRect &oval, Direction dir, unsigned startPointIndex) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +00001210 assert_known_direction(dir);
1211
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001212 /* If addOval() is called after previous moveTo(),
1213 this path is still marked as an oval. This is used to
1214 fit into WebKit's calling sequences.
1215 We can't simply check isEmpty() in this case, as additional
1216 moveTo() would mark the path non empty.
1217 */
robertphillips@google.com466310d2013-12-03 16:43:54 +00001218 bool isOval = hasOnlyMoveTos();
1219 if (isOval) {
reed026beb52015-06-10 14:23:15 -07001220 fFirstDirection = (SkPathPriv::FirstDirection)dir;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001221 } else {
reed026beb52015-06-10 14:23:15 -07001222 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001223 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001224
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001225 SkAutoDisableDirectionCheck addc(this);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001226 SkAutoPathBoundsUpdate apbu(this, oval);
1227
fmalitac08d53e2015-11-17 09:53:29 -08001228 SkDEBUGCODE(int initialVerbCount = this->countVerbs());
1229 const int kVerbs = 6; // moveTo + 4x conicTo + close
1230 this->incReserve(kVerbs);
1231
1232 OvalPointIterator ovalIter(oval, dir, startPointIndex);
1233 // The corner iterator pts are tracking "behind" the oval/radii pts.
1234 RectPointIterator rectIter(oval, dir, startPointIndex + (dir == kCW_Direction ? 0 : 1));
reed220f9262014-12-17 08:21:04 -08001235 const SkScalar weight = SK_ScalarRoot2Over2;
1236
fmalitac08d53e2015-11-17 09:53:29 -08001237 this->moveTo(ovalIter.current());
1238 for (unsigned i = 0; i < 4; ++i) {
1239 this->conicTo(rectIter.next(), ovalIter.next(), weight);
reed220f9262014-12-17 08:21:04 -08001240 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001241 this->close();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001242
fmalitac08d53e2015-11-17 09:53:29 -08001243 SkASSERT(this->countVerbs() == initialVerbCount + kVerbs);
1244
robertphillips@google.com466310d2013-12-03 16:43:54 +00001245 SkPathRef::Editor ed(&fPathRef);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001246
robertphillips@google.com466310d2013-12-03 16:43:54 +00001247 ed.setIsOval(isOval);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001248}
1249
reed@android.com8a1c16f2008-12-17 15:59:43 +00001250void SkPath::addCircle(SkScalar x, SkScalar y, SkScalar r, Direction dir) {
1251 if (r > 0) {
fmalitac08d53e2015-11-17 09:53:29 -08001252 this->addOval(SkRect::MakeLTRB(x - r, y - r, x + r, y + r), dir);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001253 }
1254}
1255
reed@android.com8a1c16f2008-12-17 15:59:43 +00001256void SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
1257 bool forceMoveTo) {
1258 if (oval.width() < 0 || oval.height() < 0) {
1259 return;
1260 }
1261
reedf90ea012015-01-29 12:03:58 -08001262 if (fPathRef->countVerbs() == 0) {
1263 forceMoveTo = true;
1264 }
1265
1266 SkPoint lonePt;
1267 if (arc_is_lone_point(oval, startAngle, sweepAngle, &lonePt)) {
1268 forceMoveTo ? this->moveTo(lonePt) : this->lineTo(lonePt);
1269 return;
1270 }
1271
reedd5d27d92015-02-09 13:54:43 -08001272 SkVector startV, stopV;
1273 SkRotationDirection dir;
1274 angles_to_unit_vectors(startAngle, sweepAngle, &startV, &stopV, &dir);
1275
reed9e779d42015-02-17 11:43:14 -08001276 SkPoint singlePt;
reedd5d27d92015-02-09 13:54:43 -08001277 SkConic conics[SkConic::kMaxConicsForArc];
reed9e779d42015-02-17 11:43:14 -08001278 int count = build_arc_conics(oval, startV, stopV, dir, conics, &singlePt);
reedd5d27d92015-02-09 13:54:43 -08001279 if (count) {
1280 this->incReserve(count * 2 + 1);
1281 const SkPoint& pt = conics[0].fPts[0];
1282 forceMoveTo ? this->moveTo(pt) : this->lineTo(pt);
1283 for (int i = 0; i < count; ++i) {
1284 this->conicTo(conics[i].fPts[1], conics[i].fPts[2], conics[i].fW);
1285 }
reed9e779d42015-02-17 11:43:14 -08001286 } else {
1287 forceMoveTo ? this->moveTo(singlePt) : this->lineTo(singlePt);
reedd5d27d92015-02-09 13:54:43 -08001288 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001289}
1290
caryclark55d49052016-01-23 05:07:04 -08001291// This converts the SVG arc to conics.
1292// Partly adapted from Niko's code in kdelibs/kdecore/svgicons.
1293// Then transcribed from webkit/chrome's SVGPathNormalizer::decomposeArcToCubic()
1294// See also SVG implementation notes:
1295// http://www.w3.org/TR/SVG/implnote.html#ArcConversionEndpointToCenter
1296// Note that arcSweep bool value is flipped from the original implementation.
1297void SkPath::arcTo(SkScalar rx, SkScalar ry, SkScalar angle, SkPath::ArcSize arcLarge,
1298 SkPath::Direction arcSweep, SkScalar x, SkScalar y) {
caryclarkf1d41512016-02-09 10:30:22 -08001299 this->injectMoveToIfNeeded();
caryclark55d49052016-01-23 05:07:04 -08001300 SkPoint srcPts[2];
1301 this->getLastPt(&srcPts[0]);
1302 // If rx = 0 or ry = 0 then this arc is treated as a straight line segment (a "lineto")
1303 // joining the endpoints.
1304 // http://www.w3.org/TR/SVG/implnote.html#ArcOutOfRangeParameters
1305 if (!rx || !ry) {
caryclarkfc752532016-01-25 05:39:04 -08001306 this->lineTo(x, y);
caryclark55d49052016-01-23 05:07:04 -08001307 return;
1308 }
1309 // If the current point and target point for the arc are identical, it should be treated as a
1310 // zero length path. This ensures continuity in animations.
1311 srcPts[1].set(x, y);
1312 if (srcPts[0] == srcPts[1]) {
caryclarkfc752532016-01-25 05:39:04 -08001313 this->lineTo(x, y);
caryclark55d49052016-01-23 05:07:04 -08001314 return;
1315 }
1316 rx = SkScalarAbs(rx);
1317 ry = SkScalarAbs(ry);
1318 SkVector midPointDistance = srcPts[0] - srcPts[1];
1319 midPointDistance *= 0.5f;
1320
1321 SkMatrix pointTransform;
1322 pointTransform.setRotate(-angle);
1323
1324 SkPoint transformedMidPoint;
1325 pointTransform.mapPoints(&transformedMidPoint, &midPointDistance, 1);
1326 SkScalar squareRx = rx * rx;
1327 SkScalar squareRy = ry * ry;
1328 SkScalar squareX = transformedMidPoint.fX * transformedMidPoint.fX;
1329 SkScalar squareY = transformedMidPoint.fY * transformedMidPoint.fY;
1330
1331 // Check if the radii are big enough to draw the arc, scale radii if not.
1332 // http://www.w3.org/TR/SVG/implnote.html#ArcCorrectionOutOfRangeRadii
1333 SkScalar radiiScale = squareX / squareRx + squareY / squareRy;
1334 if (radiiScale > 1) {
1335 radiiScale = SkScalarSqrt(radiiScale);
1336 rx *= radiiScale;
1337 ry *= radiiScale;
1338 }
1339
1340 pointTransform.setScale(1 / rx, 1 / ry);
1341 pointTransform.preRotate(-angle);
1342
1343 SkPoint unitPts[2];
1344 pointTransform.mapPoints(unitPts, srcPts, (int) SK_ARRAY_COUNT(unitPts));
1345 SkVector delta = unitPts[1] - unitPts[0];
1346
1347 SkScalar d = delta.fX * delta.fX + delta.fY * delta.fY;
1348 SkScalar scaleFactorSquared = SkTMax(1 / d - 0.25f, 0.f);
1349
1350 SkScalar scaleFactor = SkScalarSqrt(scaleFactorSquared);
1351 if (SkToBool(arcSweep) != SkToBool(arcLarge)) { // flipped from the original implementation
1352 scaleFactor = -scaleFactor;
1353 }
1354 delta.scale(scaleFactor);
1355 SkPoint centerPoint = unitPts[0] + unitPts[1];
1356 centerPoint *= 0.5f;
1357 centerPoint.offset(-delta.fY, delta.fX);
1358 unitPts[0] -= centerPoint;
1359 unitPts[1] -= centerPoint;
1360 SkScalar theta1 = SkScalarATan2(unitPts[0].fY, unitPts[0].fX);
1361 SkScalar theta2 = SkScalarATan2(unitPts[1].fY, unitPts[1].fX);
1362 SkScalar thetaArc = theta2 - theta1;
1363 if (thetaArc < 0 && !arcSweep) { // arcSweep flipped from the original implementation
1364 thetaArc += SK_ScalarPI * 2;
1365 } else if (thetaArc > 0 && arcSweep) { // arcSweep flipped from the original implementation
1366 thetaArc -= SK_ScalarPI * 2;
1367 }
1368 pointTransform.setRotate(angle);
1369 pointTransform.preScale(rx, ry);
1370
1371 int segments = SkScalarCeilToInt(SkScalarAbs(thetaArc / (SK_ScalarPI / 2)));
1372 SkScalar thetaWidth = thetaArc / segments;
1373 SkScalar t = SkScalarTan(0.5f * thetaWidth);
1374 if (!SkScalarIsFinite(t)) {
1375 return;
1376 }
1377 SkScalar startTheta = theta1;
1378 SkScalar w = SkScalarSqrt(SK_ScalarHalf + SkScalarCos(thetaWidth) * SK_ScalarHalf);
1379 for (int i = 0; i < segments; ++i) {
1380 SkScalar endTheta = startTheta + thetaWidth;
1381 SkScalar cosEndTheta, sinEndTheta = SkScalarSinCos(endTheta, &cosEndTheta);
1382
1383 unitPts[1].set(cosEndTheta, sinEndTheta);
1384 unitPts[1] += centerPoint;
1385 unitPts[0] = unitPts[1];
1386 unitPts[0].offset(t * sinEndTheta, -t * cosEndTheta);
1387 SkPoint mapped[2];
1388 pointTransform.mapPoints(mapped, unitPts, (int) SK_ARRAY_COUNT(unitPts));
1389 this->conicTo(mapped[0], mapped[1], w);
1390 startTheta = endTheta;
1391 }
1392}
1393
1394void SkPath::rArcTo(SkScalar rx, SkScalar ry, SkScalar xAxisRotate, SkPath::ArcSize largeArc,
1395 SkPath::Direction sweep, SkScalar dx, SkScalar dy) {
1396 SkPoint currentPoint;
1397 this->getLastPt(&currentPoint);
1398 this->arcTo(rx, ry, xAxisRotate, largeArc, sweep, currentPoint.fX + dx, currentPoint.fY + dy);
1399}
1400
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001401void SkPath::addArc(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001402 if (oval.isEmpty() || 0 == sweepAngle) {
1403 return;
1404 }
1405
1406 const SkScalar kFullCircleAngle = SkIntToScalar(360);
1407
1408 if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
1409 this->addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction);
reedc7789042015-01-29 12:59:11 -08001410 } else {
1411 this->arcTo(oval, startAngle, sweepAngle, true);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001412 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001413}
1414
1415/*
1416 Need to handle the case when the angle is sharp, and our computed end-points
1417 for the arc go behind pt1 and/or p2...
1418*/
reedc7789042015-01-29 12:59:11 -08001419void SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2, SkScalar radius) {
reeda8b326c2014-12-09 11:50:32 -08001420 if (radius == 0) {
1421 this->lineTo(x1, y1);
1422 return;
1423 }
1424
1425 SkVector before, after;
reed@google.comabf15c12011-01-18 20:35:51 +00001426
reed@android.com8a1c16f2008-12-17 15:59:43 +00001427 // need to know our prev pt so we can construct tangent vectors
1428 {
1429 SkPoint start;
1430 this->getLastPt(&start);
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001431 // Handle degenerate cases by adding a line to the first point and
1432 // bailing out.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001433 before.setNormalize(x1 - start.fX, y1 - start.fY);
1434 after.setNormalize(x2 - x1, y2 - y1);
1435 }
reed@google.comabf15c12011-01-18 20:35:51 +00001436
reed@android.com8a1c16f2008-12-17 15:59:43 +00001437 SkScalar cosh = SkPoint::DotProduct(before, after);
1438 SkScalar sinh = SkPoint::CrossProduct(before, after);
1439
1440 if (SkScalarNearlyZero(sinh)) { // angle is too tight
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001441 this->lineTo(x1, y1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001442 return;
1443 }
reed@google.comabf15c12011-01-18 20:35:51 +00001444
caryclark88651ae2016-01-20 11:55:11 -08001445 SkScalar dist = SkScalarAbs(SkScalarMulDiv(radius, SK_Scalar1 - cosh, sinh));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001446
1447 SkScalar xx = x1 - SkScalarMul(dist, before.fX);
1448 SkScalar yy = y1 - SkScalarMul(dist, before.fY);
caryclark88651ae2016-01-20 11:55:11 -08001449 after.setLength(dist);
1450 this->lineTo(xx, yy);
1451 SkScalar weight = SkScalarSqrt(SK_ScalarHalf + cosh * SK_ScalarHalf);
1452 this->conicTo(x1, y1, x1 + after.fX, y1 + after.fY, weight);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001453}
1454
1455///////////////////////////////////////////////////////////////////////////////
1456
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001457void SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy, AddPathMode mode) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001458 SkMatrix matrix;
1459
1460 matrix.setTranslate(dx, dy);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001461 this->addPath(path, matrix, mode);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001462}
1463
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001464void SkPath::addPath(const SkPath& path, const SkMatrix& matrix, AddPathMode mode) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001465 SkPathRef::Editor(&fPathRef, path.countVerbs(), path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001466
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001467 RawIter iter(path);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001468 SkPoint pts[4];
1469 Verb verb;
1470
1471 SkMatrix::MapPtsProc proc = matrix.getMapPtsProc();
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001472 bool firstVerb = true;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001473 while ((verb = iter.next(pts)) != kDone_Verb) {
1474 switch (verb) {
1475 case kMove_Verb:
1476 proc(matrix, &pts[0], &pts[0], 1);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001477 if (firstVerb && mode == kExtend_AddPathMode && !isEmpty()) {
1478 injectMoveToIfNeeded(); // In case last contour is closed
1479 this->lineTo(pts[0]);
1480 } else {
1481 this->moveTo(pts[0]);
1482 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001483 break;
1484 case kLine_Verb:
1485 proc(matrix, &pts[1], &pts[1], 1);
1486 this->lineTo(pts[1]);
1487 break;
1488 case kQuad_Verb:
1489 proc(matrix, &pts[1], &pts[1], 2);
1490 this->quadTo(pts[1], pts[2]);
1491 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001492 case kConic_Verb:
1493 proc(matrix, &pts[1], &pts[1], 2);
1494 this->conicTo(pts[1], pts[2], iter.conicWeight());
1495 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001496 case kCubic_Verb:
1497 proc(matrix, &pts[1], &pts[1], 3);
1498 this->cubicTo(pts[1], pts[2], pts[3]);
1499 break;
1500 case kClose_Verb:
1501 this->close();
1502 break;
1503 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001504 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001505 }
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001506 firstVerb = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001507 }
1508}
1509
1510///////////////////////////////////////////////////////////////////////////////
1511
reed@google.com277c3f82013-05-31 15:17:50 +00001512static int pts_in_verb(unsigned verb) {
1513 static const uint8_t gPtsInVerb[] = {
1514 1, // kMove
1515 1, // kLine
1516 2, // kQuad
1517 2, // kConic
1518 3, // kCubic
1519 0, // kClose
1520 0 // kDone
1521 };
1522
1523 SkASSERT(verb < SK_ARRAY_COUNT(gPtsInVerb));
1524 return gPtsInVerb[verb];
1525}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001526
reed@android.com8a1c16f2008-12-17 15:59:43 +00001527// ignore the last point of the 1st contour
1528void SkPath::reversePathTo(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001529 int i, vcount = path.fPathRef->countVerbs();
1530 // exit early if the path is empty, or just has a moveTo.
1531 if (vcount < 2) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001532 return;
1533 }
1534
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001535 SkPathRef::Editor(&fPathRef, vcount, path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001536
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001537 const uint8_t* verbs = path.fPathRef->verbs();
1538 const SkPoint* pts = path.fPathRef->points();
reed@google.com277c3f82013-05-31 15:17:50 +00001539 const SkScalar* conicWeights = path.fPathRef->conicWeights();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001540
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001541 SkASSERT(verbs[~0] == kMove_Verb);
1542 for (i = 1; i < vcount; ++i) {
reed@google.com277c3f82013-05-31 15:17:50 +00001543 unsigned v = verbs[~i];
1544 int n = pts_in_verb(v);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001545 if (n == 0) {
1546 break;
1547 }
1548 pts += n;
reed@google.com277c3f82013-05-31 15:17:50 +00001549 conicWeights += (SkPath::kConic_Verb == v);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001550 }
1551
1552 while (--i > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001553 switch (verbs[~i]) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001554 case kLine_Verb:
1555 this->lineTo(pts[-1].fX, pts[-1].fY);
1556 break;
1557 case kQuad_Verb:
1558 this->quadTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY);
1559 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001560 case kConic_Verb:
1561 this->conicTo(pts[-1], pts[-2], *--conicWeights);
1562 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001563 case kCubic_Verb:
1564 this->cubicTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY,
1565 pts[-3].fX, pts[-3].fY);
1566 break;
1567 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001568 SkDEBUGFAIL("bad verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001569 break;
1570 }
reed@google.com277c3f82013-05-31 15:17:50 +00001571 pts -= pts_in_verb(verbs[~i]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001572 }
1573}
1574
reed@google.com63d73742012-01-10 15:33:12 +00001575void SkPath::reverseAddPath(const SkPath& src) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001576 SkPathRef::Editor ed(&fPathRef, src.fPathRef->countPoints(), src.fPathRef->countVerbs());
reed@google.com63d73742012-01-10 15:33:12 +00001577
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001578 const SkPoint* pts = src.fPathRef->pointsEnd();
1579 // we will iterator through src's verbs backwards
1580 const uint8_t* verbs = src.fPathRef->verbsMemBegin(); // points at the last verb
1581 const uint8_t* verbsEnd = src.fPathRef->verbs(); // points just past the first verb
reed@google.com277c3f82013-05-31 15:17:50 +00001582 const SkScalar* conicWeights = src.fPathRef->conicWeightsEnd();
reed@google.com63d73742012-01-10 15:33:12 +00001583
1584 bool needMove = true;
1585 bool needClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001586 while (verbs < verbsEnd) {
1587 uint8_t v = *(verbs++);
reed@google.com277c3f82013-05-31 15:17:50 +00001588 int n = pts_in_verb(v);
reed@google.com63d73742012-01-10 15:33:12 +00001589
1590 if (needMove) {
1591 --pts;
1592 this->moveTo(pts->fX, pts->fY);
1593 needMove = false;
1594 }
1595 pts -= n;
1596 switch (v) {
1597 case kMove_Verb:
1598 if (needClose) {
1599 this->close();
1600 needClose = false;
1601 }
1602 needMove = true;
1603 pts += 1; // so we see the point in "if (needMove)" above
1604 break;
1605 case kLine_Verb:
1606 this->lineTo(pts[0]);
1607 break;
1608 case kQuad_Verb:
1609 this->quadTo(pts[1], pts[0]);
1610 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001611 case kConic_Verb:
1612 this->conicTo(pts[1], pts[0], *--conicWeights);
1613 break;
reed@google.com63d73742012-01-10 15:33:12 +00001614 case kCubic_Verb:
1615 this->cubicTo(pts[2], pts[1], pts[0]);
1616 break;
1617 case kClose_Verb:
1618 needClose = true;
1619 break;
1620 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00001621 SkDEBUGFAIL("unexpected verb");
reed@google.com63d73742012-01-10 15:33:12 +00001622 }
1623 }
1624}
1625
reed@android.com8a1c16f2008-12-17 15:59:43 +00001626///////////////////////////////////////////////////////////////////////////////
1627
1628void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
1629 SkMatrix matrix;
1630
1631 matrix.setTranslate(dx, dy);
1632 this->transform(matrix, dst);
1633}
1634
reed@android.com8a1c16f2008-12-17 15:59:43 +00001635static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
1636 int level = 2) {
1637 if (--level >= 0) {
1638 SkPoint tmp[7];
1639
1640 SkChopCubicAtHalf(pts, tmp);
1641 subdivide_cubic_to(path, &tmp[0], level);
1642 subdivide_cubic_to(path, &tmp[3], level);
1643 } else {
1644 path->cubicTo(pts[1], pts[2], pts[3]);
1645 }
1646}
1647
1648void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
1649 SkDEBUGCODE(this->validate();)
halcanary96fcdcc2015-08-27 07:41:13 -07001650 if (dst == nullptr) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001651 dst = (SkPath*)this;
1652 }
1653
tomhudson@google.com8d430182011-06-06 19:11:19 +00001654 if (matrix.hasPerspective()) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001655 SkPath tmp;
1656 tmp.fFillType = fFillType;
1657
1658 SkPath::Iter iter(*this, false);
1659 SkPoint pts[4];
1660 SkPath::Verb verb;
1661
reed@google.com4a3b7142012-05-16 17:16:46 +00001662 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001663 switch (verb) {
1664 case kMove_Verb:
1665 tmp.moveTo(pts[0]);
1666 break;
1667 case kLine_Verb:
1668 tmp.lineTo(pts[1]);
1669 break;
1670 case kQuad_Verb:
reed220f9262014-12-17 08:21:04 -08001671 // promote the quad to a conic
1672 tmp.conicTo(pts[1], pts[2],
1673 SkConic::TransformW(pts, SK_Scalar1, matrix));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001674 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001675 case kConic_Verb:
reed220f9262014-12-17 08:21:04 -08001676 tmp.conicTo(pts[1], pts[2],
1677 SkConic::TransformW(pts, iter.conicWeight(), matrix));
reed@google.com277c3f82013-05-31 15:17:50 +00001678 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001679 case kCubic_Verb:
1680 subdivide_cubic_to(&tmp, pts);
1681 break;
1682 case kClose_Verb:
1683 tmp.close();
1684 break;
1685 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001686 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001687 break;
1688 }
1689 }
1690
1691 dst->swap(tmp);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001692 SkPathRef::Editor ed(&dst->fPathRef);
1693 matrix.mapPoints(ed.points(), ed.pathRef()->countPoints());
reed026beb52015-06-10 14:23:15 -07001694 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001695 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001696 SkPathRef::CreateTransformedCopy(&dst->fPathRef, *fPathRef.get(), matrix);
1697
reed@android.com8a1c16f2008-12-17 15:59:43 +00001698 if (this != dst) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001699 dst->fFillType = fFillType;
reed@google.com2a6f8ab2011-10-25 18:41:23 +00001700 dst->fConvexity = fConvexity;
jvanverthb3eb6872014-10-24 07:12:51 -07001701 dst->fIsVolatile = fIsVolatile;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001702 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001703
reed026beb52015-06-10 14:23:15 -07001704 if (SkPathPriv::kUnknown_FirstDirection == fFirstDirection) {
1705 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001706 } else {
1707 SkScalar det2x2 =
1708 SkScalarMul(matrix.get(SkMatrix::kMScaleX), matrix.get(SkMatrix::kMScaleY)) -
1709 SkScalarMul(matrix.get(SkMatrix::kMSkewX), matrix.get(SkMatrix::kMSkewY));
1710 if (det2x2 < 0) {
herb9f4dbca2015-09-28 11:05:47 -07001711 dst->fFirstDirection = SkPathPriv::OppositeFirstDirection(
1712 (SkPathPriv::FirstDirection)fFirstDirection.load());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001713 } else if (det2x2 > 0) {
herb9f4dbca2015-09-28 11:05:47 -07001714 dst->fFirstDirection = fFirstDirection.load();
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001715 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001716 dst->fConvexity = kUnknown_Convexity;
reed026beb52015-06-10 14:23:15 -07001717 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001718 }
1719 }
1720
reed@android.com8a1c16f2008-12-17 15:59:43 +00001721 SkDEBUGCODE(dst->validate();)
1722 }
1723}
1724
reed@android.com8a1c16f2008-12-17 15:59:43 +00001725///////////////////////////////////////////////////////////////////////////////
1726///////////////////////////////////////////////////////////////////////////////
1727
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001728enum SegmentState {
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001729 kEmptyContour_SegmentState, // The current contour is empty. We may be
1730 // starting processing or we may have just
1731 // closed a contour.
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001732 kAfterMove_SegmentState, // We have seen a move, but nothing else.
1733 kAfterPrimitive_SegmentState // We have seen a primitive but not yet
1734 // closed the path. Also the initial state.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001735};
1736
1737SkPath::Iter::Iter() {
1738#ifdef SK_DEBUG
halcanary96fcdcc2015-08-27 07:41:13 -07001739 fPts = nullptr;
1740 fConicWeights = nullptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001741 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001742 fForceClose = fCloseLine = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001743 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001744#endif
1745 // need to init enough to make next() harmlessly return kDone_Verb
halcanary96fcdcc2015-08-27 07:41:13 -07001746 fVerbs = nullptr;
1747 fVerbStop = nullptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001748 fNeedClose = false;
1749}
1750
1751SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
1752 this->setPath(path, forceClose);
1753}
1754
1755void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001756 fPts = path.fPathRef->points();
1757 fVerbs = path.fPathRef->verbs();
1758 fVerbStop = path.fPathRef->verbsMemBegin();
reed@google.com277c3f82013-05-31 15:17:50 +00001759 fConicWeights = path.fPathRef->conicWeights() - 1; // begin one behind
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001760 fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org72785c42011-12-29 21:03:28 +00001761 fMoveTo.fX = fMoveTo.fY = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001762 fForceClose = SkToU8(forceClose);
1763 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001764 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001765}
1766
1767bool SkPath::Iter::isClosedContour() const {
halcanary96fcdcc2015-08-27 07:41:13 -07001768 if (fVerbs == nullptr || fVerbs == fVerbStop) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001769 return false;
1770 }
1771 if (fForceClose) {
1772 return true;
1773 }
1774
1775 const uint8_t* verbs = fVerbs;
1776 const uint8_t* stop = fVerbStop;
reed@google.comabf15c12011-01-18 20:35:51 +00001777
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001778 if (kMove_Verb == *(verbs - 1)) {
1779 verbs -= 1; // skip the initial moveto
reed@android.com8a1c16f2008-12-17 15:59:43 +00001780 }
1781
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001782 while (verbs > stop) {
1783 // verbs points one beyond the current verb, decrement first.
1784 unsigned v = *(--verbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001785 if (kMove_Verb == v) {
1786 break;
1787 }
1788 if (kClose_Verb == v) {
1789 return true;
1790 }
1791 }
1792 return false;
1793}
1794
1795SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001796 SkASSERT(pts);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001797 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001798 // A special case: if both points are NaN, SkPoint::operation== returns
1799 // false, but the iterator expects that they are treated as the same.
1800 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001801 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1802 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001803 return kClose_Verb;
1804 }
1805
reed@google.com9e25dbf2012-05-15 17:05:38 +00001806 pts[0] = fLastPt;
1807 pts[1] = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001808 fLastPt = fMoveTo;
1809 fCloseLine = true;
1810 return kLine_Verb;
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +00001811 } else {
1812 pts[0] = fMoveTo;
1813 return kClose_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001814 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001815}
1816
reed@google.com9e25dbf2012-05-15 17:05:38 +00001817const SkPoint& SkPath::Iter::cons_moveTo() {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001818 if (fSegmentState == kAfterMove_SegmentState) {
1819 // Set the first return pt to the move pt
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001820 fSegmentState = kAfterPrimitive_SegmentState;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001821 return fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001822 } else {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001823 SkASSERT(fSegmentState == kAfterPrimitive_SegmentState);
1824 // Set the first return pt to the last pt of the previous primitive.
reed@google.com9e25dbf2012-05-15 17:05:38 +00001825 return fPts[-1];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001826 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001827}
1828
caryclarke8c56662015-07-14 11:19:26 -07001829void SkPath::Iter::consumeDegenerateSegments(bool exact) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001830 // We need to step over anything that will not move the current draw point
1831 // forward before the next move is seen
1832 const uint8_t* lastMoveVerb = 0;
1833 const SkPoint* lastMovePt = 0;
halcanary96fcdcc2015-08-27 07:41:13 -07001834 const SkScalar* lastMoveWeight = nullptr;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001835 SkPoint lastPt = fLastPt;
1836 while (fVerbs != fVerbStop) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001837 unsigned verb = *(fVerbs - 1); // fVerbs is one beyond the current verb
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001838 switch (verb) {
1839 case kMove_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001840 // Keep a record of this most recent move
1841 lastMoveVerb = fVerbs;
1842 lastMovePt = fPts;
robertphillipsb8de1f42015-02-23 11:17:01 -08001843 lastMoveWeight = fConicWeights;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001844 lastPt = fPts[0];
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001845 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001846 fPts++;
1847 break;
1848
1849 case kClose_Verb:
schenney@chromium.org7e963602012-06-13 17:05:43 +00001850 // A close when we are in a segment is always valid except when it
1851 // follows a move which follows a segment.
1852 if (fSegmentState == kAfterPrimitive_SegmentState && !lastMoveVerb) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001853 return;
1854 }
1855 // A close at any other time must be ignored
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001856 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001857 break;
1858
1859 case kLine_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001860 if (!IsLineDegenerate(lastPt, fPts[0], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001861 if (lastMoveVerb) {
1862 fVerbs = lastMoveVerb;
1863 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001864 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001865 return;
1866 }
1867 return;
1868 }
1869 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001870 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001871 fPts++;
1872 break;
1873
reed@google.com277c3f82013-05-31 15:17:50 +00001874 case kConic_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001875 case kQuad_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001876 if (!IsQuadDegenerate(lastPt, fPts[0], fPts[1], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001877 if (lastMoveVerb) {
1878 fVerbs = lastMoveVerb;
1879 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001880 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001881 return;
1882 }
1883 return;
1884 }
1885 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001886 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001887 fPts += 2;
reed@google.com277c3f82013-05-31 15:17:50 +00001888 fConicWeights += (kConic_Verb == verb);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001889 break;
1890
1891 case kCubic_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001892 if (!IsCubicDegenerate(lastPt, fPts[0], fPts[1], fPts[2], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001893 if (lastMoveVerb) {
1894 fVerbs = lastMoveVerb;
1895 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001896 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001897 return;
1898 }
1899 return;
1900 }
1901 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001902 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001903 fPts += 3;
1904 break;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001905
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001906 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001907 SkDEBUGFAIL("Should never see kDone_Verb");
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001908 }
1909 }
1910}
1911
reed@google.com4a3b7142012-05-16 17:16:46 +00001912SkPath::Verb SkPath::Iter::doNext(SkPoint ptsParam[4]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001913 SkASSERT(ptsParam);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001914
reed@android.com8a1c16f2008-12-17 15:59:43 +00001915 if (fVerbs == fVerbStop) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001916 // Close the curve if requested and if there is some curve to close
1917 if (fNeedClose && fSegmentState == kAfterPrimitive_SegmentState) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001918 if (kLine_Verb == this->autoClose(ptsParam)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001919 return kLine_Verb;
1920 }
1921 fNeedClose = false;
1922 return kClose_Verb;
1923 }
1924 return kDone_Verb;
1925 }
1926
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001927 // fVerbs is one beyond the current verb, decrement first
1928 unsigned verb = *(--fVerbs);
reed@google.com9e25dbf2012-05-15 17:05:38 +00001929 const SkPoint* SK_RESTRICT srcPts = fPts;
1930 SkPoint* SK_RESTRICT pts = ptsParam;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001931
1932 switch (verb) {
1933 case kMove_Verb:
1934 if (fNeedClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001935 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001936 verb = this->autoClose(pts);
1937 if (verb == kClose_Verb) {
1938 fNeedClose = false;
1939 }
1940 return (Verb)verb;
1941 }
1942 if (fVerbs == fVerbStop) { // might be a trailing moveto
1943 return kDone_Verb;
1944 }
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001945 fMoveTo = *srcPts;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001946 pts[0] = *srcPts;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001947 srcPts += 1;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001948 fSegmentState = kAfterMove_SegmentState;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001949 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001950 fNeedClose = fForceClose;
1951 break;
1952 case kLine_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001953 pts[0] = this->cons_moveTo();
1954 pts[1] = srcPts[0];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001955 fLastPt = srcPts[0];
1956 fCloseLine = false;
1957 srcPts += 1;
1958 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001959 case kConic_Verb:
1960 fConicWeights += 1;
1961 // fall-through
reed@android.com8a1c16f2008-12-17 15:59:43 +00001962 case kQuad_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001963 pts[0] = this->cons_moveTo();
1964 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001965 fLastPt = srcPts[1];
1966 srcPts += 2;
1967 break;
1968 case kCubic_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001969 pts[0] = this->cons_moveTo();
1970 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001971 fLastPt = srcPts[2];
1972 srcPts += 3;
1973 break;
1974 case kClose_Verb:
1975 verb = this->autoClose(pts);
1976 if (verb == kLine_Verb) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001977 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001978 } else {
1979 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001980 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001981 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001982 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001983 break;
1984 }
1985 fPts = srcPts;
1986 return (Verb)verb;
1987}
1988
1989///////////////////////////////////////////////////////////////////////////////
1990
reed@android.com8a1c16f2008-12-17 15:59:43 +00001991/*
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001992 Format in compressed buffer: [ptCount, verbCount, pts[], verbs[]]
reed@android.com8a1c16f2008-12-17 15:59:43 +00001993*/
1994
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00001995size_t SkPath::writeToMemory(void* storage) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001996 SkDEBUGCODE(this->validate();)
1997
halcanary96fcdcc2015-08-27 07:41:13 -07001998 if (nullptr == storage) {
caryclark69006412016-02-17 12:16:27 -08001999 const int byteCount = sizeof(int32_t) * 2 + fPathRef->writeSize();
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002000 return SkAlign4(byteCount);
2001 }
2002
2003 SkWBuffer buffer(storage);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002004
robertphillips@google.com466310d2013-12-03 16:43:54 +00002005 int32_t packed = (fConvexity << kConvexity_SerializationShift) |
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002006 (fFillType << kFillType_SerializationShift) |
reed026beb52015-06-10 14:23:15 -07002007 (fFirstDirection << kDirection_SerializationShift) |
reed8f086022015-06-11 14:22:19 -07002008 (fIsVolatile << kIsVolatile_SerializationShift) |
2009 kCurrent_Version;
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002010
robertphillips@google.com2972bb52012-08-07 17:32:51 +00002011 buffer.write32(packed);
caryclark69006412016-02-17 12:16:27 -08002012 buffer.write32(fLastMoveToIndex);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002013
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002014 fPathRef->writeToBuffer(&buffer);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002015
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002016 buffer.padToAlign4();
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002017 return buffer.pos();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002018}
2019
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002020size_t SkPath::readFromMemory(const void* storage, size_t length) {
2021 SkRBufferWithSizeCheck buffer(storage, length);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002022
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00002023 int32_t packed;
2024 if (!buffer.readS32(&packed)) {
2025 return 0;
2026 }
2027
reed8f086022015-06-11 14:22:19 -07002028 unsigned version = packed & 0xFF;
caryclark69006412016-02-17 12:16:27 -08002029 if (version >= kPathPrivLastMoveToIndex_Version && !buffer.readS32(&fLastMoveToIndex)) {
2030 return 0;
2031 }
mtklein1b249332015-07-07 12:21:21 -07002032
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002033 fConvexity = (packed >> kConvexity_SerializationShift) & 0xFF;
2034 fFillType = (packed >> kFillType_SerializationShift) & 0xFF;
reed8f086022015-06-11 14:22:19 -07002035 uint8_t dir = (packed >> kDirection_SerializationShift) & 0x3;
jvanverthb3eb6872014-10-24 07:12:51 -07002036 fIsVolatile = (packed >> kIsVolatile_SerializationShift) & 0x1;
commit-bot@chromium.orgfed2ab62014-01-23 15:16:05 +00002037 SkPathRef* pathRef = SkPathRef::CreateFromBuffer(&buffer);
ajumaf8aec582016-01-13 13:46:31 -08002038 if (!pathRef) {
2039 return 0;
2040 }
2041
2042 fPathRef.reset(pathRef);
2043 SkDEBUGCODE(this->validate();)
2044 buffer.skipToAlign4();
reed@google.comabf15c12011-01-18 20:35:51 +00002045
reed8f086022015-06-11 14:22:19 -07002046 // compatibility check
2047 if (version < kPathPrivFirstDirection_Version) {
2048 switch (dir) { // old values
2049 case 0:
2050 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
2051 break;
2052 case 1:
2053 fFirstDirection = SkPathPriv::kCW_FirstDirection;
2054 break;
2055 case 2:
2056 fFirstDirection = SkPathPriv::kCCW_FirstDirection;
2057 break;
2058 default:
2059 SkASSERT(false);
2060 }
2061 } else {
2062 fFirstDirection = dir;
2063 }
2064
ajumaf8aec582016-01-13 13:46:31 -08002065 return buffer.pos();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002066}
2067
2068///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00002069
reede05fed02014-12-15 07:59:53 -08002070#include "SkStringUtils.h"
caryclark66a5d8b2014-06-24 08:30:15 -07002071#include "SkStream.h"
reed@google.com51bbe752013-01-17 22:07:50 +00002072
reed@google.com51bbe752013-01-17 22:07:50 +00002073static void append_params(SkString* str, const char label[], const SkPoint pts[],
reede05fed02014-12-15 07:59:53 -08002074 int count, SkScalarAsStringType strType, SkScalar conicWeight = -1) {
reed@google.com51bbe752013-01-17 22:07:50 +00002075 str->append(label);
2076 str->append("(");
skia.committer@gmail.com15dd3002013-01-18 07:07:28 +00002077
reed@google.com51bbe752013-01-17 22:07:50 +00002078 const SkScalar* values = &pts[0].fX;
2079 count *= 2;
2080
2081 for (int i = 0; i < count; ++i) {
reede05fed02014-12-15 07:59:53 -08002082 SkAppendScalar(str, values[i], strType);
reed@google.com51bbe752013-01-17 22:07:50 +00002083 if (i < count - 1) {
2084 str->append(", ");
2085 }
2086 }
reed@google.com277c3f82013-05-31 15:17:50 +00002087 if (conicWeight >= 0) {
2088 str->append(", ");
reede05fed02014-12-15 07:59:53 -08002089 SkAppendScalar(str, conicWeight, strType);
reed@google.com277c3f82013-05-31 15:17:50 +00002090 }
caryclark08fa28c2014-10-23 13:08:56 -07002091 str->append(");");
reede05fed02014-12-15 07:59:53 -08002092 if (kHex_SkScalarAsStringType == strType) {
caryclark08fa28c2014-10-23 13:08:56 -07002093 str->append(" // ");
2094 for (int i = 0; i < count; ++i) {
reede05fed02014-12-15 07:59:53 -08002095 SkAppendScalarDec(str, values[i]);
caryclark08fa28c2014-10-23 13:08:56 -07002096 if (i < count - 1) {
2097 str->append(", ");
2098 }
2099 }
2100 if (conicWeight >= 0) {
2101 str->append(", ");
reede05fed02014-12-15 07:59:53 -08002102 SkAppendScalarDec(str, conicWeight);
caryclark08fa28c2014-10-23 13:08:56 -07002103 }
2104 }
2105 str->append("\n");
reed@google.com51bbe752013-01-17 22:07:50 +00002106}
2107
caryclarke9562592014-09-15 09:26:09 -07002108void SkPath::dump(SkWStream* wStream, bool forceClose, bool dumpAsHex) const {
reede05fed02014-12-15 07:59:53 -08002109 SkScalarAsStringType asType = dumpAsHex ? kHex_SkScalarAsStringType : kDec_SkScalarAsStringType;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002110 Iter iter(*this, forceClose);
2111 SkPoint pts[4];
2112 Verb verb;
2113
caryclark66a5d8b2014-06-24 08:30:15 -07002114 if (!wStream) {
2115 SkDebugf("path: forceClose=%s\n", forceClose ? "true" : "false");
2116 }
reed@google.com51bbe752013-01-17 22:07:50 +00002117 SkString builder;
2118
reed@google.com4a3b7142012-05-16 17:16:46 +00002119 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002120 switch (verb) {
2121 case kMove_Verb:
reede05fed02014-12-15 07:59:53 -08002122 append_params(&builder, "path.moveTo", &pts[0], 1, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002123 break;
2124 case kLine_Verb:
reede05fed02014-12-15 07:59:53 -08002125 append_params(&builder, "path.lineTo", &pts[1], 1, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002126 break;
2127 case kQuad_Verb:
reede05fed02014-12-15 07:59:53 -08002128 append_params(&builder, "path.quadTo", &pts[1], 2, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002129 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002130 case kConic_Verb:
reede05fed02014-12-15 07:59:53 -08002131 append_params(&builder, "path.conicTo", &pts[1], 2, asType, iter.conicWeight());
reed@google.com277c3f82013-05-31 15:17:50 +00002132 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002133 case kCubic_Verb:
reede05fed02014-12-15 07:59:53 -08002134 append_params(&builder, "path.cubicTo", &pts[1], 3, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002135 break;
2136 case kClose_Verb:
caryclark66a5d8b2014-06-24 08:30:15 -07002137 builder.append("path.close();\n");
reed@android.com8a1c16f2008-12-17 15:59:43 +00002138 break;
2139 default:
2140 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
2141 verb = kDone_Verb; // stop the loop
2142 break;
2143 }
caryclark1049f122015-04-20 08:31:59 -07002144 if (!wStream && builder.size()) {
2145 SkDebugf("%s", builder.c_str());
2146 builder.reset();
2147 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002148 }
caryclark66a5d8b2014-06-24 08:30:15 -07002149 if (wStream) {
2150 wStream->writeText(builder.c_str());
caryclark66a5d8b2014-06-24 08:30:15 -07002151 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002152}
2153
reed@android.come522ca52009-11-23 20:10:41 +00002154void SkPath::dump() const {
halcanary96fcdcc2015-08-27 07:41:13 -07002155 this->dump(nullptr, false, false);
caryclarke9562592014-09-15 09:26:09 -07002156}
2157
2158void SkPath::dumpHex() const {
halcanary96fcdcc2015-08-27 07:41:13 -07002159 this->dump(nullptr, false, true);
reed@android.come522ca52009-11-23 20:10:41 +00002160}
2161
2162#ifdef SK_DEBUG
2163void SkPath::validate() const {
reed@android.come522ca52009-11-23 20:10:41 +00002164 SkASSERT((fFillType & ~3) == 0);
reed@google.comabf15c12011-01-18 20:35:51 +00002165
djsollen@google.com077348c2012-10-22 20:23:32 +00002166#ifdef SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002167 if (!fBoundsIsDirty) {
2168 SkRect bounds;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002169
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002170 bool isFinite = compute_pt_bounds(&bounds, *fPathRef.get());
robertphillips@google.com5d8d1862012-08-15 14:36:41 +00002171 SkASSERT(SkToBool(fIsFinite) == isFinite);
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002172
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002173 if (fPathRef->countPoints() <= 1) {
reed@android.come522ca52009-11-23 20:10:41 +00002174 // if we're empty, fBounds may be empty but translated, so we can't
2175 // necessarily compare to bounds directly
2176 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
2177 // be [2, 2, 2, 2]
2178 SkASSERT(bounds.isEmpty());
2179 SkASSERT(fBounds.isEmpty());
2180 } else {
reed@google.comeac52bd2011-11-14 18:13:59 +00002181 if (bounds.isEmpty()) {
2182 SkASSERT(fBounds.isEmpty());
2183 } else {
reed@google.com3563c9e2011-11-14 19:34:57 +00002184 if (!fBounds.isEmpty()) {
2185 SkASSERT(fBounds.contains(bounds));
2186 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002187 }
reed@android.come522ca52009-11-23 20:10:41 +00002188 }
2189 }
djsollen@google.com077348c2012-10-22 20:23:32 +00002190#endif // SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002191}
djsollen@google.com077348c2012-10-22 20:23:32 +00002192#endif // SK_DEBUG
reed@android.come522ca52009-11-23 20:10:41 +00002193
reed@google.com04863fa2011-05-15 04:08:24 +00002194///////////////////////////////////////////////////////////////////////////////
2195
reed@google.com0b7b9822011-05-16 12:29:27 +00002196static int sign(SkScalar x) { return x < 0; }
2197#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00002198
robertphillipsc506e302014-09-16 09:43:31 -07002199enum DirChange {
2200 kLeft_DirChange,
2201 kRight_DirChange,
2202 kStraight_DirChange,
2203 kBackwards_DirChange,
2204
2205 kInvalid_DirChange
2206};
2207
2208
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002209static bool almost_equal(SkScalar compA, SkScalar compB) {
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002210 // The error epsilon was empirically derived; worse case round rects
2211 // with a mid point outset by 2x float epsilon in tests had an error
2212 // of 12.
2213 const int epsilon = 16;
2214 if (!SkScalarIsFinite(compA) || !SkScalarIsFinite(compB)) {
2215 return false;
2216 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002217 // no need to check for small numbers because SkPath::Iter has removed degenerate values
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002218 int aBits = SkFloatAs2sCompliment(compA);
2219 int bBits = SkFloatAs2sCompliment(compB);
2220 return aBits < bBits + epsilon && bBits < aBits + epsilon;
reed@google.com04863fa2011-05-15 04:08:24 +00002221}
2222
caryclarkb4216502015-03-02 13:02:34 -08002223static bool approximately_zero_when_compared_to(double x, double y) {
2224 return x == 0 || fabs(x) < fabs(y * FLT_EPSILON);
robertphillipsc506e302014-09-16 09:43:31 -07002225}
2226
caryclarkb4216502015-03-02 13:02:34 -08002227
reed@google.com04863fa2011-05-15 04:08:24 +00002228// only valid for a single contour
2229struct Convexicator {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002230 Convexicator()
2231 : fPtCount(0)
2232 , fConvexity(SkPath::kConvex_Convexity)
reed026beb52015-06-10 14:23:15 -07002233 , fFirstDirection(SkPathPriv::kUnknown_FirstDirection)
caryclark5ccef572015-03-02 10:07:56 -08002234 , fIsFinite(true)
2235 , fIsCurve(false) {
robertphillipsc506e302014-09-16 09:43:31 -07002236 fExpectedDir = kInvalid_DirChange;
reed@google.com04863fa2011-05-15 04:08:24 +00002237 // warnings
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002238 fLastPt.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002239 fCurrPt.set(0, 0);
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002240 fLastVec.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002241 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00002242
2243 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00002244 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00002245 }
2246
2247 SkPath::Convexity getConvexity() const { return fConvexity; }
2248
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002249 /** The direction returned is only valid if the path is determined convex */
reed026beb52015-06-10 14:23:15 -07002250 SkPathPriv::FirstDirection getFirstDirection() const { return fFirstDirection; }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002251
reed@google.com04863fa2011-05-15 04:08:24 +00002252 void addPt(const SkPoint& pt) {
caryclarkd3d1a982014-12-08 04:57:38 -08002253 if (SkPath::kConcave_Convexity == fConvexity || !fIsFinite) {
reed@google.com04863fa2011-05-15 04:08:24 +00002254 return;
2255 }
2256
2257 if (0 == fPtCount) {
2258 fCurrPt = pt;
2259 ++fPtCount;
2260 } else {
2261 SkVector vec = pt - fCurrPt;
caryclarkd3d1a982014-12-08 04:57:38 -08002262 SkScalar lengthSqd = vec.lengthSqd();
2263 if (!SkScalarIsFinite(lengthSqd)) {
2264 fIsFinite = false;
caryclarke8c56662015-07-14 11:19:26 -07002265 } else if (lengthSqd) {
caryclarkb4216502015-03-02 13:02:34 -08002266 fPriorPt = fLastPt;
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002267 fLastPt = fCurrPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002268 fCurrPt = pt;
2269 if (++fPtCount == 2) {
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002270 fFirstVec = fLastVec = vec;
reed@google.com04863fa2011-05-15 04:08:24 +00002271 } else {
2272 SkASSERT(fPtCount > 2);
2273 this->addVec(vec);
2274 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002275
reed@google.com85b6e392011-05-15 20:25:17 +00002276 int sx = sign(vec.fX);
2277 int sy = sign(vec.fY);
2278 fDx += (sx != fSx);
2279 fDy += (sy != fSy);
2280 fSx = sx;
2281 fSy = sy;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002282
reed@google.com85b6e392011-05-15 20:25:17 +00002283 if (fDx > 3 || fDy > 3) {
2284 fConvexity = SkPath::kConcave_Convexity;
2285 }
reed@google.com04863fa2011-05-15 04:08:24 +00002286 }
2287 }
2288 }
2289
2290 void close() {
2291 if (fPtCount > 2) {
2292 this->addVec(fFirstVec);
2293 }
2294 }
2295
caryclarkb4216502015-03-02 13:02:34 -08002296 DirChange directionChange(const SkVector& curVec) {
2297 SkScalar cross = SkPoint::CrossProduct(fLastVec, curVec);
2298
2299 SkScalar smallest = SkTMin(fCurrPt.fX, SkTMin(fCurrPt.fY, SkTMin(fLastPt.fX, fLastPt.fY)));
2300 SkScalar largest = SkTMax(fCurrPt.fX, SkTMax(fCurrPt.fY, SkTMax(fLastPt.fX, fLastPt.fY)));
2301 largest = SkTMax(largest, -smallest);
2302
2303 if (!almost_equal(largest, largest + cross)) {
2304 int sign = SkScalarSignAsInt(cross);
2305 if (sign) {
2306 return (1 == sign) ? kRight_DirChange : kLeft_DirChange;
2307 }
2308 }
2309
2310 if (cross) {
2311 double dLastVecX = SkScalarToDouble(fLastPt.fX) - SkScalarToDouble(fPriorPt.fX);
2312 double dLastVecY = SkScalarToDouble(fLastPt.fY) - SkScalarToDouble(fPriorPt.fY);
2313 double dCurrVecX = SkScalarToDouble(fCurrPt.fX) - SkScalarToDouble(fLastPt.fX);
2314 double dCurrVecY = SkScalarToDouble(fCurrPt.fY) - SkScalarToDouble(fLastPt.fY);
2315 double dCross = dLastVecX * dCurrVecY - dLastVecY * dCurrVecX;
2316 if (!approximately_zero_when_compared_to(dCross, SkScalarToDouble(largest))) {
2317 int sign = SkScalarSignAsInt(SkDoubleToScalar(dCross));
2318 if (sign) {
2319 return (1 == sign) ? kRight_DirChange : kLeft_DirChange;
2320 }
2321 }
2322 }
2323
2324 if (!SkScalarNearlyZero(fLastVec.lengthSqd(), SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
2325 !SkScalarNearlyZero(curVec.lengthSqd(), SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
2326 fLastVec.dot(curVec) < 0.0f) {
2327 return kBackwards_DirChange;
2328 }
2329
2330 return kStraight_DirChange;
2331 }
2332
2333
caryclarkd3d1a982014-12-08 04:57:38 -08002334 bool isFinite() const {
2335 return fIsFinite;
2336 }
2337
caryclark5ccef572015-03-02 10:07:56 -08002338 void setCurve(bool isCurve) {
2339 fIsCurve = isCurve;
2340 }
2341
reed@google.com04863fa2011-05-15 04:08:24 +00002342private:
2343 void addVec(const SkVector& vec) {
2344 SkASSERT(vec.fX || vec.fY);
caryclarkb4216502015-03-02 13:02:34 -08002345 DirChange dir = this->directionChange(vec);
robertphillipsc506e302014-09-16 09:43:31 -07002346 switch (dir) {
2347 case kLeft_DirChange: // fall through
2348 case kRight_DirChange:
2349 if (kInvalid_DirChange == fExpectedDir) {
2350 fExpectedDir = dir;
reed026beb52015-06-10 14:23:15 -07002351 fFirstDirection = (kRight_DirChange == dir) ? SkPathPriv::kCW_FirstDirection
2352 : SkPathPriv::kCCW_FirstDirection;
robertphillipsc506e302014-09-16 09:43:31 -07002353 } else if (dir != fExpectedDir) {
2354 fConvexity = SkPath::kConcave_Convexity;
reed026beb52015-06-10 14:23:15 -07002355 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
robertphillipsc506e302014-09-16 09:43:31 -07002356 }
2357 fLastVec = vec;
2358 break;
2359 case kStraight_DirChange:
2360 break;
2361 case kBackwards_DirChange:
caryclark5ccef572015-03-02 10:07:56 -08002362 if (fIsCurve) {
2363 fConvexity = SkPath::kConcave_Convexity;
reed026beb52015-06-10 14:23:15 -07002364 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
caryclark5ccef572015-03-02 10:07:56 -08002365 }
robertphillipsc506e302014-09-16 09:43:31 -07002366 fLastVec = vec;
2367 break;
2368 case kInvalid_DirChange:
2369 SkFAIL("Use of invalid direction change flag");
2370 break;
reed@google.com04863fa2011-05-15 04:08:24 +00002371 }
2372 }
2373
caryclarkb4216502015-03-02 13:02:34 -08002374 SkPoint fPriorPt;
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002375 SkPoint fLastPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002376 SkPoint fCurrPt;
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002377 // fLastVec does not necessarily start at fLastPt. We only advance it when the cross product
2378 // value with the current vec is deemed to be of a significant value.
2379 SkVector fLastVec, fFirstVec;
reed@google.com04863fa2011-05-15 04:08:24 +00002380 int fPtCount; // non-degenerate points
robertphillipsc506e302014-09-16 09:43:31 -07002381 DirChange fExpectedDir;
reed@google.com04863fa2011-05-15 04:08:24 +00002382 SkPath::Convexity fConvexity;
reed026beb52015-06-10 14:23:15 -07002383 SkPathPriv::FirstDirection fFirstDirection;
reed@google.com0b7b9822011-05-16 12:29:27 +00002384 int fDx, fDy, fSx, fSy;
caryclarkd3d1a982014-12-08 04:57:38 -08002385 bool fIsFinite;
caryclark5ccef572015-03-02 10:07:56 -08002386 bool fIsCurve;
reed@google.com04863fa2011-05-15 04:08:24 +00002387};
2388
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002389SkPath::Convexity SkPath::internalGetConvexity() const {
2390 SkASSERT(kUnknown_Convexity == fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002391 SkPoint pts[4];
2392 SkPath::Verb verb;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002393 SkPath::Iter iter(*this, true);
reed@google.com04863fa2011-05-15 04:08:24 +00002394
2395 int contourCount = 0;
2396 int count;
2397 Convexicator state;
2398
caryclarkd3d1a982014-12-08 04:57:38 -08002399 if (!isFinite()) {
2400 return kUnknown_Convexity;
2401 }
caryclarke8c56662015-07-14 11:19:26 -07002402 while ((verb = iter.next(pts, true, true)) != SkPath::kDone_Verb) {
reed@google.com04863fa2011-05-15 04:08:24 +00002403 switch (verb) {
2404 case kMove_Verb:
2405 if (++contourCount > 1) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002406 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002407 return kConcave_Convexity;
2408 }
2409 pts[1] = pts[0];
caryclark5ccef572015-03-02 10:07:56 -08002410 // fall through
2411 case kLine_Verb:
reed@google.com04863fa2011-05-15 04:08:24 +00002412 count = 1;
caryclark5ccef572015-03-02 10:07:56 -08002413 state.setCurve(false);
reed@google.com04863fa2011-05-15 04:08:24 +00002414 break;
caryclark5ccef572015-03-02 10:07:56 -08002415 case kQuad_Verb:
2416 // fall through
2417 case kConic_Verb:
2418 // fall through
2419 case kCubic_Verb:
2420 count = 2 + (kCubic_Verb == verb);
2421 // As an additional enhancement, this could set curve true only
2422 // if the curve is nonlinear
2423 state.setCurve(true);
2424 break;
reed@google.com04863fa2011-05-15 04:08:24 +00002425 case kClose_Verb:
caryclark5ccef572015-03-02 10:07:56 -08002426 state.setCurve(false);
reed@google.com04863fa2011-05-15 04:08:24 +00002427 state.close();
2428 count = 0;
2429 break;
2430 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00002431 SkDEBUGFAIL("bad verb");
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002432 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002433 return kConcave_Convexity;
2434 }
2435
2436 for (int i = 1; i <= count; i++) {
2437 state.addPt(pts[i]);
2438 }
2439 // early exit
caryclarkd3d1a982014-12-08 04:57:38 -08002440 if (!state.isFinite()) {
2441 return kUnknown_Convexity;
2442 }
reed@google.com04863fa2011-05-15 04:08:24 +00002443 if (kConcave_Convexity == state.getConvexity()) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002444 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002445 return kConcave_Convexity;
2446 }
2447 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002448 fConvexity = state.getConvexity();
reed026beb52015-06-10 14:23:15 -07002449 if (kConvex_Convexity == fConvexity && SkPathPriv::kUnknown_FirstDirection == fFirstDirection) {
2450 fFirstDirection = state.getFirstDirection();
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002451 }
2452 return static_cast<Convexity>(fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002453}
reed@google.com69a99432012-01-10 18:00:10 +00002454
2455///////////////////////////////////////////////////////////////////////////////
2456
2457class ContourIter {
2458public:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002459 ContourIter(const SkPathRef& pathRef);
reed@google.com69a99432012-01-10 18:00:10 +00002460
2461 bool done() const { return fDone; }
2462 // if !done() then these may be called
2463 int count() const { return fCurrPtCount; }
2464 const SkPoint* pts() const { return fCurrPt; }
2465 void next();
2466
2467private:
2468 int fCurrPtCount;
2469 const SkPoint* fCurrPt;
2470 const uint8_t* fCurrVerb;
2471 const uint8_t* fStopVerbs;
reed@google.com277c3f82013-05-31 15:17:50 +00002472 const SkScalar* fCurrConicWeight;
reed@google.com69a99432012-01-10 18:00:10 +00002473 bool fDone;
reed@google.comd1ab9322012-01-10 18:40:03 +00002474 SkDEBUGCODE(int fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002475};
2476
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002477ContourIter::ContourIter(const SkPathRef& pathRef) {
2478 fStopVerbs = pathRef.verbsMemBegin();
reed@google.com69a99432012-01-10 18:00:10 +00002479 fDone = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002480 fCurrPt = pathRef.points();
2481 fCurrVerb = pathRef.verbs();
reed@google.com277c3f82013-05-31 15:17:50 +00002482 fCurrConicWeight = pathRef.conicWeights();
reed@google.com69a99432012-01-10 18:00:10 +00002483 fCurrPtCount = 0;
reed@google.comd1ab9322012-01-10 18:40:03 +00002484 SkDEBUGCODE(fContourCounter = 0;)
reed@google.com69a99432012-01-10 18:00:10 +00002485 this->next();
2486}
2487
2488void ContourIter::next() {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002489 if (fCurrVerb <= fStopVerbs) {
reed@google.com69a99432012-01-10 18:00:10 +00002490 fDone = true;
2491 }
2492 if (fDone) {
2493 return;
2494 }
2495
2496 // skip pts of prev contour
2497 fCurrPt += fCurrPtCount;
2498
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002499 SkASSERT(SkPath::kMove_Verb == fCurrVerb[~0]);
reed@google.com69a99432012-01-10 18:00:10 +00002500 int ptCount = 1; // moveTo
2501 const uint8_t* verbs = fCurrVerb;
2502
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002503 for (--verbs; verbs > fStopVerbs; --verbs) {
2504 switch (verbs[~0]) {
reed@google.com69a99432012-01-10 18:00:10 +00002505 case SkPath::kMove_Verb:
reed@google.com69a99432012-01-10 18:00:10 +00002506 goto CONTOUR_END;
2507 case SkPath::kLine_Verb:
2508 ptCount += 1;
2509 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002510 case SkPath::kConic_Verb:
2511 fCurrConicWeight += 1;
2512 // fall-through
reed@google.com69a99432012-01-10 18:00:10 +00002513 case SkPath::kQuad_Verb:
2514 ptCount += 2;
2515 break;
2516 case SkPath::kCubic_Verb:
2517 ptCount += 3;
2518 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002519 case SkPath::kClose_Verb:
2520 break;
2521 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00002522 SkDEBUGFAIL("unexpected verb");
reed@google.com69a99432012-01-10 18:00:10 +00002523 break;
2524 }
2525 }
2526CONTOUR_END:
2527 fCurrPtCount = ptCount;
2528 fCurrVerb = verbs;
reed@google.comd1ab9322012-01-10 18:40:03 +00002529 SkDEBUGCODE(++fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002530}
2531
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002532// returns cross product of (p1 - p0) and (p2 - p0)
reed@google.com69a99432012-01-10 18:00:10 +00002533static SkScalar cross_prod(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002534 SkScalar cross = SkPoint::CrossProduct(p1 - p0, p2 - p0);
2535 // We may get 0 when the above subtracts underflow. We expect this to be
2536 // very rare and lazily promote to double.
2537 if (0 == cross) {
2538 double p0x = SkScalarToDouble(p0.fX);
2539 double p0y = SkScalarToDouble(p0.fY);
2540
2541 double p1x = SkScalarToDouble(p1.fX);
2542 double p1y = SkScalarToDouble(p1.fY);
2543
2544 double p2x = SkScalarToDouble(p2.fX);
2545 double p2y = SkScalarToDouble(p2.fY);
2546
2547 cross = SkDoubleToScalar((p1x - p0x) * (p2y - p0y) -
2548 (p1y - p0y) * (p2x - p0x));
2549
2550 }
2551 return cross;
reed@google.com69a99432012-01-10 18:00:10 +00002552}
2553
reed@google.comc1ea60a2012-01-31 15:15:36 +00002554// Returns the first pt with the maximum Y coordinate
reed@google.com69a99432012-01-10 18:00:10 +00002555static int find_max_y(const SkPoint pts[], int count) {
2556 SkASSERT(count > 0);
2557 SkScalar max = pts[0].fY;
reed@google.comc1ea60a2012-01-31 15:15:36 +00002558 int firstIndex = 0;
reed@google.com69a99432012-01-10 18:00:10 +00002559 for (int i = 1; i < count; ++i) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002560 SkScalar y = pts[i].fY;
2561 if (y > max) {
2562 max = y;
2563 firstIndex = i;
reed@google.com69a99432012-01-10 18:00:10 +00002564 }
2565 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002566 return firstIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002567}
2568
reed@google.comcabaf1d2012-01-11 21:03:05 +00002569static int find_diff_pt(const SkPoint pts[], int index, int n, int inc) {
2570 int i = index;
2571 for (;;) {
2572 i = (i + inc) % n;
2573 if (i == index) { // we wrapped around, so abort
2574 break;
2575 }
2576 if (pts[index] != pts[i]) { // found a different point, success!
2577 break;
2578 }
2579 }
2580 return i;
2581}
2582
reed@google.comc1ea60a2012-01-31 15:15:36 +00002583/**
2584 * Starting at index, and moving forward (incrementing), find the xmin and
2585 * xmax of the contiguous points that have the same Y.
2586 */
2587static int find_min_max_x_at_y(const SkPoint pts[], int index, int n,
2588 int* maxIndexPtr) {
2589 const SkScalar y = pts[index].fY;
2590 SkScalar min = pts[index].fX;
2591 SkScalar max = min;
2592 int minIndex = index;
2593 int maxIndex = index;
2594 for (int i = index + 1; i < n; ++i) {
2595 if (pts[i].fY != y) {
2596 break;
2597 }
2598 SkScalar x = pts[i].fX;
2599 if (x < min) {
2600 min = x;
2601 minIndex = i;
2602 } else if (x > max) {
2603 max = x;
2604 maxIndex = i;
2605 }
2606 }
2607 *maxIndexPtr = maxIndex;
2608 return minIndex;
2609}
2610
reed026beb52015-06-10 14:23:15 -07002611static void crossToDir(SkScalar cross, SkPathPriv::FirstDirection* dir) {
2612 *dir = cross > 0 ? SkPathPriv::kCW_FirstDirection : SkPathPriv::kCCW_FirstDirection;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002613}
2614
reed@google.comac8543f2012-01-30 20:51:25 +00002615/*
2616 * We loop through all contours, and keep the computed cross-product of the
2617 * contour that contained the global y-max. If we just look at the first
2618 * contour, we may find one that is wound the opposite way (correctly) since
2619 * it is the interior of a hole (e.g. 'o'). Thus we must find the contour
2620 * that is outer most (or at least has the global y-max) before we can consider
2621 * its cross product.
2622 */
reed026beb52015-06-10 14:23:15 -07002623bool SkPathPriv::CheapComputeFirstDirection(const SkPath& path, FirstDirection* dir) {
herb9f4dbca2015-09-28 11:05:47 -07002624 if (kUnknown_FirstDirection != path.fFirstDirection.load()) {
2625 *dir = static_cast<FirstDirection>(path.fFirstDirection.load());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002626 return true;
2627 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002628
2629 // don't want to pay the cost for computing this if it
2630 // is unknown, so we don't call isConvex()
reed026beb52015-06-10 14:23:15 -07002631 if (SkPath::kConvex_Convexity == path.getConvexityOrUnknown()) {
2632 SkASSERT(kUnknown_FirstDirection == path.fFirstDirection);
herb9f4dbca2015-09-28 11:05:47 -07002633 *dir = static_cast<FirstDirection>(path.fFirstDirection.load());
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002634 return false;
2635 }
reed@google.com69a99432012-01-10 18:00:10 +00002636
reed026beb52015-06-10 14:23:15 -07002637 ContourIter iter(*path.fPathRef.get());
reed@google.com69a99432012-01-10 18:00:10 +00002638
reed@google.comac8543f2012-01-30 20:51:25 +00002639 // initialize with our logical y-min
reed026beb52015-06-10 14:23:15 -07002640 SkScalar ymax = path.getBounds().fTop;
reed@google.comac8543f2012-01-30 20:51:25 +00002641 SkScalar ymaxCross = 0;
2642
reed@google.com69a99432012-01-10 18:00:10 +00002643 for (; !iter.done(); iter.next()) {
2644 int n = iter.count();
reed@google.comcabaf1d2012-01-11 21:03:05 +00002645 if (n < 3) {
2646 continue;
2647 }
djsollen@google.come63793a2012-03-21 15:39:03 +00002648
reed@google.comcabaf1d2012-01-11 21:03:05 +00002649 const SkPoint* pts = iter.pts();
reed@google.com69a99432012-01-10 18:00:10 +00002650 SkScalar cross = 0;
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002651 int index = find_max_y(pts, n);
2652 if (pts[index].fY < ymax) {
2653 continue;
2654 }
2655
2656 // If there is more than 1 distinct point at the y-max, we take the
2657 // x-min and x-max of them and just subtract to compute the dir.
2658 if (pts[(index + 1) % n].fY == pts[index].fY) {
2659 int maxIndex;
2660 int minIndex = find_min_max_x_at_y(pts, index, n, &maxIndex);
2661 if (minIndex == maxIndex) {
2662 goto TRY_CROSSPROD;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002663 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002664 SkASSERT(pts[minIndex].fY == pts[index].fY);
2665 SkASSERT(pts[maxIndex].fY == pts[index].fY);
2666 SkASSERT(pts[minIndex].fX <= pts[maxIndex].fX);
2667 // we just subtract the indices, and let that auto-convert to
2668 // SkScalar, since we just want - or + to signal the direction.
2669 cross = minIndex - maxIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002670 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002671 TRY_CROSSPROD:
2672 // Find a next and prev index to use for the cross-product test,
2673 // but we try to find pts that form non-zero vectors from pts[index]
2674 //
2675 // Its possible that we can't find two non-degenerate vectors, so
2676 // we have to guard our search (e.g. all the pts could be in the
2677 // same place).
2678
2679 // we pass n - 1 instead of -1 so we don't foul up % operator by
2680 // passing it a negative LH argument.
2681 int prev = find_diff_pt(pts, index, n, n - 1);
2682 if (prev == index) {
2683 // completely degenerate, skip to next contour
reed@google.comac8543f2012-01-30 20:51:25 +00002684 continue;
2685 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002686 int next = find_diff_pt(pts, index, n, 1);
2687 SkASSERT(next != index);
2688 cross = cross_prod(pts[prev], pts[index], pts[next]);
2689 // if we get a zero and the points are horizontal, then we look at the spread in
2690 // x-direction. We really should continue to walk away from the degeneracy until
2691 // there is a divergence.
2692 if (0 == cross && pts[prev].fY == pts[index].fY && pts[next].fY == pts[index].fY) {
2693 // construct the subtract so we get the correct Direction below
2694 cross = pts[index].fX - pts[next].fX;
reed@google.com188bfcf2012-01-17 18:26:38 +00002695 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002696 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002697
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002698 if (cross) {
2699 // record our best guess so far
2700 ymax = pts[index].fY;
2701 ymaxCross = cross;
reed@google.com69a99432012-01-10 18:00:10 +00002702 }
2703 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002704 if (ymaxCross) {
2705 crossToDir(ymaxCross, dir);
reed026beb52015-06-10 14:23:15 -07002706 path.fFirstDirection = *dir;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002707 return true;
2708 } else {
2709 return false;
2710 }
reed@google.comac8543f2012-01-30 20:51:25 +00002711}
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002712
2713///////////////////////////////////////////////////////////////////////////////
2714
caryclark9aacd902015-12-14 08:38:09 -08002715static bool between(SkScalar a, SkScalar b, SkScalar c) {
2716 SkASSERT(((a <= b && b <= c) || (a >= b && b >= c)) == ((a - b) * (c - b) <= 0)
2717 || (SkScalarNearlyZero(a) && SkScalarNearlyZero(b) && SkScalarNearlyZero(c)));
2718 return (a - b) * (c - b) <= 0;
2719}
2720
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002721static SkScalar eval_cubic_coeff(SkScalar A, SkScalar B, SkScalar C,
2722 SkScalar D, SkScalar t) {
2723 return SkScalarMulAdd(SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C), t, D);
2724}
2725
2726static SkScalar eval_cubic_pts(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2727 SkScalar t) {
2728 SkScalar A = c3 + 3*(c1 - c2) - c0;
2729 SkScalar B = 3*(c2 - c1 - c1 + c0);
2730 SkScalar C = 3*(c1 - c0);
2731 SkScalar D = c0;
2732 return eval_cubic_coeff(A, B, C, D, t);
2733}
2734
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002735template <size_t N> static void find_minmax(const SkPoint pts[],
2736 SkScalar* minPtr, SkScalar* maxPtr) {
2737 SkScalar min, max;
2738 min = max = pts[0].fX;
2739 for (size_t i = 1; i < N; ++i) {
2740 min = SkMinScalar(min, pts[i].fX);
2741 max = SkMaxScalar(max, pts[i].fX);
2742 }
2743 *minPtr = min;
2744 *maxPtr = max;
2745}
2746
caryclark9cb5d752015-12-18 04:35:24 -08002747static bool checkOnCurve(SkScalar x, SkScalar y, const SkPoint& start, const SkPoint& end) {
2748 if (start.fY == end.fY) {
2749 return between(start.fX, x, end.fX) && x != end.fX;
2750 } else {
2751 return x == start.fX && y == start.fY;
2752 }
2753}
2754
caryclark9aacd902015-12-14 08:38:09 -08002755static int winding_mono_cubic(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
caryclark9cb5d752015-12-18 04:35:24 -08002756 SkScalar y0 = pts[0].fY;
2757 SkScalar y3 = pts[3].fY;
2758
2759 int dir = 1;
2760 if (y0 > y3) {
2761 SkTSwap(y0, y3);
2762 dir = -1;
2763 }
2764 if (y < y0 || y > y3) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002765 return 0;
2766 }
caryclark9cb5d752015-12-18 04:35:24 -08002767 if (checkOnCurve(x, y, pts[0], pts[3])) {
2768 *onCurveCount += 1;
caryclark9aacd902015-12-14 08:38:09 -08002769 return 0;
2770 }
caryclark9cb5d752015-12-18 04:35:24 -08002771 if (y == y3) {
2772 return 0;
2773 }
2774
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002775 // quickreject or quickaccept
2776 SkScalar min, max;
2777 find_minmax<4>(pts, &min, &max);
2778 if (x < min) {
2779 return 0;
2780 }
2781 if (x > max) {
2782 return dir;
2783 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002784
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002785 // compute the actual x(t) value
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002786 SkScalar t;
caryclark9aacd902015-12-14 08:38:09 -08002787 SkAssertResult(SkCubicClipper::ChopMonoAtY(pts, y, &t));
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002788 SkScalar xt = eval_cubic_pts(pts[0].fX, pts[1].fX, pts[2].fX, pts[3].fX, t);
caryclark9aacd902015-12-14 08:38:09 -08002789 if (SkScalarNearlyEqual(xt, x)) {
2790 if (x != pts[3].fX || y != pts[3].fY) { // don't test end points; they're start points
2791 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08002792 return 0;
caryclark9aacd902015-12-14 08:38:09 -08002793 }
2794 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002795 return xt < x ? dir : 0;
2796}
2797
caryclark9aacd902015-12-14 08:38:09 -08002798static int winding_cubic(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002799 SkPoint dst[10];
2800 int n = SkChopCubicAtYExtrema(pts, dst);
2801 int w = 0;
2802 for (int i = 0; i <= n; ++i) {
caryclark9aacd902015-12-14 08:38:09 -08002803 w += winding_mono_cubic(&dst[i * 3], x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002804 }
2805 return w;
2806}
2807
caryclark9aacd902015-12-14 08:38:09 -08002808static double conic_eval_numerator(const SkScalar src[], SkScalar w, SkScalar t) {
2809 SkASSERT(src);
2810 SkASSERT(t >= 0 && t <= 1);
2811 SkScalar src2w = src[2] * w;
2812 SkScalar C = src[0];
2813 SkScalar A = src[4] - 2 * src2w + C;
2814 SkScalar B = 2 * (src2w - C);
2815 return (A * t + B) * t + C;
2816}
2817
2818
2819static double conic_eval_denominator(SkScalar w, SkScalar t) {
2820 SkScalar B = 2 * (w - 1);
2821 SkScalar C = 1;
2822 SkScalar A = -B;
2823 return (A * t + B) * t + C;
2824}
2825
2826static int winding_mono_conic(const SkConic& conic, SkScalar x, SkScalar y, int* onCurveCount) {
2827 const SkPoint* pts = conic.fPts;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002828 SkScalar y0 = pts[0].fY;
2829 SkScalar y2 = pts[2].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002830
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002831 int dir = 1;
2832 if (y0 > y2) {
2833 SkTSwap(y0, y2);
2834 dir = -1;
2835 }
caryclark9aacd902015-12-14 08:38:09 -08002836 if (y < y0 || y > y2) {
2837 return 0;
2838 }
caryclark9cb5d752015-12-18 04:35:24 -08002839 if (checkOnCurve(x, y, pts[0], pts[2])) {
2840 *onCurveCount += 1;
2841 return 0;
2842 }
caryclark9aacd902015-12-14 08:38:09 -08002843 if (y == y2) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002844 return 0;
2845 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002846
caryclark9aacd902015-12-14 08:38:09 -08002847 SkScalar roots[2];
2848 SkScalar A = pts[2].fY;
2849 SkScalar B = pts[1].fY * conic.fW - y * conic.fW + y;
2850 SkScalar C = pts[0].fY;
2851 A += C - 2 * B; // A = a + c - 2*(b*w - yCept*w + yCept)
2852 B -= C; // B = b*w - w * yCept + yCept - a
2853 C -= y;
2854 int n = SkFindUnitQuadRoots(A, 2 * B, C, roots);
2855 SkASSERT(n <= 1);
2856 SkScalar xt;
2857 if (0 == n) {
caryclark9cb5d752015-12-18 04:35:24 -08002858 // zero roots are returned only when y0 == y
2859 // Need [0] if dir == 1
2860 // and [2] if dir == -1
2861 xt = pts[1 - dir].fX;
caryclark9aacd902015-12-14 08:38:09 -08002862 } else {
2863 SkScalar t = roots[0];
2864 xt = conic_eval_numerator(&pts[0].fX, conic.fW, t) / conic_eval_denominator(conic.fW, t);
2865 }
2866 if (SkScalarNearlyEqual(xt, x)) {
2867 if (x != pts[2].fX || y != pts[2].fY) { // don't test end points; they're start points
2868 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08002869 return 0;
caryclark9aacd902015-12-14 08:38:09 -08002870 }
2871 }
2872 return xt < x ? dir : 0;
2873}
2874
2875static bool is_mono_quad(SkScalar y0, SkScalar y1, SkScalar y2) {
2876 // return SkScalarSignAsInt(y0 - y1) + SkScalarSignAsInt(y1 - y2) != 0;
2877 if (y0 == y1) {
2878 return true;
2879 }
2880 if (y0 < y1) {
2881 return y1 <= y2;
2882 } else {
2883 return y1 >= y2;
2884 }
2885}
2886
2887static int winding_conic(const SkPoint pts[], SkScalar x, SkScalar y, SkScalar weight,
2888 int* onCurveCount) {
2889 SkConic conic(pts, weight);
caryclark9aacd902015-12-14 08:38:09 -08002890 SkConic chopped[2];
caryclark1e1e5092016-01-06 08:27:44 -08002891 // If the data points are very large, the conic may not be monotonic but may also
2892 // fail to chop. Then, the chopper does not split the original conic in two.
caryclarke114dd62016-01-06 08:15:44 -08002893 bool isMono = is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY) || !conic.chopAtYExtrema(chopped);
2894 int w = winding_mono_conic(isMono ? conic : chopped[0], x, y, onCurveCount);
2895 if (!isMono) {
caryclark9aacd902015-12-14 08:38:09 -08002896 w += winding_mono_conic(chopped[1], x, y, onCurveCount);
2897 }
2898 return w;
2899}
2900
2901static int winding_mono_quad(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
2902 SkScalar y0 = pts[0].fY;
2903 SkScalar y2 = pts[2].fY;
2904
2905 int dir = 1;
2906 if (y0 > y2) {
2907 SkTSwap(y0, y2);
2908 dir = -1;
2909 }
2910 if (y < y0 || y > y2) {
2911 return 0;
2912 }
caryclark9cb5d752015-12-18 04:35:24 -08002913 if (checkOnCurve(x, y, pts[0], pts[2])) {
2914 *onCurveCount += 1;
2915 return 0;
2916 }
caryclark9aacd902015-12-14 08:38:09 -08002917 if (y == y2) {
caryclark9aacd902015-12-14 08:38:09 -08002918 return 0;
2919 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002920 // bounds check on X (not required. is it faster?)
2921#if 0
2922 if (pts[0].fX > x && pts[1].fX > x && pts[2].fX > x) {
2923 return 0;
2924 }
2925#endif
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002926
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002927 SkScalar roots[2];
2928 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
2929 2 * (pts[1].fY - pts[0].fY),
2930 pts[0].fY - y,
2931 roots);
2932 SkASSERT(n <= 1);
2933 SkScalar xt;
2934 if (0 == n) {
caryclark9cb5d752015-12-18 04:35:24 -08002935 // zero roots are returned only when y0 == y
2936 // Need [0] if dir == 1
2937 // and [2] if dir == -1
2938 xt = pts[1 - dir].fX;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002939 } else {
2940 SkScalar t = roots[0];
2941 SkScalar C = pts[0].fX;
2942 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
2943 SkScalar B = 2 * (pts[1].fX - C);
2944 xt = SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C);
2945 }
caryclark9aacd902015-12-14 08:38:09 -08002946 if (SkScalarNearlyEqual(xt, x)) {
2947 if (x != pts[2].fX || y != pts[2].fY) { // don't test end points; they're start points
2948 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08002949 return 0;
caryclark9aacd902015-12-14 08:38:09 -08002950 }
2951 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002952 return xt < x ? dir : 0;
2953}
2954
caryclark9aacd902015-12-14 08:38:09 -08002955static int winding_quad(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002956 SkPoint dst[5];
2957 int n = 0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002958
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002959 if (!is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY)) {
2960 n = SkChopQuadAtYExtrema(pts, dst);
2961 pts = dst;
2962 }
caryclark9aacd902015-12-14 08:38:09 -08002963 int w = winding_mono_quad(pts, x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002964 if (n > 0) {
caryclark9aacd902015-12-14 08:38:09 -08002965 w += winding_mono_quad(&pts[2], x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002966 }
2967 return w;
2968}
2969
caryclark9aacd902015-12-14 08:38:09 -08002970static int winding_line(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002971 SkScalar x0 = pts[0].fX;
2972 SkScalar y0 = pts[0].fY;
2973 SkScalar x1 = pts[1].fX;
2974 SkScalar y1 = pts[1].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002975
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002976 SkScalar dy = y1 - y0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002977
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002978 int dir = 1;
2979 if (y0 > y1) {
2980 SkTSwap(y0, y1);
2981 dir = -1;
2982 }
caryclark9aacd902015-12-14 08:38:09 -08002983 if (y < y0 || y > y1) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002984 return 0;
2985 }
caryclark9cb5d752015-12-18 04:35:24 -08002986 if (checkOnCurve(x, y, pts[0], pts[1])) {
2987 *onCurveCount += 1;
2988 return 0;
2989 }
2990 if (y == y1) {
caryclark9aacd902015-12-14 08:38:09 -08002991 return 0;
2992 }
2993 SkScalar cross = SkScalarMul(x1 - x0, y - pts[0].fY) - SkScalarMul(dy, x - x0);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002994
caryclark9aacd902015-12-14 08:38:09 -08002995 if (!cross) {
fsc91065d2015-12-17 09:03:27 -08002996 // zero cross means the point is on the line, and since the case where
2997 // y of the query point is at the end point is handled above, we can be
2998 // sure that we're on the line (excluding the end point) here
caryclark9cb5d752015-12-18 04:35:24 -08002999 if (x != x1 || y != pts[1].fY) {
3000 *onCurveCount += 1;
3001 }
caryclark9aacd902015-12-14 08:38:09 -08003002 dir = 0;
3003 } else if (SkScalarSignAsInt(cross) == dir) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003004 dir = 0;
3005 }
3006 return dir;
3007}
3008
caryclark9aacd902015-12-14 08:38:09 -08003009static void tangent_cubic(const SkPoint pts[], SkScalar x, SkScalar y,
3010 SkTDArray<SkVector>* tangents) {
3011 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)
3012 && !between(pts[2].fY, y, pts[3].fY)) {
3013 return;
3014 }
3015 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)
3016 && !between(pts[2].fX, x, pts[3].fX)) {
3017 return;
3018 }
3019 SkPoint dst[10];
3020 int n = SkChopCubicAtYExtrema(pts, dst);
3021 for (int i = 0; i <= n; ++i) {
3022 SkPoint* c = &dst[i * 3];
3023 SkScalar t;
3024 SkAssertResult(SkCubicClipper::ChopMonoAtY(c, y, &t));
3025 SkScalar xt = eval_cubic_pts(c[0].fX, c[1].fX, c[2].fX, c[3].fX, t);
3026 if (!SkScalarNearlyEqual(x, xt)) {
3027 continue;
3028 }
3029 SkVector tangent;
3030 SkEvalCubicAt(c, t, nullptr, &tangent, nullptr);
3031 tangents->push(tangent);
3032 }
3033}
3034
3035static void tangent_conic(const SkPoint pts[], SkScalar x, SkScalar y, SkScalar w,
3036 SkTDArray<SkVector>* tangents) {
3037 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)) {
3038 return;
3039 }
3040 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)) {
3041 return;
3042 }
3043 SkScalar roots[2];
3044 SkScalar A = pts[2].fY;
3045 SkScalar B = pts[1].fY * w - y * w + y;
3046 SkScalar C = pts[0].fY;
3047 A += C - 2 * B; // A = a + c - 2*(b*w - yCept*w + yCept)
3048 B -= C; // B = b*w - w * yCept + yCept - a
3049 C -= y;
3050 int n = SkFindUnitQuadRoots(A, 2 * B, C, roots);
3051 for (int index = 0; index < n; ++index) {
3052 SkScalar t = roots[index];
3053 SkScalar xt = conic_eval_numerator(&pts[0].fX, w, t) / conic_eval_denominator(w, t);
3054 if (!SkScalarNearlyEqual(x, xt)) {
3055 continue;
3056 }
3057 SkConic conic(pts, w);
3058 tangents->push(conic.evalTangentAt(t));
3059 }
3060}
3061
3062static void tangent_quad(const SkPoint pts[], SkScalar x, SkScalar y,
3063 SkTDArray<SkVector>* tangents) {
3064 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)) {
3065 return;
3066 }
3067 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)) {
3068 return;
3069 }
3070 SkScalar roots[2];
3071 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
3072 2 * (pts[1].fY - pts[0].fY),
3073 pts[0].fY - y,
3074 roots);
3075 for (int index = 0; index < n; ++index) {
3076 SkScalar t = roots[index];
3077 SkScalar C = pts[0].fX;
3078 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
3079 SkScalar B = 2 * (pts[1].fX - C);
3080 SkScalar xt = (A * t + B) * t + C;
3081 if (!SkScalarNearlyEqual(x, xt)) {
3082 continue;
3083 }
3084 tangents->push(SkEvalQuadTangentAt(pts, t));
3085 }
3086}
3087
3088static void tangent_line(const SkPoint pts[], SkScalar x, SkScalar y,
3089 SkTDArray<SkVector>* tangents) {
3090 SkScalar y0 = pts[0].fY;
3091 SkScalar y1 = pts[1].fY;
3092 if (!between(y0, y, y1)) {
3093 return;
3094 }
3095 SkScalar x0 = pts[0].fX;
3096 SkScalar x1 = pts[1].fX;
3097 if (!between(x0, x, x1)) {
3098 return;
3099 }
3100 SkScalar dx = x1 - x0;
3101 SkScalar dy = y1 - y0;
3102 if (!SkScalarNearlyEqual((x - x0) * dy, dx * (y - y0))) {
3103 return;
3104 }
3105 SkVector v;
3106 v.set(dx, dy);
3107 tangents->push(v);
3108}
3109
reed@google.com4db592c2013-10-30 17:39:43 +00003110static bool contains_inclusive(const SkRect& r, SkScalar x, SkScalar y) {
3111 return r.fLeft <= x && x <= r.fRight && r.fTop <= y && y <= r.fBottom;
3112}
3113
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003114bool SkPath::contains(SkScalar x, SkScalar y) const {
3115 bool isInverse = this->isInverseFillType();
3116 if (this->isEmpty()) {
3117 return isInverse;
3118 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003119
reed@google.com4db592c2013-10-30 17:39:43 +00003120 if (!contains_inclusive(this->getBounds(), x, y)) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003121 return isInverse;
3122 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003123
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003124 SkPath::Iter iter(*this, true);
3125 bool done = false;
3126 int w = 0;
caryclark9aacd902015-12-14 08:38:09 -08003127 int onCurveCount = 0;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003128 do {
3129 SkPoint pts[4];
3130 switch (iter.next(pts, false)) {
3131 case SkPath::kMove_Verb:
3132 case SkPath::kClose_Verb:
3133 break;
3134 case SkPath::kLine_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003135 w += winding_line(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003136 break;
3137 case SkPath::kQuad_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003138 w += winding_quad(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003139 break;
reed@google.com277c3f82013-05-31 15:17:50 +00003140 case SkPath::kConic_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003141 w += winding_conic(pts, x, y, iter.conicWeight(), &onCurveCount);
reed@google.com277c3f82013-05-31 15:17:50 +00003142 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003143 case SkPath::kCubic_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003144 w += winding_cubic(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003145 break;
3146 case SkPath::kDone_Verb:
3147 done = true;
3148 break;
reed@google.com277c3f82013-05-31 15:17:50 +00003149 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003150 } while (!done);
caryclark9aacd902015-12-14 08:38:09 -08003151 bool evenOddFill = SkPath::kEvenOdd_FillType == this->getFillType()
3152 || SkPath::kInverseEvenOdd_FillType == this->getFillType();
3153 if (evenOddFill) {
3154 w &= 1;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003155 }
caryclark9aacd902015-12-14 08:38:09 -08003156 if (w) {
3157 return !isInverse;
3158 }
3159 if (onCurveCount <= 1) {
3160 return SkToBool(onCurveCount) ^ isInverse;
3161 }
3162 if ((onCurveCount & 1) || evenOddFill) {
3163 return SkToBool(onCurveCount & 1) ^ isInverse;
3164 }
3165 // If the point touches an even number of curves, and the fill is winding, check for
3166 // coincidence. Count coincidence as places where the on curve points have identical tangents.
3167 iter.setPath(*this, true);
caryclark9cb5d752015-12-18 04:35:24 -08003168 done = false;
caryclark9aacd902015-12-14 08:38:09 -08003169 SkTDArray<SkVector> tangents;
3170 do {
3171 SkPoint pts[4];
3172 int oldCount = tangents.count();
3173 switch (iter.next(pts, false)) {
3174 case SkPath::kMove_Verb:
3175 case SkPath::kClose_Verb:
3176 break;
3177 case SkPath::kLine_Verb:
3178 tangent_line(pts, x, y, &tangents);
3179 break;
3180 case SkPath::kQuad_Verb:
3181 tangent_quad(pts, x, y, &tangents);
3182 break;
3183 case SkPath::kConic_Verb:
3184 tangent_conic(pts, x, y, iter.conicWeight(), &tangents);
3185 break;
3186 case SkPath::kCubic_Verb:
3187 tangent_cubic(pts, x, y, &tangents);
3188 break;
3189 case SkPath::kDone_Verb:
3190 done = true;
3191 break;
3192 }
3193 if (tangents.count() > oldCount) {
3194 int last = tangents.count() - 1;
3195 const SkVector& tangent = tangents[last];
3196 if (SkScalarNearlyZero(tangent.lengthSqd())) {
3197 tangents.remove(last);
3198 } else {
3199 for (int index = 0; index < last; ++index) {
3200 const SkVector& test = tangents[index];
3201 if (SkScalarNearlyZero(test.cross(tangent))
caryclark9cb5d752015-12-18 04:35:24 -08003202 && SkScalarSignAsInt(tangent.fX * test.fX) <= 0
3203 && SkScalarSignAsInt(tangent.fY * test.fY) <= 0) {
caryclark9aacd902015-12-14 08:38:09 -08003204 tangents.remove(last);
3205 tangents.removeShuffle(index);
3206 break;
3207 }
3208 }
3209 }
3210 }
3211 } while (!done);
3212 return SkToBool(tangents.count()) ^ isInverse;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003213}
fmalitaaa0df4e2015-12-01 09:13:23 -08003214
3215int SkPath::ConvertConicToQuads(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2,
3216 SkScalar w, SkPoint pts[], int pow2) {
3217 const SkConic conic(p0, p1, p2, w);
3218 return conic.chopIntoQuadsPOW2(pts, pow2);
3219}