blob: 4c2f89730c84f9e109f6f8c8ae5f55b584c9eb9a [file] [log] [blame]
epoger@google.comec3ed6a2011-07-28 14:26:00 +00001/*
2 * Copyright 2006 The Android Open Source Project
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
djsollen@google.com94e75ee2012-06-08 18:30:46 +00008#include "SkBuffer.h"
caryclark9aacd902015-12-14 08:38:09 -08009#include "SkCubicClipper.h"
humper@google.com75e3ca12013-04-08 21:44:11 +000010#include "SkErrorInternals.h"
reed220f9262014-12-17 08:21:04 -080011#include "SkGeometry.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000012#include "SkMath.h"
reed026beb52015-06-10 14:23:15 -070013#include "SkPathPriv.h"
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +000014#include "SkPathRef.h"
reed@google.com4ed0fb72012-12-12 20:48:18 +000015#include "SkRRect.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000016
17////////////////////////////////////////////////////////////////////////////
18
reed@google.com3563c9e2011-11-14 19:34:57 +000019/**
20 * Path.bounds is defined to be the bounds of all the control points.
21 * If we called bounds.join(r) we would skip r if r was empty, which breaks
22 * our promise. Hence we have a custom joiner that doesn't look at emptiness
23 */
24static void joinNoEmptyChecks(SkRect* dst, const SkRect& src) {
25 dst->fLeft = SkMinScalar(dst->fLeft, src.fLeft);
26 dst->fTop = SkMinScalar(dst->fTop, src.fTop);
27 dst->fRight = SkMaxScalar(dst->fRight, src.fRight);
28 dst->fBottom = SkMaxScalar(dst->fBottom, src.fBottom);
29}
30
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000031static bool is_degenerate(const SkPath& path) {
32 SkPath::Iter iter(path, false);
33 SkPoint pts[4];
34 return SkPath::kDone_Verb == iter.next(pts);
35}
36
bsalomon@google.com30c174b2012-11-13 14:36:42 +000037class SkAutoDisableDirectionCheck {
38public:
39 SkAutoDisableDirectionCheck(SkPath* path) : fPath(path) {
herb9f4dbca2015-09-28 11:05:47 -070040 fSaved = static_cast<SkPathPriv::FirstDirection>(fPath->fFirstDirection.load());
bsalomon@google.com30c174b2012-11-13 14:36:42 +000041 }
42
43 ~SkAutoDisableDirectionCheck() {
reed026beb52015-06-10 14:23:15 -070044 fPath->fFirstDirection = fSaved;
bsalomon@google.com30c174b2012-11-13 14:36:42 +000045 }
46
47private:
reed026beb52015-06-10 14:23:15 -070048 SkPath* fPath;
49 SkPathPriv::FirstDirection fSaved;
bsalomon@google.com30c174b2012-11-13 14:36:42 +000050};
commit-bot@chromium.orge61a86c2013-11-18 16:03:59 +000051#define SkAutoDisableDirectionCheck(...) SK_REQUIRE_LOCAL_VAR(SkAutoDisableDirectionCheck)
bsalomon@google.com30c174b2012-11-13 14:36:42 +000052
reed@android.com8a1c16f2008-12-17 15:59:43 +000053/* This guy's constructor/destructor bracket a path editing operation. It is
54 used when we know the bounds of the amount we are going to add to the path
55 (usually a new contour, but not required).
reed@google.comabf15c12011-01-18 20:35:51 +000056
reed@android.com8a1c16f2008-12-17 15:59:43 +000057 It captures some state about the path up front (i.e. if it already has a
robertphillips@google.comca0c8382013-09-26 12:18:23 +000058 cached bounds), and then if it can, it updates the cache bounds explicitly,
reed@android.comd252db02009-04-01 18:31:44 +000059 avoiding the need to revisit all of the points in getBounds().
reed@google.comabf15c12011-01-18 20:35:51 +000060
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000061 It also notes if the path was originally degenerate, and if so, sets
62 isConvex to true. Thus it can only be used if the contour being added is
robertphillips@google.com466310d2013-12-03 16:43:54 +000063 convex.
reed@android.com8a1c16f2008-12-17 15:59:43 +000064 */
65class SkAutoPathBoundsUpdate {
66public:
67 SkAutoPathBoundsUpdate(SkPath* path, const SkRect& r) : fRect(r) {
68 this->init(path);
69 }
70
71 SkAutoPathBoundsUpdate(SkPath* path, SkScalar left, SkScalar top,
72 SkScalar right, SkScalar bottom) {
73 fRect.set(left, top, right, bottom);
74 this->init(path);
75 }
reed@google.comabf15c12011-01-18 20:35:51 +000076
reed@android.com8a1c16f2008-12-17 15:59:43 +000077 ~SkAutoPathBoundsUpdate() {
reed@google.com44699382013-10-31 17:28:30 +000078 fPath->setConvexity(fDegenerate ? SkPath::kConvex_Convexity
79 : SkPath::kUnknown_Convexity);
robertphillips@google.comca0c8382013-09-26 12:18:23 +000080 if (fEmpty || fHasValidBounds) {
81 fPath->setBounds(fRect);
reed@android.com8a1c16f2008-12-17 15:59:43 +000082 }
83 }
reed@google.comabf15c12011-01-18 20:35:51 +000084
reed@android.com8a1c16f2008-12-17 15:59:43 +000085private:
reed@android.com6b82d1a2009-06-03 02:35:01 +000086 SkPath* fPath;
87 SkRect fRect;
robertphillips@google.comca0c8382013-09-26 12:18:23 +000088 bool fHasValidBounds;
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000089 bool fDegenerate;
reed@android.com6b82d1a2009-06-03 02:35:01 +000090 bool fEmpty;
reed@google.comabf15c12011-01-18 20:35:51 +000091
reed@android.com6b82d1a2009-06-03 02:35:01 +000092 void init(SkPath* path) {
robertphillips@google.comca0c8382013-09-26 12:18:23 +000093 // Cannot use fRect for our bounds unless we know it is sorted
94 fRect.sort();
reed@android.com8a1c16f2008-12-17 15:59:43 +000095 fPath = path;
reed@google.coma8790de2012-10-24 21:04:04 +000096 // Mark the path's bounds as dirty if (1) they are, or (2) the path
97 // is non-finite, and therefore its bounds are not meaningful
robertphillips@google.comca0c8382013-09-26 12:18:23 +000098 fHasValidBounds = path->hasComputedBounds() && path->isFinite();
reed@android.com8a1c16f2008-12-17 15:59:43 +000099 fEmpty = path->isEmpty();
robertphillips@google.comca0c8382013-09-26 12:18:23 +0000100 if (fHasValidBounds && !fEmpty) {
101 joinNoEmptyChecks(&fRect, fPath->getBounds());
102 }
103 fDegenerate = is_degenerate(*path);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000104 }
105};
commit-bot@chromium.orge61a86c2013-11-18 16:03:59 +0000106#define SkAutoPathBoundsUpdate(...) SK_REQUIRE_LOCAL_VAR(SkAutoPathBoundsUpdate)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000107
reed@android.com8a1c16f2008-12-17 15:59:43 +0000108////////////////////////////////////////////////////////////////////////////
109
110/*
111 Stores the verbs and points as they are given to us, with exceptions:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000112 - we only record "Close" if it was immediately preceeded by Move | Line | Quad | Cubic
reed@android.com8a1c16f2008-12-17 15:59:43 +0000113 - we insert a Move(0,0) if Line | Quad | Cubic is our first command
114
115 The iterator does more cleanup, especially if forceClose == true
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000116 1. If we encounter degenerate segments, remove them
117 2. if we encounter Close, return a cons'd up Line() first (if the curr-pt != start-pt)
118 3. if we encounter Move without a preceeding Close, and forceClose is true, goto #2
119 4. if we encounter Line | Quad | Cubic after Close, cons up a Move
reed@android.com8a1c16f2008-12-17 15:59:43 +0000120*/
121
122////////////////////////////////////////////////////////////////////////////
123
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000124// flag to require a moveTo if we begin with something else, like lineTo etc.
125#define INITIAL_LASTMOVETOINDEX_VALUE ~0
126
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000127SkPath::SkPath()
djsollen523cda32015-02-17 08:06:32 -0800128 : fPathRef(SkPathRef::CreateEmpty()) {
bungeman@google.coma5809a32013-06-21 15:13:34 +0000129 this->resetFields();
jvanverthb3eb6872014-10-24 07:12:51 -0700130 fIsVolatile = false;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000131}
132
133void SkPath::resetFields() {
134 //fPathRef is assumed to have been emptied by the caller.
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000135 fLastMoveToIndex = INITIAL_LASTMOVETOINDEX_VALUE;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000136 fFillType = kWinding_FillType;
reed@google.com04863fa2011-05-15 04:08:24 +0000137 fConvexity = kUnknown_Convexity;
reed026beb52015-06-10 14:23:15 -0700138 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000139
140 // We don't touch Android's fSourcePath. It's used to track texture garbage collection, so we
halcanary96fcdcc2015-08-27 07:41:13 -0700141 // don't want to muck with it if it's been set to something non-nullptr.
reed@android.com6b82d1a2009-06-03 02:35:01 +0000142}
reed@android.com8a1c16f2008-12-17 15:59:43 +0000143
bungeman@google.coma5809a32013-06-21 15:13:34 +0000144SkPath::SkPath(const SkPath& that)
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000145 : fPathRef(SkRef(that.fPathRef.get())) {
bungeman@google.coma5809a32013-06-21 15:13:34 +0000146 this->copyFields(that);
147 SkDEBUGCODE(that.validate();)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000148}
149
150SkPath::~SkPath() {
151 SkDEBUGCODE(this->validate();)
152}
153
bungeman@google.coma5809a32013-06-21 15:13:34 +0000154SkPath& SkPath::operator=(const SkPath& that) {
155 SkDEBUGCODE(that.validate();)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000156
bungeman@google.coma5809a32013-06-21 15:13:34 +0000157 if (this != &that) {
158 fPathRef.reset(SkRef(that.fPathRef.get()));
159 this->copyFields(that);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000160 }
161 SkDEBUGCODE(this->validate();)
162 return *this;
163}
164
bungeman@google.coma5809a32013-06-21 15:13:34 +0000165void SkPath::copyFields(const SkPath& that) {
166 //fPathRef is assumed to have been set by the caller.
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000167 fLastMoveToIndex = that.fLastMoveToIndex;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000168 fFillType = that.fFillType;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000169 fConvexity = that.fConvexity;
herb9f4dbca2015-09-28 11:05:47 -0700170 // Simulate fFirstDirection = that.fFirstDirection;
171 fFirstDirection.store(that.fFirstDirection.load());
jvanverthb3eb6872014-10-24 07:12:51 -0700172 fIsVolatile = that.fIsVolatile;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000173}
174
djsollen@google.com9c1a9672013-08-09 13:49:13 +0000175bool operator==(const SkPath& a, const SkPath& b) {
reed@android.com6b82d1a2009-06-03 02:35:01 +0000176 // note: don't need to look at isConvex or bounds, since just comparing the
177 // raw data is sufficient.
reed@android.com3abec1d2009-03-02 05:36:20 +0000178 return &a == &b ||
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000179 (a.fFillType == b.fFillType && *a.fPathRef.get() == *b.fPathRef.get());
reed@android.com3abec1d2009-03-02 05:36:20 +0000180}
181
bungeman@google.coma5809a32013-06-21 15:13:34 +0000182void SkPath::swap(SkPath& that) {
bungeman@google.coma5809a32013-06-21 15:13:34 +0000183 if (this != &that) {
bungeman77a53de2015-10-01 12:28:49 -0700184 fPathRef.swap(that.fPathRef);
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000185 SkTSwap<int>(fLastMoveToIndex, that.fLastMoveToIndex);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000186 SkTSwap<uint8_t>(fFillType, that.fFillType);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000187 SkTSwap<uint8_t>(fConvexity, that.fConvexity);
herb9f4dbca2015-09-28 11:05:47 -0700188 // Simulate SkTSwap<uint8_t>(fFirstDirection, that.fFirstDirection);
189 uint8_t temp = fFirstDirection;
190 fFirstDirection.store(that.fFirstDirection.load());
191 that.fFirstDirection.store(temp);
jvanverthb3eb6872014-10-24 07:12:51 -0700192 SkTSwap<SkBool8>(fIsVolatile, that.fIsVolatile);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000193 }
194}
195
caryclark8e7b19d2016-02-18 04:11:48 -0800196bool SkPath::isInterpolatable(const SkPath& compare) const {
197 int count = fPathRef->countVerbs();
198 if (count != compare.fPathRef->countVerbs()) {
199 return false;
200 }
201 if (!count) {
202 return true;
203 }
204 if (memcmp(fPathRef->verbsMemBegin(), compare.fPathRef->verbsMemBegin(),
205 count)) {
206 return false;
207 }
208 return !SkToBool(memcmp(fPathRef->conicWeights(), compare.fPathRef->conicWeights(),
209 fPathRef->countWeights() * sizeof(*fPathRef->conicWeights())));
210}
211
212bool SkPath::interpolate(const SkPath& ending, SkScalar weight, SkPath* out) const {
213 int verbCount = fPathRef->countVerbs();
214 if (verbCount != ending.fPathRef->countVerbs()) {
215 return false;
216 }
217 out->reset();
218 out->addPath(*this);
219 fPathRef->interpolate(*ending.fPathRef, weight, out->fPathRef);
220 return true;
221}
222
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000223static inline bool check_edge_against_rect(const SkPoint& p0,
224 const SkPoint& p1,
225 const SkRect& rect,
reed026beb52015-06-10 14:23:15 -0700226 SkPathPriv::FirstDirection dir) {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000227 const SkPoint* edgeBegin;
228 SkVector v;
reed026beb52015-06-10 14:23:15 -0700229 if (SkPathPriv::kCW_FirstDirection == dir) {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000230 v = p1 - p0;
231 edgeBegin = &p0;
232 } else {
233 v = p0 - p1;
234 edgeBegin = &p1;
235 }
236 if (v.fX || v.fY) {
237 // check the cross product of v with the vec from edgeBegin to each rect corner
238 SkScalar yL = SkScalarMul(v.fY, rect.fLeft - edgeBegin->fX);
239 SkScalar xT = SkScalarMul(v.fX, rect.fTop - edgeBegin->fY);
240 SkScalar yR = SkScalarMul(v.fY, rect.fRight - edgeBegin->fX);
241 SkScalar xB = SkScalarMul(v.fX, rect.fBottom - edgeBegin->fY);
242 if ((xT < yL) || (xT < yR) || (xB < yL) || (xB < yR)) {
243 return false;
244 }
245 }
246 return true;
247}
248
249bool SkPath::conservativelyContainsRect(const SkRect& rect) const {
250 // This only handles non-degenerate convex paths currently.
251 if (kConvex_Convexity != this->getConvexity()) {
252 return false;
253 }
skia.committer@gmail.comcec8de62012-11-14 02:01:22 +0000254
reed026beb52015-06-10 14:23:15 -0700255 SkPathPriv::FirstDirection direction;
256 if (!SkPathPriv::CheapComputeFirstDirection(*this, &direction)) {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000257 return false;
258 }
259
260 SkPoint firstPt;
261 SkPoint prevPt;
262 RawIter iter(*this);
263 SkPath::Verb verb;
264 SkPoint pts[4];
265 SkDEBUGCODE(int moveCnt = 0;)
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000266 SkDEBUGCODE(int segmentCount = 0;)
267 SkDEBUGCODE(int closeCount = 0;)
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000268
269 while ((verb = iter.next(pts)) != kDone_Verb) {
270 int nextPt = -1;
271 switch (verb) {
272 case kMove_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000273 SkASSERT(!segmentCount && !closeCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000274 SkDEBUGCODE(++moveCnt);
275 firstPt = prevPt = pts[0];
276 break;
277 case kLine_Verb:
278 nextPt = 1;
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000279 SkASSERT(moveCnt && !closeCount);
280 SkDEBUGCODE(++segmentCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000281 break;
282 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000283 case kConic_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000284 SkASSERT(moveCnt && !closeCount);
285 SkDEBUGCODE(++segmentCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000286 nextPt = 2;
287 break;
288 case kCubic_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000289 SkASSERT(moveCnt && !closeCount);
290 SkDEBUGCODE(++segmentCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000291 nextPt = 3;
292 break;
293 case kClose_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000294 SkDEBUGCODE(++closeCount;)
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000295 break;
296 default:
297 SkDEBUGFAIL("unknown verb");
298 }
299 if (-1 != nextPt) {
reed220f9262014-12-17 08:21:04 -0800300 if (SkPath::kConic_Verb == verb) {
301 SkConic orig;
302 orig.set(pts, iter.conicWeight());
303 SkPoint quadPts[5];
304 int count = orig.chopIntoQuadsPOW2(quadPts, 1);
djsollenf2b340f2016-01-29 08:51:04 -0800305 SkASSERT_RELEASE(2 == count);
reed220f9262014-12-17 08:21:04 -0800306
307 if (!check_edge_against_rect(quadPts[0], quadPts[2], rect, direction)) {
308 return false;
309 }
310 if (!check_edge_against_rect(quadPts[2], quadPts[4], rect, direction)) {
311 return false;
312 }
313 } else {
314 if (!check_edge_against_rect(prevPt, pts[nextPt], rect, direction)) {
315 return false;
316 }
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000317 }
318 prevPt = pts[nextPt];
319 }
320 }
321
322 return check_edge_against_rect(prevPt, firstPt, rect, direction);
323}
324
robertphillips@google.com7101abe2013-10-29 22:45:37 +0000325uint32_t SkPath::getGenerationID() const {
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000326 uint32_t genID = fPathRef->genID();
djsollen523cda32015-02-17 08:06:32 -0800327#ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000328 SkASSERT((unsigned)fFillType < (1 << (32 - kPathRefGenIDBitCnt)));
329 genID |= static_cast<uint32_t>(fFillType) << kPathRefGenIDBitCnt;
330#endif
331 return genID;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000332}
djsollen@google.come63793a2012-03-21 15:39:03 +0000333
reed@android.com8a1c16f2008-12-17 15:59:43 +0000334void SkPath::reset() {
335 SkDEBUGCODE(this->validate();)
336
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000337 fPathRef.reset(SkPathRef::CreateEmpty());
bungeman@google.coma5809a32013-06-21 15:13:34 +0000338 this->resetFields();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000339}
340
341void SkPath::rewind() {
342 SkDEBUGCODE(this->validate();)
343
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000344 SkPathRef::Rewind(&fPathRef);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000345 this->resetFields();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000346}
347
fsb1475b02016-01-20 09:51:07 -0800348bool SkPath::isLastContourClosed() const {
349 int verbCount = fPathRef->countVerbs();
350 if (0 == verbCount) {
351 return false;
352 }
353 return kClose_Verb == fPathRef->atVerb(verbCount - 1);
354}
355
reed@google.com7e6c4d12012-05-10 14:05:43 +0000356bool SkPath::isLine(SkPoint line[2]) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000357 int verbCount = fPathRef->countVerbs();
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000358
commit-bot@chromium.orga62efcc2013-08-05 13:23:13 +0000359 if (2 == verbCount) {
360 SkASSERT(kMove_Verb == fPathRef->atVerb(0));
361 if (kLine_Verb == fPathRef->atVerb(1)) {
362 SkASSERT(2 == fPathRef->countPoints());
reed@google.com7e6c4d12012-05-10 14:05:43 +0000363 if (line) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000364 const SkPoint* pts = fPathRef->points();
reed@google.com7e6c4d12012-05-10 14:05:43 +0000365 line[0] = pts[0];
366 line[1] = pts[1];
367 }
368 return true;
369 }
370 }
371 return false;
372}
373
caryclark@google.comf1316942011-07-26 19:54:45 +0000374/*
375 Determines if path is a rect by keeping track of changes in direction
376 and looking for a loop either clockwise or counterclockwise.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000377
caryclark@google.comf1316942011-07-26 19:54:45 +0000378 The direction is computed such that:
379 0: vertical up
caryclark@google.comf68154a2012-11-21 15:18:06 +0000380 1: horizontal left
caryclark@google.comf1316942011-07-26 19:54:45 +0000381 2: vertical down
caryclark@google.comf68154a2012-11-21 15:18:06 +0000382 3: horizontal right
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000383
caryclark@google.comf1316942011-07-26 19:54:45 +0000384A rectangle cycles up/right/down/left or up/left/down/right.
385
386The test fails if:
387 The path is closed, and followed by a line.
388 A second move creates a new endpoint.
389 A diagonal line is parsed.
390 There's more than four changes of direction.
391 There's a discontinuity on the line (e.g., a move in the middle)
392 The line reverses direction.
caryclark@google.comf1316942011-07-26 19:54:45 +0000393 The path contains a quadratic or cubic.
394 The path contains fewer than four points.
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000395 *The rectangle doesn't complete a cycle.
396 *The final point isn't equal to the first point.
397
398 *These last two conditions we relax if we have a 3-edge path that would
399 form a rectangle if it were closed (as we do when we fill a path)
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000400
caryclark@google.comf1316942011-07-26 19:54:45 +0000401It's OK if the path has:
402 Several colinear line segments composing a rectangle side.
403 Single points on the rectangle side.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000404
caryclark@google.comf1316942011-07-26 19:54:45 +0000405The direction takes advantage of the corners found since opposite sides
406must travel in opposite directions.
407
408FIXME: Allow colinear quads and cubics to be treated like lines.
409FIXME: If the API passes fill-only, return true if the filled stroke
410 is a rectangle, though the caller failed to close the path.
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000411
412 first,last,next direction state-machine:
413 0x1 is set if the segment is horizontal
414 0x2 is set if the segment is moving to the right or down
415 thus:
416 two directions are opposites iff (dirA ^ dirB) == 0x2
417 two directions are perpendicular iff (dirA ^ dirB) == 0x1
skia.committer@gmail.comf5e67c12014-01-16 07:01:48 +0000418
caryclark@google.comf1316942011-07-26 19:54:45 +0000419 */
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000420static int rect_make_dir(SkScalar dx, SkScalar dy) {
421 return ((0 != dx) << 0) | ((dx > 0 || dy > 0) << 1);
422}
caryclark@google.comf68154a2012-11-21 15:18:06 +0000423bool SkPath::isRectContour(bool allowPartial, int* currVerb, const SkPoint** ptsPtr,
424 bool* isClosed, Direction* direction) const {
caryclark@google.comf1316942011-07-26 19:54:45 +0000425 int corners = 0;
426 SkPoint first, last;
caryclark@google.com56f233a2012-11-19 13:06:06 +0000427 const SkPoint* pts = *ptsPtr;
halcanary96fcdcc2015-08-27 07:41:13 -0700428 const SkPoint* savePts = nullptr;
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000429 first.set(0, 0);
430 last.set(0, 0);
431 int firstDirection = 0;
432 int lastDirection = 0;
433 int nextDirection = 0;
434 bool closedOrMoved = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000435 bool autoClose = false;
caryclark95bc5f32015-04-08 08:34:15 -0700436 bool insertClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000437 int verbCnt = fPathRef->countVerbs();
caryclark@google.com56f233a2012-11-19 13:06:06 +0000438 while (*currVerb < verbCnt && (!allowPartial || !autoClose)) {
caryclark95bc5f32015-04-08 08:34:15 -0700439 uint8_t verb = insertClose ? (uint8_t) kClose_Verb : fPathRef->atVerb(*currVerb);
440 switch (verb) {
caryclark@google.comf1316942011-07-26 19:54:45 +0000441 case kClose_Verb:
caryclark@google.com56f233a2012-11-19 13:06:06 +0000442 savePts = pts;
443 pts = *ptsPtr;
caryclark@google.comf1316942011-07-26 19:54:45 +0000444 autoClose = true;
caryclark95bc5f32015-04-08 08:34:15 -0700445 insertClose = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000446 case kLine_Verb: {
447 SkScalar left = last.fX;
448 SkScalar top = last.fY;
449 SkScalar right = pts->fX;
450 SkScalar bottom = pts->fY;
451 ++pts;
452 if (left != right && top != bottom) {
453 return false; // diagonal
454 }
455 if (left == right && top == bottom) {
456 break; // single point on side OK
457 }
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000458 nextDirection = rect_make_dir(right - left, bottom - top);
caryclark@google.comf1316942011-07-26 19:54:45 +0000459 if (0 == corners) {
460 firstDirection = nextDirection;
461 first = last;
462 last = pts[-1];
463 corners = 1;
464 closedOrMoved = false;
465 break;
466 }
467 if (closedOrMoved) {
468 return false; // closed followed by a line
469 }
caryclark@google.combfe90372012-11-21 13:56:20 +0000470 if (autoClose && nextDirection == firstDirection) {
471 break; // colinear with first
472 }
caryclark@google.comf1316942011-07-26 19:54:45 +0000473 closedOrMoved = autoClose;
474 if (lastDirection != nextDirection) {
475 if (++corners > 4) {
476 return false; // too many direction changes
477 }
478 }
479 last = pts[-1];
480 if (lastDirection == nextDirection) {
481 break; // colinear segment
482 }
483 // Possible values for corners are 2, 3, and 4.
484 // When corners == 3, nextDirection opposes firstDirection.
485 // Otherwise, nextDirection at corner 2 opposes corner 4.
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000486 int turn = firstDirection ^ (corners - 1);
caryclark@google.comf1316942011-07-26 19:54:45 +0000487 int directionCycle = 3 == corners ? 0 : nextDirection ^ turn;
488 if ((directionCycle ^ turn) != nextDirection) {
489 return false; // direction didn't follow cycle
490 }
491 break;
492 }
493 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000494 case kConic_Verb:
caryclark@google.comf1316942011-07-26 19:54:45 +0000495 case kCubic_Verb:
496 return false; // quadratic, cubic not allowed
497 case kMove_Verb:
caryclark95bc5f32015-04-08 08:34:15 -0700498 if (allowPartial && !autoClose && firstDirection) {
499 insertClose = true;
500 *currVerb -= 1; // try move again afterwards
501 goto addMissingClose;
502 }
caryclark@google.comf1316942011-07-26 19:54:45 +0000503 last = *pts++;
504 closedOrMoved = true;
505 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000506 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000507 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000508 break;
caryclark@google.comf1316942011-07-26 19:54:45 +0000509 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000510 *currVerb += 1;
caryclark@google.comf1316942011-07-26 19:54:45 +0000511 lastDirection = nextDirection;
caryclark95bc5f32015-04-08 08:34:15 -0700512addMissingClose:
513 ;
caryclark@google.comf1316942011-07-26 19:54:45 +0000514 }
515 // Success if 4 corners and first point equals last
caryclark@google.combfe90372012-11-21 13:56:20 +0000516 bool result = 4 == corners && (first == last || autoClose);
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000517 if (!result) {
518 // check if we are just an incomplete rectangle, in which case we can
519 // return true, but not claim to be closed.
520 // e.g.
521 // 3 sided rectangle
522 // 4 sided but the last edge is not long enough to reach the start
523 //
524 SkScalar closeX = first.x() - last.x();
525 SkScalar closeY = first.y() - last.y();
526 if (closeX && closeY) {
527 return false; // we're diagonal, abort (can we ever reach this?)
528 }
529 int closeDirection = rect_make_dir(closeX, closeY);
530 // make sure the close-segment doesn't double-back on itself
531 if (3 == corners || (4 == corners && closeDirection == lastDirection)) {
532 result = true;
533 autoClose = false; // we are not closed
534 }
535 }
caryclark@google.combfe90372012-11-21 13:56:20 +0000536 if (savePts) {
537 *ptsPtr = savePts;
538 }
caryclark@google.comf68154a2012-11-21 15:18:06 +0000539 if (result && isClosed) {
540 *isClosed = autoClose;
541 }
542 if (result && direction) {
sugoi@google.com12b4e272012-12-06 20:13:11 +0000543 *direction = firstDirection == ((lastDirection + 1) & 3) ? kCCW_Direction : kCW_Direction;
caryclark@google.comf68154a2012-11-21 15:18:06 +0000544 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000545 return result;
546}
547
robertphillips4f662e62014-12-29 14:06:51 -0800548bool SkPath::isRect(SkRect* rect, bool* isClosed, Direction* direction) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000549 SkDEBUGCODE(this->validate();)
550 int currVerb = 0;
551 const SkPoint* pts = fPathRef->points();
robertphillipsfe7c4272014-12-29 11:36:39 -0800552 const SkPoint* first = pts;
robertphillips4f662e62014-12-29 14:06:51 -0800553 if (!this->isRectContour(false, &currVerb, &pts, isClosed, direction)) {
robertphillipsfe7c4272014-12-29 11:36:39 -0800554 return false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000555 }
robertphillipsfe7c4272014-12-29 11:36:39 -0800556 if (rect) {
robertphillips4f662e62014-12-29 14:06:51 -0800557 int32_t num = SkToS32(pts - first);
558 if (num) {
559 rect->set(first, num);
robertphillipsfe7c4272014-12-29 11:36:39 -0800560 } else {
561 // 'pts' isn't updated for open rects
562 *rect = this->getBounds();
563 }
564 }
565 return true;
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000566}
skia.committer@gmail.com020b25b2013-06-22 07:00:58 +0000567
caryclark95bc5f32015-04-08 08:34:15 -0700568bool SkPath::isNestedFillRects(SkRect rects[2], Direction dirs[2]) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000569 SkDEBUGCODE(this->validate();)
570 int currVerb = 0;
571 const SkPoint* pts = fPathRef->points();
572 const SkPoint* first = pts;
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000573 Direction testDirs[2];
halcanary96fcdcc2015-08-27 07:41:13 -0700574 if (!isRectContour(true, &currVerb, &pts, nullptr, &testDirs[0])) {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000575 return false;
576 }
577 const SkPoint* last = pts;
578 SkRect testRects[2];
caryclark95bc5f32015-04-08 08:34:15 -0700579 bool isClosed;
580 if (isRectContour(false, &currVerb, &pts, &isClosed, &testDirs[1])) {
scroggo@google.com614f9e32013-05-09 18:05:32 +0000581 testRects[0].set(first, SkToS32(last - first));
caryclark95bc5f32015-04-08 08:34:15 -0700582 if (!isClosed) {
583 pts = fPathRef->points() + fPathRef->countPoints();
584 }
scroggo@google.com614f9e32013-05-09 18:05:32 +0000585 testRects[1].set(last, SkToS32(pts - last));
caryclark@google.com56f233a2012-11-19 13:06:06 +0000586 if (testRects[0].contains(testRects[1])) {
587 if (rects) {
588 rects[0] = testRects[0];
589 rects[1] = testRects[1];
590 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000591 if (dirs) {
592 dirs[0] = testDirs[0];
593 dirs[1] = testDirs[1];
594 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000595 return true;
596 }
597 if (testRects[1].contains(testRects[0])) {
598 if (rects) {
599 rects[0] = testRects[1];
600 rects[1] = testRects[0];
601 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000602 if (dirs) {
603 dirs[0] = testDirs[1];
604 dirs[1] = testDirs[0];
605 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000606 return true;
607 }
608 }
609 return false;
610}
611
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000612int SkPath::countPoints() const {
613 return fPathRef->countPoints();
614}
615
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000616int SkPath::getPoints(SkPoint dst[], int max) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000617 SkDEBUGCODE(this->validate();)
618
619 SkASSERT(max >= 0);
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000620 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000621 int count = SkMin32(max, fPathRef->countPoints());
mtklein8bf5d172015-12-09 13:15:07 -0800622 sk_careful_memcpy(dst, fPathRef->points(), count * sizeof(SkPoint));
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000623 return fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000624}
625
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000626SkPoint SkPath::getPoint(int index) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000627 if ((unsigned)index < (unsigned)fPathRef->countPoints()) {
628 return fPathRef->atPoint(index);
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000629 }
630 return SkPoint::Make(0, 0);
631}
632
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000633int SkPath::countVerbs() const {
634 return fPathRef->countVerbs();
635}
636
637static inline void copy_verbs_reverse(uint8_t* inorderDst,
638 const uint8_t* reversedSrc,
639 int count) {
640 for (int i = 0; i < count; ++i) {
641 inorderDst[i] = reversedSrc[~i];
642 }
643}
644
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000645int SkPath::getVerbs(uint8_t dst[], int max) const {
646 SkDEBUGCODE(this->validate();)
647
648 SkASSERT(max >= 0);
649 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000650 int count = SkMin32(max, fPathRef->countVerbs());
651 copy_verbs_reverse(dst, fPathRef->verbs(), count);
652 return fPathRef->countVerbs();
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000653}
654
reed@google.com294dd7b2011-10-11 11:58:32 +0000655bool SkPath::getLastPt(SkPoint* lastPt) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000656 SkDEBUGCODE(this->validate();)
657
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000658 int count = fPathRef->countPoints();
reed@google.com294dd7b2011-10-11 11:58:32 +0000659 if (count > 0) {
660 if (lastPt) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000661 *lastPt = fPathRef->atPoint(count - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000662 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000663 return true;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000664 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000665 if (lastPt) {
666 lastPt->set(0, 0);
667 }
668 return false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000669}
670
caryclarkaec25102015-04-29 08:28:30 -0700671void SkPath::setPt(int index, SkScalar x, SkScalar y) {
672 SkDEBUGCODE(this->validate();)
673
674 int count = fPathRef->countPoints();
675 if (count <= index) {
676 return;
677 } else {
678 SkPathRef::Editor ed(&fPathRef);
679 ed.atPoint(index)->set(x, y);
680 }
681}
682
reed@android.com8a1c16f2008-12-17 15:59:43 +0000683void SkPath::setLastPt(SkScalar x, SkScalar y) {
684 SkDEBUGCODE(this->validate();)
685
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000686 int count = fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000687 if (count == 0) {
688 this->moveTo(x, y);
689 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000690 SkPathRef::Editor ed(&fPathRef);
691 ed.atPoint(count-1)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000692 }
693}
694
reed@google.com04863fa2011-05-15 04:08:24 +0000695void SkPath::setConvexity(Convexity c) {
696 if (fConvexity != c) {
697 fConvexity = c;
reed@google.com04863fa2011-05-15 04:08:24 +0000698 }
699}
700
reed@android.com8a1c16f2008-12-17 15:59:43 +0000701//////////////////////////////////////////////////////////////////////////////
702// Construction methods
703
reed026beb52015-06-10 14:23:15 -0700704#define DIRTY_AFTER_EDIT \
705 do { \
706 fConvexity = kUnknown_Convexity; \
707 fFirstDirection = SkPathPriv::kUnknown_FirstDirection; \
reed@google.comb54455e2011-05-16 14:16:04 +0000708 } while (0)
709
reed@android.com8a1c16f2008-12-17 15:59:43 +0000710void SkPath::incReserve(U16CPU inc) {
711 SkDEBUGCODE(this->validate();)
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000712 SkPathRef::Editor(&fPathRef, inc, inc);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000713 SkDEBUGCODE(this->validate();)
714}
715
716void SkPath::moveTo(SkScalar x, SkScalar y) {
717 SkDEBUGCODE(this->validate();)
718
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000719 SkPathRef::Editor ed(&fPathRef);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000720
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000721 // remember our index
722 fLastMoveToIndex = fPathRef->countPoints();
723
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000724 ed.growForVerb(kMove_Verb)->set(x, y);
bsalomonb17c1292014-08-28 14:04:55 -0700725
726 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000727}
728
729void SkPath::rMoveTo(SkScalar x, SkScalar y) {
730 SkPoint pt;
731 this->getLastPt(&pt);
732 this->moveTo(pt.fX + x, pt.fY + y);
733}
734
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000735void SkPath::injectMoveToIfNeeded() {
736 if (fLastMoveToIndex < 0) {
737 SkScalar x, y;
738 if (fPathRef->countVerbs() == 0) {
739 x = y = 0;
740 } else {
741 const SkPoint& pt = fPathRef->atPoint(~fLastMoveToIndex);
742 x = pt.fX;
743 y = pt.fY;
744 }
745 this->moveTo(x, y);
746 }
747}
748
reed@android.com8a1c16f2008-12-17 15:59:43 +0000749void SkPath::lineTo(SkScalar x, SkScalar y) {
750 SkDEBUGCODE(this->validate();)
751
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000752 this->injectMoveToIfNeeded();
753
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000754 SkPathRef::Editor ed(&fPathRef);
755 ed.growForVerb(kLine_Verb)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000756
reed@google.comb54455e2011-05-16 14:16:04 +0000757 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000758}
759
760void SkPath::rLineTo(SkScalar x, SkScalar y) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000761 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000762 SkPoint pt;
763 this->getLastPt(&pt);
764 this->lineTo(pt.fX + x, pt.fY + y);
765}
766
767void SkPath::quadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
768 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000769
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000770 this->injectMoveToIfNeeded();
771
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000772 SkPathRef::Editor ed(&fPathRef);
773 SkPoint* pts = ed.growForVerb(kQuad_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000774 pts[0].set(x1, y1);
775 pts[1].set(x2, y2);
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000776
reed@google.comb54455e2011-05-16 14:16:04 +0000777 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000778}
779
780void SkPath::rQuadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000781 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000782 SkPoint pt;
783 this->getLastPt(&pt);
784 this->quadTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2);
785}
786
reed@google.com277c3f82013-05-31 15:17:50 +0000787void SkPath::conicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
788 SkScalar w) {
789 // check for <= 0 or NaN with this test
790 if (!(w > 0)) {
791 this->lineTo(x2, y2);
792 } else if (!SkScalarIsFinite(w)) {
793 this->lineTo(x1, y1);
794 this->lineTo(x2, y2);
795 } else if (SK_Scalar1 == w) {
796 this->quadTo(x1, y1, x2, y2);
797 } else {
798 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000799
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000800 this->injectMoveToIfNeeded();
801
reed@google.com277c3f82013-05-31 15:17:50 +0000802 SkPathRef::Editor ed(&fPathRef);
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000803 SkPoint* pts = ed.growForVerb(kConic_Verb, w);
reed@google.com277c3f82013-05-31 15:17:50 +0000804 pts[0].set(x1, y1);
805 pts[1].set(x2, y2);
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000806
reed@google.com277c3f82013-05-31 15:17:50 +0000807 DIRTY_AFTER_EDIT;
808 }
809}
810
811void SkPath::rConicTo(SkScalar dx1, SkScalar dy1, SkScalar dx2, SkScalar dy2,
812 SkScalar w) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000813 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@google.com277c3f82013-05-31 15:17:50 +0000814 SkPoint pt;
815 this->getLastPt(&pt);
816 this->conicTo(pt.fX + dx1, pt.fY + dy1, pt.fX + dx2, pt.fY + dy2, w);
817}
818
reed@android.com8a1c16f2008-12-17 15:59:43 +0000819void SkPath::cubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
820 SkScalar x3, SkScalar y3) {
821 SkDEBUGCODE(this->validate();)
822
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000823 this->injectMoveToIfNeeded();
824
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000825 SkPathRef::Editor ed(&fPathRef);
826 SkPoint* pts = ed.growForVerb(kCubic_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000827 pts[0].set(x1, y1);
828 pts[1].set(x2, y2);
829 pts[2].set(x3, y3);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000830
reed@google.comb54455e2011-05-16 14:16:04 +0000831 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000832}
833
834void SkPath::rCubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
835 SkScalar x3, SkScalar y3) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000836 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000837 SkPoint pt;
838 this->getLastPt(&pt);
839 this->cubicTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2,
840 pt.fX + x3, pt.fY + y3);
841}
842
843void SkPath::close() {
844 SkDEBUGCODE(this->validate();)
845
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000846 int count = fPathRef->countVerbs();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000847 if (count > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000848 switch (fPathRef->atVerb(count - 1)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000849 case kLine_Verb:
850 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000851 case kConic_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +0000852 case kCubic_Verb:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000853 case kMove_Verb: {
854 SkPathRef::Editor ed(&fPathRef);
855 ed.growForVerb(kClose_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000856 break;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000857 }
reed@google.com277c3f82013-05-31 15:17:50 +0000858 case kClose_Verb:
reed@google.comfa2f2a42013-05-30 15:29:48 +0000859 // don't add a close if it's the first verb or a repeat
reed@google.com7950a9e2013-05-30 14:57:55 +0000860 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000861 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000862 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000863 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000864 }
865 }
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000866
867 // signal that we need a moveTo to follow us (unless we're done)
868#if 0
869 if (fLastMoveToIndex >= 0) {
870 fLastMoveToIndex = ~fLastMoveToIndex;
871 }
872#else
873 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
874#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000875}
876
877///////////////////////////////////////////////////////////////////////////////
reed@google.comabf15c12011-01-18 20:35:51 +0000878
fmalitac08d53e2015-11-17 09:53:29 -0800879namespace {
880
881template <unsigned N>
882class PointIterator {
883public:
884 PointIterator(SkPath::Direction dir, unsigned startIndex)
885 : fCurrent(startIndex % N)
886 , fAdvance(dir == SkPath::kCW_Direction ? 1 : N - 1) { }
887
888 const SkPoint& current() const {
889 SkASSERT(fCurrent < N);
890 return fPts[fCurrent];
891 }
892
893 const SkPoint& next() {
894 fCurrent = (fCurrent + fAdvance) % N;
895 return this->current();
896 }
897
898protected:
899 SkPoint fPts[N];
900
901private:
902 unsigned fCurrent;
903 unsigned fAdvance;
904};
905
906class RectPointIterator : public PointIterator<4> {
907public:
908 RectPointIterator(const SkRect& rect, SkPath::Direction dir, unsigned startIndex)
909 : PointIterator(dir, startIndex) {
910
911 fPts[0] = SkPoint::Make(rect.fLeft, rect.fTop);
912 fPts[1] = SkPoint::Make(rect.fRight, rect.fTop);
913 fPts[2] = SkPoint::Make(rect.fRight, rect.fBottom);
914 fPts[3] = SkPoint::Make(rect.fLeft, rect.fBottom);
915 }
916};
917
918class OvalPointIterator : public PointIterator<4> {
919public:
920 OvalPointIterator(const SkRect& oval, SkPath::Direction dir, unsigned startIndex)
921 : PointIterator(dir, startIndex) {
922
923 const SkScalar cx = oval.centerX();
924 const SkScalar cy = oval.centerY();
925
926 fPts[0] = SkPoint::Make(cx, oval.fTop);
927 fPts[1] = SkPoint::Make(oval.fRight, cy);
928 fPts[2] = SkPoint::Make(cx, oval.fBottom);
929 fPts[3] = SkPoint::Make(oval.fLeft, cy);
930 }
931};
932
933class RRectPointIterator : public PointIterator<8> {
934public:
935 RRectPointIterator(const SkRRect& rrect, SkPath::Direction dir, unsigned startIndex)
936 : PointIterator(dir, startIndex) {
937
938 const SkRect& bounds = rrect.getBounds();
939 const SkScalar L = bounds.fLeft;
940 const SkScalar T = bounds.fTop;
941 const SkScalar R = bounds.fRight;
942 const SkScalar B = bounds.fBottom;
943
944 fPts[0] = SkPoint::Make(L + rrect.radii(SkRRect::kUpperLeft_Corner).fX, T);
945 fPts[1] = SkPoint::Make(R - rrect.radii(SkRRect::kUpperRight_Corner).fX, T);
946 fPts[2] = SkPoint::Make(R, T + rrect.radii(SkRRect::kUpperRight_Corner).fY);
947 fPts[3] = SkPoint::Make(R, B - rrect.radii(SkRRect::kLowerRight_Corner).fY);
948 fPts[4] = SkPoint::Make(R - rrect.radii(SkRRect::kLowerRight_Corner).fX, B);
949 fPts[5] = SkPoint::Make(L + rrect.radii(SkRRect::kLowerLeft_Corner).fX, B);
950 fPts[6] = SkPoint::Make(L, B - rrect.radii(SkRRect::kLowerLeft_Corner).fY);
951 fPts[7] = SkPoint::Make(L, T + rrect.radii(SkRRect::kUpperLeft_Corner).fY);
952 }
953};
954
955} // anonymous namespace
956
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000957static void assert_known_direction(int dir) {
958 SkASSERT(SkPath::kCW_Direction == dir || SkPath::kCCW_Direction == dir);
959}
960
reed@android.com8a1c16f2008-12-17 15:59:43 +0000961void SkPath::addRect(const SkRect& rect, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -0800962 this->addRect(rect, dir, 0);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000963}
964
965void SkPath::addRect(SkScalar left, SkScalar top, SkScalar right,
966 SkScalar bottom, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -0800967 this->addRect(SkRect::MakeLTRB(left, top, right, bottom), dir, 0);
968}
969
970void SkPath::addRect(const SkRect &rect, Direction dir, unsigned startIndex) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000971 assert_known_direction(dir);
reed026beb52015-06-10 14:23:15 -0700972 fFirstDirection = this->hasOnlyMoveTos() ?
fmalitac08d53e2015-11-17 09:53:29 -0800973 (SkPathPriv::FirstDirection)dir : SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000974 SkAutoDisableDirectionCheck addc(this);
fmalitac08d53e2015-11-17 09:53:29 -0800975 SkAutoPathBoundsUpdate apbu(this, rect);
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000976
fmalitac08d53e2015-11-17 09:53:29 -0800977 SkDEBUGCODE(int initialVerbCount = this->countVerbs());
reed@android.com8a1c16f2008-12-17 15:59:43 +0000978
fmalitac08d53e2015-11-17 09:53:29 -0800979 const int kVerbs = 5; // moveTo + 3x lineTo + close
980 this->incReserve(kVerbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000981
fmalitac08d53e2015-11-17 09:53:29 -0800982 RectPointIterator iter(rect, dir, startIndex);
983
984 this->moveTo(iter.current());
985 this->lineTo(iter.next());
986 this->lineTo(iter.next());
987 this->lineTo(iter.next());
reed@android.com8a1c16f2008-12-17 15:59:43 +0000988 this->close();
fmalitac08d53e2015-11-17 09:53:29 -0800989
990 SkASSERT(this->countVerbs() == initialVerbCount + kVerbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000991}
992
reed@google.com744faba2012-05-29 19:54:52 +0000993void SkPath::addPoly(const SkPoint pts[], int count, bool close) {
994 SkDEBUGCODE(this->validate();)
995 if (count <= 0) {
996 return;
997 }
998
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000999 fLastMoveToIndex = fPathRef->countPoints();
1000
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001001 // +close makes room for the extra kClose_Verb
1002 SkPathRef::Editor ed(&fPathRef, count+close, count);
1003
1004 ed.growForVerb(kMove_Verb)->set(pts[0].fX, pts[0].fY);
reed@google.com744faba2012-05-29 19:54:52 +00001005 if (count > 1) {
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001006 SkPoint* p = ed.growForRepeatedVerb(kLine_Verb, count - 1);
1007 memcpy(p, &pts[1], (count-1) * sizeof(SkPoint));
reed@google.com744faba2012-05-29 19:54:52 +00001008 }
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001009
reed@google.com744faba2012-05-29 19:54:52 +00001010 if (close) {
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001011 ed.growForVerb(kClose_Verb);
caryclark63c684a2015-02-25 09:04:04 -08001012 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
reed@google.com744faba2012-05-29 19:54:52 +00001013 }
1014
reed@google.com744faba2012-05-29 19:54:52 +00001015 DIRTY_AFTER_EDIT;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001016 SkDEBUGCODE(this->validate();)
reed@google.com744faba2012-05-29 19:54:52 +00001017}
1018
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001019#include "SkGeometry.h"
1020
reedf90ea012015-01-29 12:03:58 -08001021static bool arc_is_lone_point(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
1022 SkPoint* pt) {
1023 if (0 == sweepAngle && (0 == startAngle || SkIntToScalar(360) == startAngle)) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001024 // Chrome uses this path to move into and out of ovals. If not
1025 // treated as a special case the moves can distort the oval's
1026 // bounding box (and break the circle special case).
reedf90ea012015-01-29 12:03:58 -08001027 pt->set(oval.fRight, oval.centerY());
1028 return true;
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001029 } else if (0 == oval.width() && 0 == oval.height()) {
1030 // Chrome will sometimes create 0 radius round rects. Having degenerate
1031 // quad segments in the path prevents the path from being recognized as
1032 // a rect.
1033 // TODO: optimizing the case where only one of width or height is zero
1034 // should also be considered. This case, however, doesn't seem to be
1035 // as common as the single point case.
reedf90ea012015-01-29 12:03:58 -08001036 pt->set(oval.fRight, oval.fTop);
1037 return true;
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001038 }
reedf90ea012015-01-29 12:03:58 -08001039 return false;
1040}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001041
reedd5d27d92015-02-09 13:54:43 -08001042// Return the unit vectors pointing at the start/stop points for the given start/sweep angles
1043//
1044static void angles_to_unit_vectors(SkScalar startAngle, SkScalar sweepAngle,
1045 SkVector* startV, SkVector* stopV, SkRotationDirection* dir) {
1046 startV->fY = SkScalarSinCos(SkDegreesToRadians(startAngle), &startV->fX);
1047 stopV->fY = SkScalarSinCos(SkDegreesToRadians(startAngle + sweepAngle), &stopV->fX);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001048
1049 /* If the sweep angle is nearly (but less than) 360, then due to precision
reedd5d27d92015-02-09 13:54:43 -08001050 loss in radians-conversion and/or sin/cos, we may end up with coincident
1051 vectors, which will fool SkBuildQuadArc into doing nothing (bad) instead
1052 of drawing a nearly complete circle (good).
1053 e.g. canvas.drawArc(0, 359.99, ...)
1054 -vs- canvas.drawArc(0, 359.9, ...)
1055 We try to detect this edge case, and tweak the stop vector
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001056 */
reedd5d27d92015-02-09 13:54:43 -08001057 if (*startV == *stopV) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001058 SkScalar sw = SkScalarAbs(sweepAngle);
1059 if (sw < SkIntToScalar(360) && sw > SkIntToScalar(359)) {
1060 SkScalar stopRad = SkDegreesToRadians(startAngle + sweepAngle);
1061 // make a guess at a tiny angle (in radians) to tweak by
1062 SkScalar deltaRad = SkScalarCopySign(SK_Scalar1/512, sweepAngle);
1063 // not sure how much will be enough, so we use a loop
1064 do {
1065 stopRad -= deltaRad;
reedd5d27d92015-02-09 13:54:43 -08001066 stopV->fY = SkScalarSinCos(stopRad, &stopV->fX);
1067 } while (*startV == *stopV);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001068 }
1069 }
reedd5d27d92015-02-09 13:54:43 -08001070 *dir = sweepAngle > 0 ? kCW_SkRotationDirection : kCCW_SkRotationDirection;
1071}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001072
reed9e779d42015-02-17 11:43:14 -08001073/**
1074 * If this returns 0, then the caller should just line-to the singlePt, else it should
1075 * ignore singlePt and append the specified number of conics.
1076 */
reedd5d27d92015-02-09 13:54:43 -08001077static int build_arc_conics(const SkRect& oval, const SkVector& start, const SkVector& stop,
reed9e779d42015-02-17 11:43:14 -08001078 SkRotationDirection dir, SkConic conics[SkConic::kMaxConicsForArc],
1079 SkPoint* singlePt) {
reedd5d27d92015-02-09 13:54:43 -08001080 SkMatrix matrix;
1081
1082 matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height()));
1083 matrix.postTranslate(oval.centerX(), oval.centerY());
1084
reed9e779d42015-02-17 11:43:14 -08001085 int count = SkConic::BuildUnitArc(start, stop, dir, &matrix, conics);
1086 if (0 == count) {
1087 matrix.mapXY(start.x(), start.y(), singlePt);
1088 }
1089 return count;
reedd5d27d92015-02-09 13:54:43 -08001090}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001091
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001092void SkPath::addRoundRect(const SkRect& rect, const SkScalar radii[],
reed@android.com8a1c16f2008-12-17 15:59:43 +00001093 Direction dir) {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001094 SkRRect rrect;
1095 rrect.setRectRadii(rect, (const SkVector*) radii);
1096 this->addRRect(rrect, dir);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001097}
1098
reed@google.com4ed0fb72012-12-12 20:48:18 +00001099void SkPath::addRRect(const SkRRect& rrect, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -08001100 // legacy start indices: 6 (CW) and 7(CCW)
1101 this->addRRect(rrect, dir, dir == kCW_Direction ? 6 : 7);
1102}
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001103
fmalitac08d53e2015-11-17 09:53:29 -08001104void SkPath::addRRect(const SkRRect &rrect, Direction dir, unsigned startIndex) {
1105 assert_known_direction(dir);
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001106
fmalitac08d53e2015-11-17 09:53:29 -08001107 if (rrect.isEmpty()) {
1108 return;
reed1b28a3a2014-12-17 14:42:09 -08001109 }
fmalitac08d53e2015-11-17 09:53:29 -08001110
caryclarkda707bf2015-11-19 14:47:43 -08001111 bool isRRect = hasOnlyMoveTos();
fmalitac08d53e2015-11-17 09:53:29 -08001112 const SkRect& bounds = rrect.getBounds();
1113
1114 if (rrect.isRect()) {
1115 // degenerate(rect) => radii points are collapsing
1116 this->addRect(bounds, dir, (startIndex + 1) / 2);
1117 } else if (rrect.isOval()) {
1118 // degenerate(oval) => line points are collapsing
1119 this->addOval(bounds, dir, startIndex / 2);
1120 } else {
1121 fFirstDirection = this->hasOnlyMoveTos() ?
1122 (SkPathPriv::FirstDirection)dir : SkPathPriv::kUnknown_FirstDirection;
1123
1124 SkAutoPathBoundsUpdate apbu(this, bounds);
1125 SkAutoDisableDirectionCheck addc(this);
1126
1127 // we start with a conic on odd indices when moving CW vs. even indices when moving CCW
1128 const bool startsWithConic = ((startIndex & 1) == (dir == kCW_Direction));
1129 const SkScalar weight = SK_ScalarRoot2Over2;
1130
1131 SkDEBUGCODE(int initialVerbCount = this->countVerbs());
1132 const int kVerbs = startsWithConic
1133 ? 9 // moveTo + 4x conicTo + 3x lineTo + close
1134 : 10; // moveTo + 4x lineTo + 4x conicTo + close
1135 this->incReserve(kVerbs);
1136
1137 RRectPointIterator rrectIter(rrect, dir, startIndex);
1138 // Corner iterator indices follow the collapsed radii model,
1139 // adjusted such that the start pt is "behind" the radii start pt.
1140 const unsigned rectStartIndex = startIndex / 2 + (dir == kCW_Direction ? 0 : 1);
1141 RectPointIterator rectIter(bounds, dir, rectStartIndex);
1142
1143 this->moveTo(rrectIter.current());
1144 if (startsWithConic) {
1145 for (unsigned i = 0; i < 3; ++i) {
1146 this->conicTo(rectIter.next(), rrectIter.next(), weight);
1147 this->lineTo(rrectIter.next());
1148 }
1149 this->conicTo(rectIter.next(), rrectIter.next(), weight);
1150 // final lineTo handled by close().
1151 } else {
1152 for (unsigned i = 0; i < 4; ++i) {
1153 this->lineTo(rrectIter.next());
1154 this->conicTo(rectIter.next(), rrectIter.next(), weight);
1155 }
1156 }
1157 this->close();
1158
caryclarkda707bf2015-11-19 14:47:43 -08001159 SkPathRef::Editor ed(&fPathRef);
1160 ed.setIsRRect(isRRect);
1161
fmalitac08d53e2015-11-17 09:53:29 -08001162 SkASSERT(this->countVerbs() == initialVerbCount + kVerbs);
1163 }
1164
1165 SkDEBUGCODE(fPathRef->validate();)
reed@google.com4ed0fb72012-12-12 20:48:18 +00001166}
1167
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001168bool SkPath::hasOnlyMoveTos() const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001169 int count = fPathRef->countVerbs();
1170 const uint8_t* verbs = const_cast<const SkPathRef*>(fPathRef.get())->verbsMemBegin();
1171 for (int i = 0; i < count; ++i) {
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001172 if (*verbs == kLine_Verb ||
1173 *verbs == kQuad_Verb ||
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001174 *verbs == kConic_Verb ||
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001175 *verbs == kCubic_Verb) {
1176 return false;
1177 }
1178 ++verbs;
1179 }
1180 return true;
1181}
1182
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001183void SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
1184 Direction dir) {
1185 assert_known_direction(dir);
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001186
humper@google.com75e3ca12013-04-08 21:44:11 +00001187 if (rx < 0 || ry < 0) {
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001188 SkErrorInternals::SetError( kInvalidArgument_SkError,
humper@google.com75e3ca12013-04-08 21:44:11 +00001189 "I got %f and %f as radii to SkPath::AddRoundRect, "
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001190 "but negative radii are not allowed.",
humper@google.com75e3ca12013-04-08 21:44:11 +00001191 SkScalarToDouble(rx), SkScalarToDouble(ry) );
1192 return;
1193 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001194
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001195 SkRRect rrect;
1196 rrect.setRectXY(rect, rx, ry);
1197 this->addRRect(rrect, dir);
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001198}
1199
reed@android.com8a1c16f2008-12-17 15:59:43 +00001200void SkPath::addOval(const SkRect& oval, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -08001201 // legacy start index: 1
1202 this->addOval(oval, dir, 1);
1203}
1204
1205void SkPath::addOval(const SkRect &oval, Direction dir, unsigned startPointIndex) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +00001206 assert_known_direction(dir);
1207
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001208 /* If addOval() is called after previous moveTo(),
1209 this path is still marked as an oval. This is used to
1210 fit into WebKit's calling sequences.
1211 We can't simply check isEmpty() in this case, as additional
1212 moveTo() would mark the path non empty.
1213 */
robertphillips@google.com466310d2013-12-03 16:43:54 +00001214 bool isOval = hasOnlyMoveTos();
1215 if (isOval) {
reed026beb52015-06-10 14:23:15 -07001216 fFirstDirection = (SkPathPriv::FirstDirection)dir;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001217 } else {
reed026beb52015-06-10 14:23:15 -07001218 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001219 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001220
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001221 SkAutoDisableDirectionCheck addc(this);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001222 SkAutoPathBoundsUpdate apbu(this, oval);
1223
fmalitac08d53e2015-11-17 09:53:29 -08001224 SkDEBUGCODE(int initialVerbCount = this->countVerbs());
1225 const int kVerbs = 6; // moveTo + 4x conicTo + close
1226 this->incReserve(kVerbs);
1227
1228 OvalPointIterator ovalIter(oval, dir, startPointIndex);
1229 // The corner iterator pts are tracking "behind" the oval/radii pts.
1230 RectPointIterator rectIter(oval, dir, startPointIndex + (dir == kCW_Direction ? 0 : 1));
reed220f9262014-12-17 08:21:04 -08001231 const SkScalar weight = SK_ScalarRoot2Over2;
1232
fmalitac08d53e2015-11-17 09:53:29 -08001233 this->moveTo(ovalIter.current());
1234 for (unsigned i = 0; i < 4; ++i) {
1235 this->conicTo(rectIter.next(), ovalIter.next(), weight);
reed220f9262014-12-17 08:21:04 -08001236 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001237 this->close();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001238
fmalitac08d53e2015-11-17 09:53:29 -08001239 SkASSERT(this->countVerbs() == initialVerbCount + kVerbs);
1240
robertphillips@google.com466310d2013-12-03 16:43:54 +00001241 SkPathRef::Editor ed(&fPathRef);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001242
robertphillips@google.com466310d2013-12-03 16:43:54 +00001243 ed.setIsOval(isOval);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001244}
1245
reed@android.com8a1c16f2008-12-17 15:59:43 +00001246void SkPath::addCircle(SkScalar x, SkScalar y, SkScalar r, Direction dir) {
1247 if (r > 0) {
fmalitac08d53e2015-11-17 09:53:29 -08001248 this->addOval(SkRect::MakeLTRB(x - r, y - r, x + r, y + r), dir);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001249 }
1250}
1251
reed@android.com8a1c16f2008-12-17 15:59:43 +00001252void SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
1253 bool forceMoveTo) {
1254 if (oval.width() < 0 || oval.height() < 0) {
1255 return;
1256 }
1257
reedf90ea012015-01-29 12:03:58 -08001258 if (fPathRef->countVerbs() == 0) {
1259 forceMoveTo = true;
1260 }
1261
1262 SkPoint lonePt;
1263 if (arc_is_lone_point(oval, startAngle, sweepAngle, &lonePt)) {
1264 forceMoveTo ? this->moveTo(lonePt) : this->lineTo(lonePt);
1265 return;
1266 }
1267
reedd5d27d92015-02-09 13:54:43 -08001268 SkVector startV, stopV;
1269 SkRotationDirection dir;
1270 angles_to_unit_vectors(startAngle, sweepAngle, &startV, &stopV, &dir);
1271
reed9e779d42015-02-17 11:43:14 -08001272 SkPoint singlePt;
reedd5d27d92015-02-09 13:54:43 -08001273 SkConic conics[SkConic::kMaxConicsForArc];
reed9e779d42015-02-17 11:43:14 -08001274 int count = build_arc_conics(oval, startV, stopV, dir, conics, &singlePt);
reedd5d27d92015-02-09 13:54:43 -08001275 if (count) {
1276 this->incReserve(count * 2 + 1);
1277 const SkPoint& pt = conics[0].fPts[0];
1278 forceMoveTo ? this->moveTo(pt) : this->lineTo(pt);
1279 for (int i = 0; i < count; ++i) {
1280 this->conicTo(conics[i].fPts[1], conics[i].fPts[2], conics[i].fW);
1281 }
reed9e779d42015-02-17 11:43:14 -08001282 } else {
1283 forceMoveTo ? this->moveTo(singlePt) : this->lineTo(singlePt);
reedd5d27d92015-02-09 13:54:43 -08001284 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001285}
1286
caryclark55d49052016-01-23 05:07:04 -08001287// This converts the SVG arc to conics.
1288// Partly adapted from Niko's code in kdelibs/kdecore/svgicons.
1289// Then transcribed from webkit/chrome's SVGPathNormalizer::decomposeArcToCubic()
1290// See also SVG implementation notes:
1291// http://www.w3.org/TR/SVG/implnote.html#ArcConversionEndpointToCenter
1292// Note that arcSweep bool value is flipped from the original implementation.
1293void SkPath::arcTo(SkScalar rx, SkScalar ry, SkScalar angle, SkPath::ArcSize arcLarge,
1294 SkPath::Direction arcSweep, SkScalar x, SkScalar y) {
caryclarkf1d41512016-02-09 10:30:22 -08001295 this->injectMoveToIfNeeded();
caryclark55d49052016-01-23 05:07:04 -08001296 SkPoint srcPts[2];
1297 this->getLastPt(&srcPts[0]);
1298 // If rx = 0 or ry = 0 then this arc is treated as a straight line segment (a "lineto")
1299 // joining the endpoints.
1300 // http://www.w3.org/TR/SVG/implnote.html#ArcOutOfRangeParameters
1301 if (!rx || !ry) {
caryclarkfc752532016-01-25 05:39:04 -08001302 this->lineTo(x, y);
caryclark55d49052016-01-23 05:07:04 -08001303 return;
1304 }
1305 // If the current point and target point for the arc are identical, it should be treated as a
1306 // zero length path. This ensures continuity in animations.
1307 srcPts[1].set(x, y);
1308 if (srcPts[0] == srcPts[1]) {
caryclarkfc752532016-01-25 05:39:04 -08001309 this->lineTo(x, y);
caryclark55d49052016-01-23 05:07:04 -08001310 return;
1311 }
1312 rx = SkScalarAbs(rx);
1313 ry = SkScalarAbs(ry);
1314 SkVector midPointDistance = srcPts[0] - srcPts[1];
1315 midPointDistance *= 0.5f;
1316
1317 SkMatrix pointTransform;
1318 pointTransform.setRotate(-angle);
1319
1320 SkPoint transformedMidPoint;
1321 pointTransform.mapPoints(&transformedMidPoint, &midPointDistance, 1);
1322 SkScalar squareRx = rx * rx;
1323 SkScalar squareRy = ry * ry;
1324 SkScalar squareX = transformedMidPoint.fX * transformedMidPoint.fX;
1325 SkScalar squareY = transformedMidPoint.fY * transformedMidPoint.fY;
1326
1327 // Check if the radii are big enough to draw the arc, scale radii if not.
1328 // http://www.w3.org/TR/SVG/implnote.html#ArcCorrectionOutOfRangeRadii
1329 SkScalar radiiScale = squareX / squareRx + squareY / squareRy;
1330 if (radiiScale > 1) {
1331 radiiScale = SkScalarSqrt(radiiScale);
1332 rx *= radiiScale;
1333 ry *= radiiScale;
1334 }
1335
1336 pointTransform.setScale(1 / rx, 1 / ry);
1337 pointTransform.preRotate(-angle);
1338
1339 SkPoint unitPts[2];
1340 pointTransform.mapPoints(unitPts, srcPts, (int) SK_ARRAY_COUNT(unitPts));
1341 SkVector delta = unitPts[1] - unitPts[0];
1342
1343 SkScalar d = delta.fX * delta.fX + delta.fY * delta.fY;
1344 SkScalar scaleFactorSquared = SkTMax(1 / d - 0.25f, 0.f);
1345
1346 SkScalar scaleFactor = SkScalarSqrt(scaleFactorSquared);
1347 if (SkToBool(arcSweep) != SkToBool(arcLarge)) { // flipped from the original implementation
1348 scaleFactor = -scaleFactor;
1349 }
1350 delta.scale(scaleFactor);
1351 SkPoint centerPoint = unitPts[0] + unitPts[1];
1352 centerPoint *= 0.5f;
1353 centerPoint.offset(-delta.fY, delta.fX);
1354 unitPts[0] -= centerPoint;
1355 unitPts[1] -= centerPoint;
1356 SkScalar theta1 = SkScalarATan2(unitPts[0].fY, unitPts[0].fX);
1357 SkScalar theta2 = SkScalarATan2(unitPts[1].fY, unitPts[1].fX);
1358 SkScalar thetaArc = theta2 - theta1;
1359 if (thetaArc < 0 && !arcSweep) { // arcSweep flipped from the original implementation
1360 thetaArc += SK_ScalarPI * 2;
1361 } else if (thetaArc > 0 && arcSweep) { // arcSweep flipped from the original implementation
1362 thetaArc -= SK_ScalarPI * 2;
1363 }
1364 pointTransform.setRotate(angle);
1365 pointTransform.preScale(rx, ry);
1366
1367 int segments = SkScalarCeilToInt(SkScalarAbs(thetaArc / (SK_ScalarPI / 2)));
1368 SkScalar thetaWidth = thetaArc / segments;
1369 SkScalar t = SkScalarTan(0.5f * thetaWidth);
1370 if (!SkScalarIsFinite(t)) {
1371 return;
1372 }
1373 SkScalar startTheta = theta1;
1374 SkScalar w = SkScalarSqrt(SK_ScalarHalf + SkScalarCos(thetaWidth) * SK_ScalarHalf);
1375 for (int i = 0; i < segments; ++i) {
1376 SkScalar endTheta = startTheta + thetaWidth;
1377 SkScalar cosEndTheta, sinEndTheta = SkScalarSinCos(endTheta, &cosEndTheta);
1378
1379 unitPts[1].set(cosEndTheta, sinEndTheta);
1380 unitPts[1] += centerPoint;
1381 unitPts[0] = unitPts[1];
1382 unitPts[0].offset(t * sinEndTheta, -t * cosEndTheta);
1383 SkPoint mapped[2];
1384 pointTransform.mapPoints(mapped, unitPts, (int) SK_ARRAY_COUNT(unitPts));
1385 this->conicTo(mapped[0], mapped[1], w);
1386 startTheta = endTheta;
1387 }
1388}
1389
1390void SkPath::rArcTo(SkScalar rx, SkScalar ry, SkScalar xAxisRotate, SkPath::ArcSize largeArc,
1391 SkPath::Direction sweep, SkScalar dx, SkScalar dy) {
1392 SkPoint currentPoint;
1393 this->getLastPt(&currentPoint);
1394 this->arcTo(rx, ry, xAxisRotate, largeArc, sweep, currentPoint.fX + dx, currentPoint.fY + dy);
1395}
1396
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001397void SkPath::addArc(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001398 if (oval.isEmpty() || 0 == sweepAngle) {
1399 return;
1400 }
1401
1402 const SkScalar kFullCircleAngle = SkIntToScalar(360);
1403
1404 if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
1405 this->addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction);
reedc7789042015-01-29 12:59:11 -08001406 } else {
1407 this->arcTo(oval, startAngle, sweepAngle, true);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001408 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001409}
1410
1411/*
1412 Need to handle the case when the angle is sharp, and our computed end-points
1413 for the arc go behind pt1 and/or p2...
1414*/
reedc7789042015-01-29 12:59:11 -08001415void SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2, SkScalar radius) {
reeda8b326c2014-12-09 11:50:32 -08001416 if (radius == 0) {
1417 this->lineTo(x1, y1);
1418 return;
1419 }
1420
1421 SkVector before, after;
reed@google.comabf15c12011-01-18 20:35:51 +00001422
reed@android.com8a1c16f2008-12-17 15:59:43 +00001423 // need to know our prev pt so we can construct tangent vectors
1424 {
1425 SkPoint start;
1426 this->getLastPt(&start);
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001427 // Handle degenerate cases by adding a line to the first point and
1428 // bailing out.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001429 before.setNormalize(x1 - start.fX, y1 - start.fY);
1430 after.setNormalize(x2 - x1, y2 - y1);
1431 }
reed@google.comabf15c12011-01-18 20:35:51 +00001432
reed@android.com8a1c16f2008-12-17 15:59:43 +00001433 SkScalar cosh = SkPoint::DotProduct(before, after);
1434 SkScalar sinh = SkPoint::CrossProduct(before, after);
1435
1436 if (SkScalarNearlyZero(sinh)) { // angle is too tight
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001437 this->lineTo(x1, y1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001438 return;
1439 }
reed@google.comabf15c12011-01-18 20:35:51 +00001440
caryclark88651ae2016-01-20 11:55:11 -08001441 SkScalar dist = SkScalarAbs(SkScalarMulDiv(radius, SK_Scalar1 - cosh, sinh));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001442
1443 SkScalar xx = x1 - SkScalarMul(dist, before.fX);
1444 SkScalar yy = y1 - SkScalarMul(dist, before.fY);
caryclark88651ae2016-01-20 11:55:11 -08001445 after.setLength(dist);
1446 this->lineTo(xx, yy);
1447 SkScalar weight = SkScalarSqrt(SK_ScalarHalf + cosh * SK_ScalarHalf);
1448 this->conicTo(x1, y1, x1 + after.fX, y1 + after.fY, weight);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001449}
1450
1451///////////////////////////////////////////////////////////////////////////////
1452
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001453void SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy, AddPathMode mode) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001454 SkMatrix matrix;
1455
1456 matrix.setTranslate(dx, dy);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001457 this->addPath(path, matrix, mode);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001458}
1459
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001460void SkPath::addPath(const SkPath& path, const SkMatrix& matrix, AddPathMode mode) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001461 SkPathRef::Editor(&fPathRef, path.countVerbs(), path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001462
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001463 RawIter iter(path);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001464 SkPoint pts[4];
1465 Verb verb;
1466
1467 SkMatrix::MapPtsProc proc = matrix.getMapPtsProc();
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001468 bool firstVerb = true;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001469 while ((verb = iter.next(pts)) != kDone_Verb) {
1470 switch (verb) {
1471 case kMove_Verb:
1472 proc(matrix, &pts[0], &pts[0], 1);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001473 if (firstVerb && mode == kExtend_AddPathMode && !isEmpty()) {
1474 injectMoveToIfNeeded(); // In case last contour is closed
1475 this->lineTo(pts[0]);
1476 } else {
1477 this->moveTo(pts[0]);
1478 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001479 break;
1480 case kLine_Verb:
1481 proc(matrix, &pts[1], &pts[1], 1);
1482 this->lineTo(pts[1]);
1483 break;
1484 case kQuad_Verb:
1485 proc(matrix, &pts[1], &pts[1], 2);
1486 this->quadTo(pts[1], pts[2]);
1487 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001488 case kConic_Verb:
1489 proc(matrix, &pts[1], &pts[1], 2);
1490 this->conicTo(pts[1], pts[2], iter.conicWeight());
1491 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001492 case kCubic_Verb:
1493 proc(matrix, &pts[1], &pts[1], 3);
1494 this->cubicTo(pts[1], pts[2], pts[3]);
1495 break;
1496 case kClose_Verb:
1497 this->close();
1498 break;
1499 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001500 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001501 }
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001502 firstVerb = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001503 }
1504}
1505
1506///////////////////////////////////////////////////////////////////////////////
1507
reed@google.com277c3f82013-05-31 15:17:50 +00001508static int pts_in_verb(unsigned verb) {
1509 static const uint8_t gPtsInVerb[] = {
1510 1, // kMove
1511 1, // kLine
1512 2, // kQuad
1513 2, // kConic
1514 3, // kCubic
1515 0, // kClose
1516 0 // kDone
1517 };
1518
1519 SkASSERT(verb < SK_ARRAY_COUNT(gPtsInVerb));
1520 return gPtsInVerb[verb];
1521}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001522
reed@android.com8a1c16f2008-12-17 15:59:43 +00001523// ignore the last point of the 1st contour
1524void SkPath::reversePathTo(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001525 int i, vcount = path.fPathRef->countVerbs();
1526 // exit early if the path is empty, or just has a moveTo.
1527 if (vcount < 2) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001528 return;
1529 }
1530
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001531 SkPathRef::Editor(&fPathRef, vcount, path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001532
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001533 const uint8_t* verbs = path.fPathRef->verbs();
1534 const SkPoint* pts = path.fPathRef->points();
reed@google.com277c3f82013-05-31 15:17:50 +00001535 const SkScalar* conicWeights = path.fPathRef->conicWeights();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001536
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001537 SkASSERT(verbs[~0] == kMove_Verb);
1538 for (i = 1; i < vcount; ++i) {
reed@google.com277c3f82013-05-31 15:17:50 +00001539 unsigned v = verbs[~i];
1540 int n = pts_in_verb(v);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001541 if (n == 0) {
1542 break;
1543 }
1544 pts += n;
reed@google.com277c3f82013-05-31 15:17:50 +00001545 conicWeights += (SkPath::kConic_Verb == v);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001546 }
1547
1548 while (--i > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001549 switch (verbs[~i]) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001550 case kLine_Verb:
1551 this->lineTo(pts[-1].fX, pts[-1].fY);
1552 break;
1553 case kQuad_Verb:
1554 this->quadTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY);
1555 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001556 case kConic_Verb:
1557 this->conicTo(pts[-1], pts[-2], *--conicWeights);
1558 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001559 case kCubic_Verb:
1560 this->cubicTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY,
1561 pts[-3].fX, pts[-3].fY);
1562 break;
1563 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001564 SkDEBUGFAIL("bad verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001565 break;
1566 }
reed@google.com277c3f82013-05-31 15:17:50 +00001567 pts -= pts_in_verb(verbs[~i]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001568 }
1569}
1570
reed@google.com63d73742012-01-10 15:33:12 +00001571void SkPath::reverseAddPath(const SkPath& src) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001572 SkPathRef::Editor ed(&fPathRef, src.fPathRef->countPoints(), src.fPathRef->countVerbs());
reed@google.com63d73742012-01-10 15:33:12 +00001573
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001574 const SkPoint* pts = src.fPathRef->pointsEnd();
1575 // we will iterator through src's verbs backwards
1576 const uint8_t* verbs = src.fPathRef->verbsMemBegin(); // points at the last verb
1577 const uint8_t* verbsEnd = src.fPathRef->verbs(); // points just past the first verb
reed@google.com277c3f82013-05-31 15:17:50 +00001578 const SkScalar* conicWeights = src.fPathRef->conicWeightsEnd();
reed@google.com63d73742012-01-10 15:33:12 +00001579
1580 bool needMove = true;
1581 bool needClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001582 while (verbs < verbsEnd) {
1583 uint8_t v = *(verbs++);
reed@google.com277c3f82013-05-31 15:17:50 +00001584 int n = pts_in_verb(v);
reed@google.com63d73742012-01-10 15:33:12 +00001585
1586 if (needMove) {
1587 --pts;
1588 this->moveTo(pts->fX, pts->fY);
1589 needMove = false;
1590 }
1591 pts -= n;
1592 switch (v) {
1593 case kMove_Verb:
1594 if (needClose) {
1595 this->close();
1596 needClose = false;
1597 }
1598 needMove = true;
1599 pts += 1; // so we see the point in "if (needMove)" above
1600 break;
1601 case kLine_Verb:
1602 this->lineTo(pts[0]);
1603 break;
1604 case kQuad_Verb:
1605 this->quadTo(pts[1], pts[0]);
1606 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001607 case kConic_Verb:
1608 this->conicTo(pts[1], pts[0], *--conicWeights);
1609 break;
reed@google.com63d73742012-01-10 15:33:12 +00001610 case kCubic_Verb:
1611 this->cubicTo(pts[2], pts[1], pts[0]);
1612 break;
1613 case kClose_Verb:
1614 needClose = true;
1615 break;
1616 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00001617 SkDEBUGFAIL("unexpected verb");
reed@google.com63d73742012-01-10 15:33:12 +00001618 }
1619 }
1620}
1621
reed@android.com8a1c16f2008-12-17 15:59:43 +00001622///////////////////////////////////////////////////////////////////////////////
1623
1624void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
1625 SkMatrix matrix;
1626
1627 matrix.setTranslate(dx, dy);
1628 this->transform(matrix, dst);
1629}
1630
reed@android.com8a1c16f2008-12-17 15:59:43 +00001631static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
1632 int level = 2) {
1633 if (--level >= 0) {
1634 SkPoint tmp[7];
1635
1636 SkChopCubicAtHalf(pts, tmp);
1637 subdivide_cubic_to(path, &tmp[0], level);
1638 subdivide_cubic_to(path, &tmp[3], level);
1639 } else {
1640 path->cubicTo(pts[1], pts[2], pts[3]);
1641 }
1642}
1643
1644void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
1645 SkDEBUGCODE(this->validate();)
halcanary96fcdcc2015-08-27 07:41:13 -07001646 if (dst == nullptr) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001647 dst = (SkPath*)this;
1648 }
1649
tomhudson@google.com8d430182011-06-06 19:11:19 +00001650 if (matrix.hasPerspective()) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001651 SkPath tmp;
1652 tmp.fFillType = fFillType;
1653
1654 SkPath::Iter iter(*this, false);
1655 SkPoint pts[4];
1656 SkPath::Verb verb;
1657
reed@google.com4a3b7142012-05-16 17:16:46 +00001658 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001659 switch (verb) {
1660 case kMove_Verb:
1661 tmp.moveTo(pts[0]);
1662 break;
1663 case kLine_Verb:
1664 tmp.lineTo(pts[1]);
1665 break;
1666 case kQuad_Verb:
reed220f9262014-12-17 08:21:04 -08001667 // promote the quad to a conic
1668 tmp.conicTo(pts[1], pts[2],
1669 SkConic::TransformW(pts, SK_Scalar1, matrix));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001670 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001671 case kConic_Verb:
reed220f9262014-12-17 08:21:04 -08001672 tmp.conicTo(pts[1], pts[2],
1673 SkConic::TransformW(pts, iter.conicWeight(), matrix));
reed@google.com277c3f82013-05-31 15:17:50 +00001674 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001675 case kCubic_Verb:
1676 subdivide_cubic_to(&tmp, pts);
1677 break;
1678 case kClose_Verb:
1679 tmp.close();
1680 break;
1681 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001682 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001683 break;
1684 }
1685 }
1686
1687 dst->swap(tmp);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001688 SkPathRef::Editor ed(&dst->fPathRef);
1689 matrix.mapPoints(ed.points(), ed.pathRef()->countPoints());
reed026beb52015-06-10 14:23:15 -07001690 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001691 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001692 SkPathRef::CreateTransformedCopy(&dst->fPathRef, *fPathRef.get(), matrix);
1693
reed@android.com8a1c16f2008-12-17 15:59:43 +00001694 if (this != dst) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001695 dst->fFillType = fFillType;
reed@google.com2a6f8ab2011-10-25 18:41:23 +00001696 dst->fConvexity = fConvexity;
jvanverthb3eb6872014-10-24 07:12:51 -07001697 dst->fIsVolatile = fIsVolatile;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001698 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001699
reed026beb52015-06-10 14:23:15 -07001700 if (SkPathPriv::kUnknown_FirstDirection == fFirstDirection) {
1701 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001702 } else {
1703 SkScalar det2x2 =
1704 SkScalarMul(matrix.get(SkMatrix::kMScaleX), matrix.get(SkMatrix::kMScaleY)) -
1705 SkScalarMul(matrix.get(SkMatrix::kMSkewX), matrix.get(SkMatrix::kMSkewY));
1706 if (det2x2 < 0) {
herb9f4dbca2015-09-28 11:05:47 -07001707 dst->fFirstDirection = SkPathPriv::OppositeFirstDirection(
1708 (SkPathPriv::FirstDirection)fFirstDirection.load());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001709 } else if (det2x2 > 0) {
herb9f4dbca2015-09-28 11:05:47 -07001710 dst->fFirstDirection = fFirstDirection.load();
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001711 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001712 dst->fConvexity = kUnknown_Convexity;
reed026beb52015-06-10 14:23:15 -07001713 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001714 }
1715 }
1716
reed@android.com8a1c16f2008-12-17 15:59:43 +00001717 SkDEBUGCODE(dst->validate();)
1718 }
1719}
1720
reed@android.com8a1c16f2008-12-17 15:59:43 +00001721///////////////////////////////////////////////////////////////////////////////
1722///////////////////////////////////////////////////////////////////////////////
1723
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001724enum SegmentState {
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001725 kEmptyContour_SegmentState, // The current contour is empty. We may be
1726 // starting processing or we may have just
1727 // closed a contour.
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001728 kAfterMove_SegmentState, // We have seen a move, but nothing else.
1729 kAfterPrimitive_SegmentState // We have seen a primitive but not yet
1730 // closed the path. Also the initial state.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001731};
1732
1733SkPath::Iter::Iter() {
1734#ifdef SK_DEBUG
halcanary96fcdcc2015-08-27 07:41:13 -07001735 fPts = nullptr;
1736 fConicWeights = nullptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001737 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001738 fForceClose = fCloseLine = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001739 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001740#endif
1741 // need to init enough to make next() harmlessly return kDone_Verb
halcanary96fcdcc2015-08-27 07:41:13 -07001742 fVerbs = nullptr;
1743 fVerbStop = nullptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001744 fNeedClose = false;
1745}
1746
1747SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
1748 this->setPath(path, forceClose);
1749}
1750
1751void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001752 fPts = path.fPathRef->points();
1753 fVerbs = path.fPathRef->verbs();
1754 fVerbStop = path.fPathRef->verbsMemBegin();
reed@google.com277c3f82013-05-31 15:17:50 +00001755 fConicWeights = path.fPathRef->conicWeights() - 1; // begin one behind
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001756 fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org72785c42011-12-29 21:03:28 +00001757 fMoveTo.fX = fMoveTo.fY = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001758 fForceClose = SkToU8(forceClose);
1759 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001760 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001761}
1762
1763bool SkPath::Iter::isClosedContour() const {
halcanary96fcdcc2015-08-27 07:41:13 -07001764 if (fVerbs == nullptr || fVerbs == fVerbStop) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001765 return false;
1766 }
1767 if (fForceClose) {
1768 return true;
1769 }
1770
1771 const uint8_t* verbs = fVerbs;
1772 const uint8_t* stop = fVerbStop;
reed@google.comabf15c12011-01-18 20:35:51 +00001773
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001774 if (kMove_Verb == *(verbs - 1)) {
1775 verbs -= 1; // skip the initial moveto
reed@android.com8a1c16f2008-12-17 15:59:43 +00001776 }
1777
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001778 while (verbs > stop) {
1779 // verbs points one beyond the current verb, decrement first.
1780 unsigned v = *(--verbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001781 if (kMove_Verb == v) {
1782 break;
1783 }
1784 if (kClose_Verb == v) {
1785 return true;
1786 }
1787 }
1788 return false;
1789}
1790
1791SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001792 SkASSERT(pts);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001793 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001794 // A special case: if both points are NaN, SkPoint::operation== returns
1795 // false, but the iterator expects that they are treated as the same.
1796 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001797 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1798 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001799 return kClose_Verb;
1800 }
1801
reed@google.com9e25dbf2012-05-15 17:05:38 +00001802 pts[0] = fLastPt;
1803 pts[1] = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001804 fLastPt = fMoveTo;
1805 fCloseLine = true;
1806 return kLine_Verb;
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +00001807 } else {
1808 pts[0] = fMoveTo;
1809 return kClose_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001810 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001811}
1812
reed@google.com9e25dbf2012-05-15 17:05:38 +00001813const SkPoint& SkPath::Iter::cons_moveTo() {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001814 if (fSegmentState == kAfterMove_SegmentState) {
1815 // Set the first return pt to the move pt
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001816 fSegmentState = kAfterPrimitive_SegmentState;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001817 return fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001818 } else {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001819 SkASSERT(fSegmentState == kAfterPrimitive_SegmentState);
1820 // Set the first return pt to the last pt of the previous primitive.
reed@google.com9e25dbf2012-05-15 17:05:38 +00001821 return fPts[-1];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001822 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001823}
1824
caryclarke8c56662015-07-14 11:19:26 -07001825void SkPath::Iter::consumeDegenerateSegments(bool exact) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001826 // We need to step over anything that will not move the current draw point
1827 // forward before the next move is seen
1828 const uint8_t* lastMoveVerb = 0;
1829 const SkPoint* lastMovePt = 0;
halcanary96fcdcc2015-08-27 07:41:13 -07001830 const SkScalar* lastMoveWeight = nullptr;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001831 SkPoint lastPt = fLastPt;
1832 while (fVerbs != fVerbStop) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001833 unsigned verb = *(fVerbs - 1); // fVerbs is one beyond the current verb
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001834 switch (verb) {
1835 case kMove_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001836 // Keep a record of this most recent move
1837 lastMoveVerb = fVerbs;
1838 lastMovePt = fPts;
robertphillipsb8de1f42015-02-23 11:17:01 -08001839 lastMoveWeight = fConicWeights;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001840 lastPt = fPts[0];
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001841 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001842 fPts++;
1843 break;
1844
1845 case kClose_Verb:
schenney@chromium.org7e963602012-06-13 17:05:43 +00001846 // A close when we are in a segment is always valid except when it
1847 // follows a move which follows a segment.
1848 if (fSegmentState == kAfterPrimitive_SegmentState && !lastMoveVerb) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001849 return;
1850 }
1851 // A close at any other time must be ignored
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001852 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001853 break;
1854
1855 case kLine_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001856 if (!IsLineDegenerate(lastPt, fPts[0], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001857 if (lastMoveVerb) {
1858 fVerbs = lastMoveVerb;
1859 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001860 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001861 return;
1862 }
1863 return;
1864 }
1865 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001866 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001867 fPts++;
1868 break;
1869
reed@google.com277c3f82013-05-31 15:17:50 +00001870 case kConic_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001871 case kQuad_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001872 if (!IsQuadDegenerate(lastPt, fPts[0], fPts[1], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001873 if (lastMoveVerb) {
1874 fVerbs = lastMoveVerb;
1875 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001876 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001877 return;
1878 }
1879 return;
1880 }
1881 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001882 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001883 fPts += 2;
reed@google.com277c3f82013-05-31 15:17:50 +00001884 fConicWeights += (kConic_Verb == verb);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001885 break;
1886
1887 case kCubic_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001888 if (!IsCubicDegenerate(lastPt, fPts[0], fPts[1], fPts[2], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001889 if (lastMoveVerb) {
1890 fVerbs = lastMoveVerb;
1891 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001892 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001893 return;
1894 }
1895 return;
1896 }
1897 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001898 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001899 fPts += 3;
1900 break;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001901
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001902 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001903 SkDEBUGFAIL("Should never see kDone_Verb");
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001904 }
1905 }
1906}
1907
reed@google.com4a3b7142012-05-16 17:16:46 +00001908SkPath::Verb SkPath::Iter::doNext(SkPoint ptsParam[4]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001909 SkASSERT(ptsParam);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001910
reed@android.com8a1c16f2008-12-17 15:59:43 +00001911 if (fVerbs == fVerbStop) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001912 // Close the curve if requested and if there is some curve to close
1913 if (fNeedClose && fSegmentState == kAfterPrimitive_SegmentState) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001914 if (kLine_Verb == this->autoClose(ptsParam)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001915 return kLine_Verb;
1916 }
1917 fNeedClose = false;
1918 return kClose_Verb;
1919 }
1920 return kDone_Verb;
1921 }
1922
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001923 // fVerbs is one beyond the current verb, decrement first
1924 unsigned verb = *(--fVerbs);
reed@google.com9e25dbf2012-05-15 17:05:38 +00001925 const SkPoint* SK_RESTRICT srcPts = fPts;
1926 SkPoint* SK_RESTRICT pts = ptsParam;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001927
1928 switch (verb) {
1929 case kMove_Verb:
1930 if (fNeedClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001931 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001932 verb = this->autoClose(pts);
1933 if (verb == kClose_Verb) {
1934 fNeedClose = false;
1935 }
1936 return (Verb)verb;
1937 }
1938 if (fVerbs == fVerbStop) { // might be a trailing moveto
1939 return kDone_Verb;
1940 }
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001941 fMoveTo = *srcPts;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001942 pts[0] = *srcPts;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001943 srcPts += 1;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001944 fSegmentState = kAfterMove_SegmentState;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001945 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001946 fNeedClose = fForceClose;
1947 break;
1948 case kLine_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001949 pts[0] = this->cons_moveTo();
1950 pts[1] = srcPts[0];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001951 fLastPt = srcPts[0];
1952 fCloseLine = false;
1953 srcPts += 1;
1954 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001955 case kConic_Verb:
1956 fConicWeights += 1;
1957 // fall-through
reed@android.com8a1c16f2008-12-17 15:59:43 +00001958 case kQuad_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001959 pts[0] = this->cons_moveTo();
1960 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001961 fLastPt = srcPts[1];
1962 srcPts += 2;
1963 break;
1964 case kCubic_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001965 pts[0] = this->cons_moveTo();
1966 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001967 fLastPt = srcPts[2];
1968 srcPts += 3;
1969 break;
1970 case kClose_Verb:
1971 verb = this->autoClose(pts);
1972 if (verb == kLine_Verb) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001973 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001974 } else {
1975 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001976 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001977 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001978 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001979 break;
1980 }
1981 fPts = srcPts;
1982 return (Verb)verb;
1983}
1984
1985///////////////////////////////////////////////////////////////////////////////
1986
reed@android.com8a1c16f2008-12-17 15:59:43 +00001987/*
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001988 Format in compressed buffer: [ptCount, verbCount, pts[], verbs[]]
reed@android.com8a1c16f2008-12-17 15:59:43 +00001989*/
1990
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00001991size_t SkPath::writeToMemory(void* storage) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001992 SkDEBUGCODE(this->validate();)
1993
halcanary96fcdcc2015-08-27 07:41:13 -07001994 if (nullptr == storage) {
caryclark69006412016-02-17 12:16:27 -08001995 const int byteCount = sizeof(int32_t) * 2 + fPathRef->writeSize();
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001996 return SkAlign4(byteCount);
1997 }
1998
1999 SkWBuffer buffer(storage);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002000
robertphillips@google.com466310d2013-12-03 16:43:54 +00002001 int32_t packed = (fConvexity << kConvexity_SerializationShift) |
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002002 (fFillType << kFillType_SerializationShift) |
reed026beb52015-06-10 14:23:15 -07002003 (fFirstDirection << kDirection_SerializationShift) |
reed8f086022015-06-11 14:22:19 -07002004 (fIsVolatile << kIsVolatile_SerializationShift) |
2005 kCurrent_Version;
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002006
robertphillips@google.com2972bb52012-08-07 17:32:51 +00002007 buffer.write32(packed);
caryclark69006412016-02-17 12:16:27 -08002008 buffer.write32(fLastMoveToIndex);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002009
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002010 fPathRef->writeToBuffer(&buffer);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002011
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002012 buffer.padToAlign4();
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002013 return buffer.pos();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002014}
2015
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002016size_t SkPath::readFromMemory(const void* storage, size_t length) {
2017 SkRBufferWithSizeCheck buffer(storage, length);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002018
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00002019 int32_t packed;
2020 if (!buffer.readS32(&packed)) {
2021 return 0;
2022 }
2023
reed8f086022015-06-11 14:22:19 -07002024 unsigned version = packed & 0xFF;
caryclark69006412016-02-17 12:16:27 -08002025 if (version >= kPathPrivLastMoveToIndex_Version && !buffer.readS32(&fLastMoveToIndex)) {
2026 return 0;
2027 }
mtklein1b249332015-07-07 12:21:21 -07002028
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002029 fConvexity = (packed >> kConvexity_SerializationShift) & 0xFF;
2030 fFillType = (packed >> kFillType_SerializationShift) & 0xFF;
reed8f086022015-06-11 14:22:19 -07002031 uint8_t dir = (packed >> kDirection_SerializationShift) & 0x3;
jvanverthb3eb6872014-10-24 07:12:51 -07002032 fIsVolatile = (packed >> kIsVolatile_SerializationShift) & 0x1;
commit-bot@chromium.orgfed2ab62014-01-23 15:16:05 +00002033 SkPathRef* pathRef = SkPathRef::CreateFromBuffer(&buffer);
ajumaf8aec582016-01-13 13:46:31 -08002034 if (!pathRef) {
2035 return 0;
2036 }
2037
2038 fPathRef.reset(pathRef);
2039 SkDEBUGCODE(this->validate();)
2040 buffer.skipToAlign4();
reed@google.comabf15c12011-01-18 20:35:51 +00002041
reed8f086022015-06-11 14:22:19 -07002042 // compatibility check
2043 if (version < kPathPrivFirstDirection_Version) {
2044 switch (dir) { // old values
2045 case 0:
2046 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
2047 break;
2048 case 1:
2049 fFirstDirection = SkPathPriv::kCW_FirstDirection;
2050 break;
2051 case 2:
2052 fFirstDirection = SkPathPriv::kCCW_FirstDirection;
2053 break;
2054 default:
2055 SkASSERT(false);
2056 }
2057 } else {
2058 fFirstDirection = dir;
2059 }
2060
ajumaf8aec582016-01-13 13:46:31 -08002061 return buffer.pos();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002062}
2063
2064///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00002065
reede05fed02014-12-15 07:59:53 -08002066#include "SkStringUtils.h"
caryclark66a5d8b2014-06-24 08:30:15 -07002067#include "SkStream.h"
reed@google.com51bbe752013-01-17 22:07:50 +00002068
reed@google.com51bbe752013-01-17 22:07:50 +00002069static void append_params(SkString* str, const char label[], const SkPoint pts[],
reede05fed02014-12-15 07:59:53 -08002070 int count, SkScalarAsStringType strType, SkScalar conicWeight = -1) {
reed@google.com51bbe752013-01-17 22:07:50 +00002071 str->append(label);
2072 str->append("(");
skia.committer@gmail.com15dd3002013-01-18 07:07:28 +00002073
reed@google.com51bbe752013-01-17 22:07:50 +00002074 const SkScalar* values = &pts[0].fX;
2075 count *= 2;
2076
2077 for (int i = 0; i < count; ++i) {
reede05fed02014-12-15 07:59:53 -08002078 SkAppendScalar(str, values[i], strType);
reed@google.com51bbe752013-01-17 22:07:50 +00002079 if (i < count - 1) {
2080 str->append(", ");
2081 }
2082 }
reed@google.com277c3f82013-05-31 15:17:50 +00002083 if (conicWeight >= 0) {
2084 str->append(", ");
reede05fed02014-12-15 07:59:53 -08002085 SkAppendScalar(str, conicWeight, strType);
reed@google.com277c3f82013-05-31 15:17:50 +00002086 }
caryclark08fa28c2014-10-23 13:08:56 -07002087 str->append(");");
reede05fed02014-12-15 07:59:53 -08002088 if (kHex_SkScalarAsStringType == strType) {
caryclark08fa28c2014-10-23 13:08:56 -07002089 str->append(" // ");
2090 for (int i = 0; i < count; ++i) {
reede05fed02014-12-15 07:59:53 -08002091 SkAppendScalarDec(str, values[i]);
caryclark08fa28c2014-10-23 13:08:56 -07002092 if (i < count - 1) {
2093 str->append(", ");
2094 }
2095 }
2096 if (conicWeight >= 0) {
2097 str->append(", ");
reede05fed02014-12-15 07:59:53 -08002098 SkAppendScalarDec(str, conicWeight);
caryclark08fa28c2014-10-23 13:08:56 -07002099 }
2100 }
2101 str->append("\n");
reed@google.com51bbe752013-01-17 22:07:50 +00002102}
2103
caryclarke9562592014-09-15 09:26:09 -07002104void SkPath::dump(SkWStream* wStream, bool forceClose, bool dumpAsHex) const {
reede05fed02014-12-15 07:59:53 -08002105 SkScalarAsStringType asType = dumpAsHex ? kHex_SkScalarAsStringType : kDec_SkScalarAsStringType;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002106 Iter iter(*this, forceClose);
2107 SkPoint pts[4];
2108 Verb verb;
2109
caryclark66a5d8b2014-06-24 08:30:15 -07002110 if (!wStream) {
2111 SkDebugf("path: forceClose=%s\n", forceClose ? "true" : "false");
2112 }
reed@google.com51bbe752013-01-17 22:07:50 +00002113 SkString builder;
2114
reed@google.com4a3b7142012-05-16 17:16:46 +00002115 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002116 switch (verb) {
2117 case kMove_Verb:
reede05fed02014-12-15 07:59:53 -08002118 append_params(&builder, "path.moveTo", &pts[0], 1, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002119 break;
2120 case kLine_Verb:
reede05fed02014-12-15 07:59:53 -08002121 append_params(&builder, "path.lineTo", &pts[1], 1, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002122 break;
2123 case kQuad_Verb:
reede05fed02014-12-15 07:59:53 -08002124 append_params(&builder, "path.quadTo", &pts[1], 2, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002125 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002126 case kConic_Verb:
reede05fed02014-12-15 07:59:53 -08002127 append_params(&builder, "path.conicTo", &pts[1], 2, asType, iter.conicWeight());
reed@google.com277c3f82013-05-31 15:17:50 +00002128 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002129 case kCubic_Verb:
reede05fed02014-12-15 07:59:53 -08002130 append_params(&builder, "path.cubicTo", &pts[1], 3, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002131 break;
2132 case kClose_Verb:
caryclark66a5d8b2014-06-24 08:30:15 -07002133 builder.append("path.close();\n");
reed@android.com8a1c16f2008-12-17 15:59:43 +00002134 break;
2135 default:
2136 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
2137 verb = kDone_Verb; // stop the loop
2138 break;
2139 }
caryclark1049f122015-04-20 08:31:59 -07002140 if (!wStream && builder.size()) {
2141 SkDebugf("%s", builder.c_str());
2142 builder.reset();
2143 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002144 }
caryclark66a5d8b2014-06-24 08:30:15 -07002145 if (wStream) {
2146 wStream->writeText(builder.c_str());
caryclark66a5d8b2014-06-24 08:30:15 -07002147 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002148}
2149
reed@android.come522ca52009-11-23 20:10:41 +00002150void SkPath::dump() const {
halcanary96fcdcc2015-08-27 07:41:13 -07002151 this->dump(nullptr, false, false);
caryclarke9562592014-09-15 09:26:09 -07002152}
2153
2154void SkPath::dumpHex() const {
halcanary96fcdcc2015-08-27 07:41:13 -07002155 this->dump(nullptr, false, true);
reed@android.come522ca52009-11-23 20:10:41 +00002156}
2157
2158#ifdef SK_DEBUG
2159void SkPath::validate() const {
reed@android.come522ca52009-11-23 20:10:41 +00002160 SkASSERT((fFillType & ~3) == 0);
reed@google.comabf15c12011-01-18 20:35:51 +00002161
djsollen@google.com077348c2012-10-22 20:23:32 +00002162#ifdef SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002163 if (!fBoundsIsDirty) {
2164 SkRect bounds;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002165
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002166 bool isFinite = compute_pt_bounds(&bounds, *fPathRef.get());
robertphillips@google.com5d8d1862012-08-15 14:36:41 +00002167 SkASSERT(SkToBool(fIsFinite) == isFinite);
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002168
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002169 if (fPathRef->countPoints() <= 1) {
reed@android.come522ca52009-11-23 20:10:41 +00002170 // if we're empty, fBounds may be empty but translated, so we can't
2171 // necessarily compare to bounds directly
2172 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
2173 // be [2, 2, 2, 2]
2174 SkASSERT(bounds.isEmpty());
2175 SkASSERT(fBounds.isEmpty());
2176 } else {
reed@google.comeac52bd2011-11-14 18:13:59 +00002177 if (bounds.isEmpty()) {
2178 SkASSERT(fBounds.isEmpty());
2179 } else {
reed@google.com3563c9e2011-11-14 19:34:57 +00002180 if (!fBounds.isEmpty()) {
2181 SkASSERT(fBounds.contains(bounds));
2182 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002183 }
reed@android.come522ca52009-11-23 20:10:41 +00002184 }
2185 }
djsollen@google.com077348c2012-10-22 20:23:32 +00002186#endif // SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002187}
djsollen@google.com077348c2012-10-22 20:23:32 +00002188#endif // SK_DEBUG
reed@android.come522ca52009-11-23 20:10:41 +00002189
reed@google.com04863fa2011-05-15 04:08:24 +00002190///////////////////////////////////////////////////////////////////////////////
2191
reed@google.com0b7b9822011-05-16 12:29:27 +00002192static int sign(SkScalar x) { return x < 0; }
2193#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00002194
robertphillipsc506e302014-09-16 09:43:31 -07002195enum DirChange {
2196 kLeft_DirChange,
2197 kRight_DirChange,
2198 kStraight_DirChange,
2199 kBackwards_DirChange,
2200
2201 kInvalid_DirChange
2202};
2203
2204
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002205static bool almost_equal(SkScalar compA, SkScalar compB) {
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002206 // The error epsilon was empirically derived; worse case round rects
2207 // with a mid point outset by 2x float epsilon in tests had an error
2208 // of 12.
2209 const int epsilon = 16;
2210 if (!SkScalarIsFinite(compA) || !SkScalarIsFinite(compB)) {
2211 return false;
2212 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002213 // no need to check for small numbers because SkPath::Iter has removed degenerate values
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002214 int aBits = SkFloatAs2sCompliment(compA);
2215 int bBits = SkFloatAs2sCompliment(compB);
2216 return aBits < bBits + epsilon && bBits < aBits + epsilon;
reed@google.com04863fa2011-05-15 04:08:24 +00002217}
2218
caryclarkb4216502015-03-02 13:02:34 -08002219static bool approximately_zero_when_compared_to(double x, double y) {
2220 return x == 0 || fabs(x) < fabs(y * FLT_EPSILON);
robertphillipsc506e302014-09-16 09:43:31 -07002221}
2222
caryclarkb4216502015-03-02 13:02:34 -08002223
reed@google.com04863fa2011-05-15 04:08:24 +00002224// only valid for a single contour
2225struct Convexicator {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002226 Convexicator()
2227 : fPtCount(0)
2228 , fConvexity(SkPath::kConvex_Convexity)
reed026beb52015-06-10 14:23:15 -07002229 , fFirstDirection(SkPathPriv::kUnknown_FirstDirection)
caryclark5ccef572015-03-02 10:07:56 -08002230 , fIsFinite(true)
2231 , fIsCurve(false) {
robertphillipsc506e302014-09-16 09:43:31 -07002232 fExpectedDir = kInvalid_DirChange;
reed@google.com04863fa2011-05-15 04:08:24 +00002233 // warnings
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002234 fLastPt.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002235 fCurrPt.set(0, 0);
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002236 fLastVec.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002237 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00002238
2239 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00002240 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00002241 }
2242
2243 SkPath::Convexity getConvexity() const { return fConvexity; }
2244
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002245 /** The direction returned is only valid if the path is determined convex */
reed026beb52015-06-10 14:23:15 -07002246 SkPathPriv::FirstDirection getFirstDirection() const { return fFirstDirection; }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002247
reed@google.com04863fa2011-05-15 04:08:24 +00002248 void addPt(const SkPoint& pt) {
caryclarkd3d1a982014-12-08 04:57:38 -08002249 if (SkPath::kConcave_Convexity == fConvexity || !fIsFinite) {
reed@google.com04863fa2011-05-15 04:08:24 +00002250 return;
2251 }
2252
2253 if (0 == fPtCount) {
2254 fCurrPt = pt;
2255 ++fPtCount;
2256 } else {
2257 SkVector vec = pt - fCurrPt;
caryclarkd3d1a982014-12-08 04:57:38 -08002258 SkScalar lengthSqd = vec.lengthSqd();
2259 if (!SkScalarIsFinite(lengthSqd)) {
2260 fIsFinite = false;
caryclarke8c56662015-07-14 11:19:26 -07002261 } else if (lengthSqd) {
caryclarkb4216502015-03-02 13:02:34 -08002262 fPriorPt = fLastPt;
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002263 fLastPt = fCurrPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002264 fCurrPt = pt;
2265 if (++fPtCount == 2) {
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002266 fFirstVec = fLastVec = vec;
reed@google.com04863fa2011-05-15 04:08:24 +00002267 } else {
2268 SkASSERT(fPtCount > 2);
2269 this->addVec(vec);
2270 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002271
reed@google.com85b6e392011-05-15 20:25:17 +00002272 int sx = sign(vec.fX);
2273 int sy = sign(vec.fY);
2274 fDx += (sx != fSx);
2275 fDy += (sy != fSy);
2276 fSx = sx;
2277 fSy = sy;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002278
reed@google.com85b6e392011-05-15 20:25:17 +00002279 if (fDx > 3 || fDy > 3) {
2280 fConvexity = SkPath::kConcave_Convexity;
2281 }
reed@google.com04863fa2011-05-15 04:08:24 +00002282 }
2283 }
2284 }
2285
2286 void close() {
2287 if (fPtCount > 2) {
2288 this->addVec(fFirstVec);
2289 }
2290 }
2291
caryclarkb4216502015-03-02 13:02:34 -08002292 DirChange directionChange(const SkVector& curVec) {
2293 SkScalar cross = SkPoint::CrossProduct(fLastVec, curVec);
2294
2295 SkScalar smallest = SkTMin(fCurrPt.fX, SkTMin(fCurrPt.fY, SkTMin(fLastPt.fX, fLastPt.fY)));
2296 SkScalar largest = SkTMax(fCurrPt.fX, SkTMax(fCurrPt.fY, SkTMax(fLastPt.fX, fLastPt.fY)));
2297 largest = SkTMax(largest, -smallest);
2298
2299 if (!almost_equal(largest, largest + cross)) {
2300 int sign = SkScalarSignAsInt(cross);
2301 if (sign) {
2302 return (1 == sign) ? kRight_DirChange : kLeft_DirChange;
2303 }
2304 }
2305
2306 if (cross) {
2307 double dLastVecX = SkScalarToDouble(fLastPt.fX) - SkScalarToDouble(fPriorPt.fX);
2308 double dLastVecY = SkScalarToDouble(fLastPt.fY) - SkScalarToDouble(fPriorPt.fY);
2309 double dCurrVecX = SkScalarToDouble(fCurrPt.fX) - SkScalarToDouble(fLastPt.fX);
2310 double dCurrVecY = SkScalarToDouble(fCurrPt.fY) - SkScalarToDouble(fLastPt.fY);
2311 double dCross = dLastVecX * dCurrVecY - dLastVecY * dCurrVecX;
2312 if (!approximately_zero_when_compared_to(dCross, SkScalarToDouble(largest))) {
2313 int sign = SkScalarSignAsInt(SkDoubleToScalar(dCross));
2314 if (sign) {
2315 return (1 == sign) ? kRight_DirChange : kLeft_DirChange;
2316 }
2317 }
2318 }
2319
2320 if (!SkScalarNearlyZero(fLastVec.lengthSqd(), SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
2321 !SkScalarNearlyZero(curVec.lengthSqd(), SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
2322 fLastVec.dot(curVec) < 0.0f) {
2323 return kBackwards_DirChange;
2324 }
2325
2326 return kStraight_DirChange;
2327 }
2328
2329
caryclarkd3d1a982014-12-08 04:57:38 -08002330 bool isFinite() const {
2331 return fIsFinite;
2332 }
2333
caryclark5ccef572015-03-02 10:07:56 -08002334 void setCurve(bool isCurve) {
2335 fIsCurve = isCurve;
2336 }
2337
reed@google.com04863fa2011-05-15 04:08:24 +00002338private:
2339 void addVec(const SkVector& vec) {
2340 SkASSERT(vec.fX || vec.fY);
caryclarkb4216502015-03-02 13:02:34 -08002341 DirChange dir = this->directionChange(vec);
robertphillipsc506e302014-09-16 09:43:31 -07002342 switch (dir) {
2343 case kLeft_DirChange: // fall through
2344 case kRight_DirChange:
2345 if (kInvalid_DirChange == fExpectedDir) {
2346 fExpectedDir = dir;
reed026beb52015-06-10 14:23:15 -07002347 fFirstDirection = (kRight_DirChange == dir) ? SkPathPriv::kCW_FirstDirection
2348 : SkPathPriv::kCCW_FirstDirection;
robertphillipsc506e302014-09-16 09:43:31 -07002349 } else if (dir != fExpectedDir) {
2350 fConvexity = SkPath::kConcave_Convexity;
reed026beb52015-06-10 14:23:15 -07002351 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
robertphillipsc506e302014-09-16 09:43:31 -07002352 }
2353 fLastVec = vec;
2354 break;
2355 case kStraight_DirChange:
2356 break;
2357 case kBackwards_DirChange:
caryclark5ccef572015-03-02 10:07:56 -08002358 if (fIsCurve) {
2359 fConvexity = SkPath::kConcave_Convexity;
reed026beb52015-06-10 14:23:15 -07002360 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
caryclark5ccef572015-03-02 10:07:56 -08002361 }
robertphillipsc506e302014-09-16 09:43:31 -07002362 fLastVec = vec;
2363 break;
2364 case kInvalid_DirChange:
2365 SkFAIL("Use of invalid direction change flag");
2366 break;
reed@google.com04863fa2011-05-15 04:08:24 +00002367 }
2368 }
2369
caryclarkb4216502015-03-02 13:02:34 -08002370 SkPoint fPriorPt;
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002371 SkPoint fLastPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002372 SkPoint fCurrPt;
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002373 // fLastVec does not necessarily start at fLastPt. We only advance it when the cross product
2374 // value with the current vec is deemed to be of a significant value.
2375 SkVector fLastVec, fFirstVec;
reed@google.com04863fa2011-05-15 04:08:24 +00002376 int fPtCount; // non-degenerate points
robertphillipsc506e302014-09-16 09:43:31 -07002377 DirChange fExpectedDir;
reed@google.com04863fa2011-05-15 04:08:24 +00002378 SkPath::Convexity fConvexity;
reed026beb52015-06-10 14:23:15 -07002379 SkPathPriv::FirstDirection fFirstDirection;
reed@google.com0b7b9822011-05-16 12:29:27 +00002380 int fDx, fDy, fSx, fSy;
caryclarkd3d1a982014-12-08 04:57:38 -08002381 bool fIsFinite;
caryclark5ccef572015-03-02 10:07:56 -08002382 bool fIsCurve;
reed@google.com04863fa2011-05-15 04:08:24 +00002383};
2384
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002385SkPath::Convexity SkPath::internalGetConvexity() const {
2386 SkASSERT(kUnknown_Convexity == fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002387 SkPoint pts[4];
2388 SkPath::Verb verb;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002389 SkPath::Iter iter(*this, true);
reed@google.com04863fa2011-05-15 04:08:24 +00002390
2391 int contourCount = 0;
2392 int count;
2393 Convexicator state;
2394
caryclarkd3d1a982014-12-08 04:57:38 -08002395 if (!isFinite()) {
2396 return kUnknown_Convexity;
2397 }
caryclarke8c56662015-07-14 11:19:26 -07002398 while ((verb = iter.next(pts, true, true)) != SkPath::kDone_Verb) {
reed@google.com04863fa2011-05-15 04:08:24 +00002399 switch (verb) {
2400 case kMove_Verb:
2401 if (++contourCount > 1) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002402 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002403 return kConcave_Convexity;
2404 }
2405 pts[1] = pts[0];
caryclark5ccef572015-03-02 10:07:56 -08002406 // fall through
2407 case kLine_Verb:
reed@google.com04863fa2011-05-15 04:08:24 +00002408 count = 1;
caryclark5ccef572015-03-02 10:07:56 -08002409 state.setCurve(false);
reed@google.com04863fa2011-05-15 04:08:24 +00002410 break;
caryclark5ccef572015-03-02 10:07:56 -08002411 case kQuad_Verb:
2412 // fall through
2413 case kConic_Verb:
2414 // fall through
2415 case kCubic_Verb:
2416 count = 2 + (kCubic_Verb == verb);
2417 // As an additional enhancement, this could set curve true only
2418 // if the curve is nonlinear
2419 state.setCurve(true);
2420 break;
reed@google.com04863fa2011-05-15 04:08:24 +00002421 case kClose_Verb:
caryclark5ccef572015-03-02 10:07:56 -08002422 state.setCurve(false);
reed@google.com04863fa2011-05-15 04:08:24 +00002423 state.close();
2424 count = 0;
2425 break;
2426 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00002427 SkDEBUGFAIL("bad verb");
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002428 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002429 return kConcave_Convexity;
2430 }
2431
2432 for (int i = 1; i <= count; i++) {
2433 state.addPt(pts[i]);
2434 }
2435 // early exit
caryclarkd3d1a982014-12-08 04:57:38 -08002436 if (!state.isFinite()) {
2437 return kUnknown_Convexity;
2438 }
reed@google.com04863fa2011-05-15 04:08:24 +00002439 if (kConcave_Convexity == state.getConvexity()) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002440 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002441 return kConcave_Convexity;
2442 }
2443 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002444 fConvexity = state.getConvexity();
reed026beb52015-06-10 14:23:15 -07002445 if (kConvex_Convexity == fConvexity && SkPathPriv::kUnknown_FirstDirection == fFirstDirection) {
2446 fFirstDirection = state.getFirstDirection();
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002447 }
2448 return static_cast<Convexity>(fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002449}
reed@google.com69a99432012-01-10 18:00:10 +00002450
2451///////////////////////////////////////////////////////////////////////////////
2452
2453class ContourIter {
2454public:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002455 ContourIter(const SkPathRef& pathRef);
reed@google.com69a99432012-01-10 18:00:10 +00002456
2457 bool done() const { return fDone; }
2458 // if !done() then these may be called
2459 int count() const { return fCurrPtCount; }
2460 const SkPoint* pts() const { return fCurrPt; }
2461 void next();
2462
2463private:
2464 int fCurrPtCount;
2465 const SkPoint* fCurrPt;
2466 const uint8_t* fCurrVerb;
2467 const uint8_t* fStopVerbs;
reed@google.com277c3f82013-05-31 15:17:50 +00002468 const SkScalar* fCurrConicWeight;
reed@google.com69a99432012-01-10 18:00:10 +00002469 bool fDone;
reed@google.comd1ab9322012-01-10 18:40:03 +00002470 SkDEBUGCODE(int fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002471};
2472
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002473ContourIter::ContourIter(const SkPathRef& pathRef) {
2474 fStopVerbs = pathRef.verbsMemBegin();
reed@google.com69a99432012-01-10 18:00:10 +00002475 fDone = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002476 fCurrPt = pathRef.points();
2477 fCurrVerb = pathRef.verbs();
reed@google.com277c3f82013-05-31 15:17:50 +00002478 fCurrConicWeight = pathRef.conicWeights();
reed@google.com69a99432012-01-10 18:00:10 +00002479 fCurrPtCount = 0;
reed@google.comd1ab9322012-01-10 18:40:03 +00002480 SkDEBUGCODE(fContourCounter = 0;)
reed@google.com69a99432012-01-10 18:00:10 +00002481 this->next();
2482}
2483
2484void ContourIter::next() {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002485 if (fCurrVerb <= fStopVerbs) {
reed@google.com69a99432012-01-10 18:00:10 +00002486 fDone = true;
2487 }
2488 if (fDone) {
2489 return;
2490 }
2491
2492 // skip pts of prev contour
2493 fCurrPt += fCurrPtCount;
2494
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002495 SkASSERT(SkPath::kMove_Verb == fCurrVerb[~0]);
reed@google.com69a99432012-01-10 18:00:10 +00002496 int ptCount = 1; // moveTo
2497 const uint8_t* verbs = fCurrVerb;
2498
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002499 for (--verbs; verbs > fStopVerbs; --verbs) {
2500 switch (verbs[~0]) {
reed@google.com69a99432012-01-10 18:00:10 +00002501 case SkPath::kMove_Verb:
reed@google.com69a99432012-01-10 18:00:10 +00002502 goto CONTOUR_END;
2503 case SkPath::kLine_Verb:
2504 ptCount += 1;
2505 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002506 case SkPath::kConic_Verb:
2507 fCurrConicWeight += 1;
2508 // fall-through
reed@google.com69a99432012-01-10 18:00:10 +00002509 case SkPath::kQuad_Verb:
2510 ptCount += 2;
2511 break;
2512 case SkPath::kCubic_Verb:
2513 ptCount += 3;
2514 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002515 case SkPath::kClose_Verb:
2516 break;
2517 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00002518 SkDEBUGFAIL("unexpected verb");
reed@google.com69a99432012-01-10 18:00:10 +00002519 break;
2520 }
2521 }
2522CONTOUR_END:
2523 fCurrPtCount = ptCount;
2524 fCurrVerb = verbs;
reed@google.comd1ab9322012-01-10 18:40:03 +00002525 SkDEBUGCODE(++fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002526}
2527
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002528// returns cross product of (p1 - p0) and (p2 - p0)
reed@google.com69a99432012-01-10 18:00:10 +00002529static SkScalar cross_prod(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002530 SkScalar cross = SkPoint::CrossProduct(p1 - p0, p2 - p0);
2531 // We may get 0 when the above subtracts underflow. We expect this to be
2532 // very rare and lazily promote to double.
2533 if (0 == cross) {
2534 double p0x = SkScalarToDouble(p0.fX);
2535 double p0y = SkScalarToDouble(p0.fY);
2536
2537 double p1x = SkScalarToDouble(p1.fX);
2538 double p1y = SkScalarToDouble(p1.fY);
2539
2540 double p2x = SkScalarToDouble(p2.fX);
2541 double p2y = SkScalarToDouble(p2.fY);
2542
2543 cross = SkDoubleToScalar((p1x - p0x) * (p2y - p0y) -
2544 (p1y - p0y) * (p2x - p0x));
2545
2546 }
2547 return cross;
reed@google.com69a99432012-01-10 18:00:10 +00002548}
2549
reed@google.comc1ea60a2012-01-31 15:15:36 +00002550// Returns the first pt with the maximum Y coordinate
reed@google.com69a99432012-01-10 18:00:10 +00002551static int find_max_y(const SkPoint pts[], int count) {
2552 SkASSERT(count > 0);
2553 SkScalar max = pts[0].fY;
reed@google.comc1ea60a2012-01-31 15:15:36 +00002554 int firstIndex = 0;
reed@google.com69a99432012-01-10 18:00:10 +00002555 for (int i = 1; i < count; ++i) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002556 SkScalar y = pts[i].fY;
2557 if (y > max) {
2558 max = y;
2559 firstIndex = i;
reed@google.com69a99432012-01-10 18:00:10 +00002560 }
2561 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002562 return firstIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002563}
2564
reed@google.comcabaf1d2012-01-11 21:03:05 +00002565static int find_diff_pt(const SkPoint pts[], int index, int n, int inc) {
2566 int i = index;
2567 for (;;) {
2568 i = (i + inc) % n;
2569 if (i == index) { // we wrapped around, so abort
2570 break;
2571 }
2572 if (pts[index] != pts[i]) { // found a different point, success!
2573 break;
2574 }
2575 }
2576 return i;
2577}
2578
reed@google.comc1ea60a2012-01-31 15:15:36 +00002579/**
2580 * Starting at index, and moving forward (incrementing), find the xmin and
2581 * xmax of the contiguous points that have the same Y.
2582 */
2583static int find_min_max_x_at_y(const SkPoint pts[], int index, int n,
2584 int* maxIndexPtr) {
2585 const SkScalar y = pts[index].fY;
2586 SkScalar min = pts[index].fX;
2587 SkScalar max = min;
2588 int minIndex = index;
2589 int maxIndex = index;
2590 for (int i = index + 1; i < n; ++i) {
2591 if (pts[i].fY != y) {
2592 break;
2593 }
2594 SkScalar x = pts[i].fX;
2595 if (x < min) {
2596 min = x;
2597 minIndex = i;
2598 } else if (x > max) {
2599 max = x;
2600 maxIndex = i;
2601 }
2602 }
2603 *maxIndexPtr = maxIndex;
2604 return minIndex;
2605}
2606
reed026beb52015-06-10 14:23:15 -07002607static void crossToDir(SkScalar cross, SkPathPriv::FirstDirection* dir) {
2608 *dir = cross > 0 ? SkPathPriv::kCW_FirstDirection : SkPathPriv::kCCW_FirstDirection;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002609}
2610
reed@google.comac8543f2012-01-30 20:51:25 +00002611/*
2612 * We loop through all contours, and keep the computed cross-product of the
2613 * contour that contained the global y-max. If we just look at the first
2614 * contour, we may find one that is wound the opposite way (correctly) since
2615 * it is the interior of a hole (e.g. 'o'). Thus we must find the contour
2616 * that is outer most (or at least has the global y-max) before we can consider
2617 * its cross product.
2618 */
reed026beb52015-06-10 14:23:15 -07002619bool SkPathPriv::CheapComputeFirstDirection(const SkPath& path, FirstDirection* dir) {
herb9f4dbca2015-09-28 11:05:47 -07002620 if (kUnknown_FirstDirection != path.fFirstDirection.load()) {
2621 *dir = static_cast<FirstDirection>(path.fFirstDirection.load());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002622 return true;
2623 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002624
2625 // don't want to pay the cost for computing this if it
2626 // is unknown, so we don't call isConvex()
reed026beb52015-06-10 14:23:15 -07002627 if (SkPath::kConvex_Convexity == path.getConvexityOrUnknown()) {
2628 SkASSERT(kUnknown_FirstDirection == path.fFirstDirection);
herb9f4dbca2015-09-28 11:05:47 -07002629 *dir = static_cast<FirstDirection>(path.fFirstDirection.load());
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002630 return false;
2631 }
reed@google.com69a99432012-01-10 18:00:10 +00002632
reed026beb52015-06-10 14:23:15 -07002633 ContourIter iter(*path.fPathRef.get());
reed@google.com69a99432012-01-10 18:00:10 +00002634
reed@google.comac8543f2012-01-30 20:51:25 +00002635 // initialize with our logical y-min
reed026beb52015-06-10 14:23:15 -07002636 SkScalar ymax = path.getBounds().fTop;
reed@google.comac8543f2012-01-30 20:51:25 +00002637 SkScalar ymaxCross = 0;
2638
reed@google.com69a99432012-01-10 18:00:10 +00002639 for (; !iter.done(); iter.next()) {
2640 int n = iter.count();
reed@google.comcabaf1d2012-01-11 21:03:05 +00002641 if (n < 3) {
2642 continue;
2643 }
djsollen@google.come63793a2012-03-21 15:39:03 +00002644
reed@google.comcabaf1d2012-01-11 21:03:05 +00002645 const SkPoint* pts = iter.pts();
reed@google.com69a99432012-01-10 18:00:10 +00002646 SkScalar cross = 0;
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002647 int index = find_max_y(pts, n);
2648 if (pts[index].fY < ymax) {
2649 continue;
2650 }
2651
2652 // If there is more than 1 distinct point at the y-max, we take the
2653 // x-min and x-max of them and just subtract to compute the dir.
2654 if (pts[(index + 1) % n].fY == pts[index].fY) {
2655 int maxIndex;
2656 int minIndex = find_min_max_x_at_y(pts, index, n, &maxIndex);
2657 if (minIndex == maxIndex) {
2658 goto TRY_CROSSPROD;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002659 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002660 SkASSERT(pts[minIndex].fY == pts[index].fY);
2661 SkASSERT(pts[maxIndex].fY == pts[index].fY);
2662 SkASSERT(pts[minIndex].fX <= pts[maxIndex].fX);
2663 // we just subtract the indices, and let that auto-convert to
2664 // SkScalar, since we just want - or + to signal the direction.
2665 cross = minIndex - maxIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002666 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002667 TRY_CROSSPROD:
2668 // Find a next and prev index to use for the cross-product test,
2669 // but we try to find pts that form non-zero vectors from pts[index]
2670 //
2671 // Its possible that we can't find two non-degenerate vectors, so
2672 // we have to guard our search (e.g. all the pts could be in the
2673 // same place).
2674
2675 // we pass n - 1 instead of -1 so we don't foul up % operator by
2676 // passing it a negative LH argument.
2677 int prev = find_diff_pt(pts, index, n, n - 1);
2678 if (prev == index) {
2679 // completely degenerate, skip to next contour
reed@google.comac8543f2012-01-30 20:51:25 +00002680 continue;
2681 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002682 int next = find_diff_pt(pts, index, n, 1);
2683 SkASSERT(next != index);
2684 cross = cross_prod(pts[prev], pts[index], pts[next]);
2685 // if we get a zero and the points are horizontal, then we look at the spread in
2686 // x-direction. We really should continue to walk away from the degeneracy until
2687 // there is a divergence.
2688 if (0 == cross && pts[prev].fY == pts[index].fY && pts[next].fY == pts[index].fY) {
2689 // construct the subtract so we get the correct Direction below
2690 cross = pts[index].fX - pts[next].fX;
reed@google.com188bfcf2012-01-17 18:26:38 +00002691 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002692 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002693
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002694 if (cross) {
2695 // record our best guess so far
2696 ymax = pts[index].fY;
2697 ymaxCross = cross;
reed@google.com69a99432012-01-10 18:00:10 +00002698 }
2699 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002700 if (ymaxCross) {
2701 crossToDir(ymaxCross, dir);
reed026beb52015-06-10 14:23:15 -07002702 path.fFirstDirection = *dir;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002703 return true;
2704 } else {
2705 return false;
2706 }
reed@google.comac8543f2012-01-30 20:51:25 +00002707}
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002708
2709///////////////////////////////////////////////////////////////////////////////
2710
caryclark9aacd902015-12-14 08:38:09 -08002711static bool between(SkScalar a, SkScalar b, SkScalar c) {
2712 SkASSERT(((a <= b && b <= c) || (a >= b && b >= c)) == ((a - b) * (c - b) <= 0)
2713 || (SkScalarNearlyZero(a) && SkScalarNearlyZero(b) && SkScalarNearlyZero(c)));
2714 return (a - b) * (c - b) <= 0;
2715}
2716
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002717static SkScalar eval_cubic_coeff(SkScalar A, SkScalar B, SkScalar C,
2718 SkScalar D, SkScalar t) {
2719 return SkScalarMulAdd(SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C), t, D);
2720}
2721
2722static SkScalar eval_cubic_pts(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2723 SkScalar t) {
2724 SkScalar A = c3 + 3*(c1 - c2) - c0;
2725 SkScalar B = 3*(c2 - c1 - c1 + c0);
2726 SkScalar C = 3*(c1 - c0);
2727 SkScalar D = c0;
2728 return eval_cubic_coeff(A, B, C, D, t);
2729}
2730
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002731template <size_t N> static void find_minmax(const SkPoint pts[],
2732 SkScalar* minPtr, SkScalar* maxPtr) {
2733 SkScalar min, max;
2734 min = max = pts[0].fX;
2735 for (size_t i = 1; i < N; ++i) {
2736 min = SkMinScalar(min, pts[i].fX);
2737 max = SkMaxScalar(max, pts[i].fX);
2738 }
2739 *minPtr = min;
2740 *maxPtr = max;
2741}
2742
caryclark9cb5d752015-12-18 04:35:24 -08002743static bool checkOnCurve(SkScalar x, SkScalar y, const SkPoint& start, const SkPoint& end) {
2744 if (start.fY == end.fY) {
2745 return between(start.fX, x, end.fX) && x != end.fX;
2746 } else {
2747 return x == start.fX && y == start.fY;
2748 }
2749}
2750
caryclark9aacd902015-12-14 08:38:09 -08002751static int winding_mono_cubic(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
caryclark9cb5d752015-12-18 04:35:24 -08002752 SkScalar y0 = pts[0].fY;
2753 SkScalar y3 = pts[3].fY;
2754
2755 int dir = 1;
2756 if (y0 > y3) {
2757 SkTSwap(y0, y3);
2758 dir = -1;
2759 }
2760 if (y < y0 || y > y3) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002761 return 0;
2762 }
caryclark9cb5d752015-12-18 04:35:24 -08002763 if (checkOnCurve(x, y, pts[0], pts[3])) {
2764 *onCurveCount += 1;
caryclark9aacd902015-12-14 08:38:09 -08002765 return 0;
2766 }
caryclark9cb5d752015-12-18 04:35:24 -08002767 if (y == y3) {
2768 return 0;
2769 }
2770
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002771 // quickreject or quickaccept
2772 SkScalar min, max;
2773 find_minmax<4>(pts, &min, &max);
2774 if (x < min) {
2775 return 0;
2776 }
2777 if (x > max) {
2778 return dir;
2779 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002780
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002781 // compute the actual x(t) value
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002782 SkScalar t;
caryclark9aacd902015-12-14 08:38:09 -08002783 SkAssertResult(SkCubicClipper::ChopMonoAtY(pts, y, &t));
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002784 SkScalar xt = eval_cubic_pts(pts[0].fX, pts[1].fX, pts[2].fX, pts[3].fX, t);
caryclark9aacd902015-12-14 08:38:09 -08002785 if (SkScalarNearlyEqual(xt, x)) {
2786 if (x != pts[3].fX || y != pts[3].fY) { // don't test end points; they're start points
2787 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08002788 return 0;
caryclark9aacd902015-12-14 08:38:09 -08002789 }
2790 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002791 return xt < x ? dir : 0;
2792}
2793
caryclark9aacd902015-12-14 08:38:09 -08002794static int winding_cubic(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002795 SkPoint dst[10];
2796 int n = SkChopCubicAtYExtrema(pts, dst);
2797 int w = 0;
2798 for (int i = 0; i <= n; ++i) {
caryclark9aacd902015-12-14 08:38:09 -08002799 w += winding_mono_cubic(&dst[i * 3], x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002800 }
2801 return w;
2802}
2803
caryclark9aacd902015-12-14 08:38:09 -08002804static double conic_eval_numerator(const SkScalar src[], SkScalar w, SkScalar t) {
2805 SkASSERT(src);
2806 SkASSERT(t >= 0 && t <= 1);
2807 SkScalar src2w = src[2] * w;
2808 SkScalar C = src[0];
2809 SkScalar A = src[4] - 2 * src2w + C;
2810 SkScalar B = 2 * (src2w - C);
2811 return (A * t + B) * t + C;
2812}
2813
2814
2815static double conic_eval_denominator(SkScalar w, SkScalar t) {
2816 SkScalar B = 2 * (w - 1);
2817 SkScalar C = 1;
2818 SkScalar A = -B;
2819 return (A * t + B) * t + C;
2820}
2821
2822static int winding_mono_conic(const SkConic& conic, SkScalar x, SkScalar y, int* onCurveCount) {
2823 const SkPoint* pts = conic.fPts;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002824 SkScalar y0 = pts[0].fY;
2825 SkScalar y2 = pts[2].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002826
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002827 int dir = 1;
2828 if (y0 > y2) {
2829 SkTSwap(y0, y2);
2830 dir = -1;
2831 }
caryclark9aacd902015-12-14 08:38:09 -08002832 if (y < y0 || y > y2) {
2833 return 0;
2834 }
caryclark9cb5d752015-12-18 04:35:24 -08002835 if (checkOnCurve(x, y, pts[0], pts[2])) {
2836 *onCurveCount += 1;
2837 return 0;
2838 }
caryclark9aacd902015-12-14 08:38:09 -08002839 if (y == y2) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002840 return 0;
2841 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002842
caryclark9aacd902015-12-14 08:38:09 -08002843 SkScalar roots[2];
2844 SkScalar A = pts[2].fY;
2845 SkScalar B = pts[1].fY * conic.fW - y * conic.fW + y;
2846 SkScalar C = pts[0].fY;
2847 A += C - 2 * B; // A = a + c - 2*(b*w - yCept*w + yCept)
2848 B -= C; // B = b*w - w * yCept + yCept - a
2849 C -= y;
2850 int n = SkFindUnitQuadRoots(A, 2 * B, C, roots);
2851 SkASSERT(n <= 1);
2852 SkScalar xt;
2853 if (0 == n) {
caryclark9cb5d752015-12-18 04:35:24 -08002854 // zero roots are returned only when y0 == y
2855 // Need [0] if dir == 1
2856 // and [2] if dir == -1
2857 xt = pts[1 - dir].fX;
caryclark9aacd902015-12-14 08:38:09 -08002858 } else {
2859 SkScalar t = roots[0];
2860 xt = conic_eval_numerator(&pts[0].fX, conic.fW, t) / conic_eval_denominator(conic.fW, t);
2861 }
2862 if (SkScalarNearlyEqual(xt, x)) {
2863 if (x != pts[2].fX || y != pts[2].fY) { // don't test end points; they're start points
2864 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08002865 return 0;
caryclark9aacd902015-12-14 08:38:09 -08002866 }
2867 }
2868 return xt < x ? dir : 0;
2869}
2870
2871static bool is_mono_quad(SkScalar y0, SkScalar y1, SkScalar y2) {
2872 // return SkScalarSignAsInt(y0 - y1) + SkScalarSignAsInt(y1 - y2) != 0;
2873 if (y0 == y1) {
2874 return true;
2875 }
2876 if (y0 < y1) {
2877 return y1 <= y2;
2878 } else {
2879 return y1 >= y2;
2880 }
2881}
2882
2883static int winding_conic(const SkPoint pts[], SkScalar x, SkScalar y, SkScalar weight,
2884 int* onCurveCount) {
2885 SkConic conic(pts, weight);
caryclark9aacd902015-12-14 08:38:09 -08002886 SkConic chopped[2];
caryclark1e1e5092016-01-06 08:27:44 -08002887 // If the data points are very large, the conic may not be monotonic but may also
2888 // fail to chop. Then, the chopper does not split the original conic in two.
caryclarke114dd62016-01-06 08:15:44 -08002889 bool isMono = is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY) || !conic.chopAtYExtrema(chopped);
2890 int w = winding_mono_conic(isMono ? conic : chopped[0], x, y, onCurveCount);
2891 if (!isMono) {
caryclark9aacd902015-12-14 08:38:09 -08002892 w += winding_mono_conic(chopped[1], x, y, onCurveCount);
2893 }
2894 return w;
2895}
2896
2897static int winding_mono_quad(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
2898 SkScalar y0 = pts[0].fY;
2899 SkScalar y2 = pts[2].fY;
2900
2901 int dir = 1;
2902 if (y0 > y2) {
2903 SkTSwap(y0, y2);
2904 dir = -1;
2905 }
2906 if (y < y0 || y > y2) {
2907 return 0;
2908 }
caryclark9cb5d752015-12-18 04:35:24 -08002909 if (checkOnCurve(x, y, pts[0], pts[2])) {
2910 *onCurveCount += 1;
2911 return 0;
2912 }
caryclark9aacd902015-12-14 08:38:09 -08002913 if (y == y2) {
caryclark9aacd902015-12-14 08:38:09 -08002914 return 0;
2915 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002916 // bounds check on X (not required. is it faster?)
2917#if 0
2918 if (pts[0].fX > x && pts[1].fX > x && pts[2].fX > x) {
2919 return 0;
2920 }
2921#endif
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002922
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002923 SkScalar roots[2];
2924 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
2925 2 * (pts[1].fY - pts[0].fY),
2926 pts[0].fY - y,
2927 roots);
2928 SkASSERT(n <= 1);
2929 SkScalar xt;
2930 if (0 == n) {
caryclark9cb5d752015-12-18 04:35:24 -08002931 // zero roots are returned only when y0 == y
2932 // Need [0] if dir == 1
2933 // and [2] if dir == -1
2934 xt = pts[1 - dir].fX;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002935 } else {
2936 SkScalar t = roots[0];
2937 SkScalar C = pts[0].fX;
2938 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
2939 SkScalar B = 2 * (pts[1].fX - C);
2940 xt = SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C);
2941 }
caryclark9aacd902015-12-14 08:38:09 -08002942 if (SkScalarNearlyEqual(xt, x)) {
2943 if (x != pts[2].fX || y != pts[2].fY) { // don't test end points; they're start points
2944 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08002945 return 0;
caryclark9aacd902015-12-14 08:38:09 -08002946 }
2947 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002948 return xt < x ? dir : 0;
2949}
2950
caryclark9aacd902015-12-14 08:38:09 -08002951static int winding_quad(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002952 SkPoint dst[5];
2953 int n = 0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002954
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002955 if (!is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY)) {
2956 n = SkChopQuadAtYExtrema(pts, dst);
2957 pts = dst;
2958 }
caryclark9aacd902015-12-14 08:38:09 -08002959 int w = winding_mono_quad(pts, x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002960 if (n > 0) {
caryclark9aacd902015-12-14 08:38:09 -08002961 w += winding_mono_quad(&pts[2], x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002962 }
2963 return w;
2964}
2965
caryclark9aacd902015-12-14 08:38:09 -08002966static int winding_line(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002967 SkScalar x0 = pts[0].fX;
2968 SkScalar y0 = pts[0].fY;
2969 SkScalar x1 = pts[1].fX;
2970 SkScalar y1 = pts[1].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002971
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002972 SkScalar dy = y1 - y0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002973
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002974 int dir = 1;
2975 if (y0 > y1) {
2976 SkTSwap(y0, y1);
2977 dir = -1;
2978 }
caryclark9aacd902015-12-14 08:38:09 -08002979 if (y < y0 || y > y1) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002980 return 0;
2981 }
caryclark9cb5d752015-12-18 04:35:24 -08002982 if (checkOnCurve(x, y, pts[0], pts[1])) {
2983 *onCurveCount += 1;
2984 return 0;
2985 }
2986 if (y == y1) {
caryclark9aacd902015-12-14 08:38:09 -08002987 return 0;
2988 }
2989 SkScalar cross = SkScalarMul(x1 - x0, y - pts[0].fY) - SkScalarMul(dy, x - x0);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002990
caryclark9aacd902015-12-14 08:38:09 -08002991 if (!cross) {
fsc91065d2015-12-17 09:03:27 -08002992 // zero cross means the point is on the line, and since the case where
2993 // y of the query point is at the end point is handled above, we can be
2994 // sure that we're on the line (excluding the end point) here
caryclark9cb5d752015-12-18 04:35:24 -08002995 if (x != x1 || y != pts[1].fY) {
2996 *onCurveCount += 1;
2997 }
caryclark9aacd902015-12-14 08:38:09 -08002998 dir = 0;
2999 } else if (SkScalarSignAsInt(cross) == dir) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003000 dir = 0;
3001 }
3002 return dir;
3003}
3004
caryclark9aacd902015-12-14 08:38:09 -08003005static void tangent_cubic(const SkPoint pts[], SkScalar x, SkScalar y,
3006 SkTDArray<SkVector>* tangents) {
3007 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)
3008 && !between(pts[2].fY, y, pts[3].fY)) {
3009 return;
3010 }
3011 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)
3012 && !between(pts[2].fX, x, pts[3].fX)) {
3013 return;
3014 }
3015 SkPoint dst[10];
3016 int n = SkChopCubicAtYExtrema(pts, dst);
3017 for (int i = 0; i <= n; ++i) {
3018 SkPoint* c = &dst[i * 3];
3019 SkScalar t;
3020 SkAssertResult(SkCubicClipper::ChopMonoAtY(c, y, &t));
3021 SkScalar xt = eval_cubic_pts(c[0].fX, c[1].fX, c[2].fX, c[3].fX, t);
3022 if (!SkScalarNearlyEqual(x, xt)) {
3023 continue;
3024 }
3025 SkVector tangent;
3026 SkEvalCubicAt(c, t, nullptr, &tangent, nullptr);
3027 tangents->push(tangent);
3028 }
3029}
3030
3031static void tangent_conic(const SkPoint pts[], SkScalar x, SkScalar y, SkScalar w,
3032 SkTDArray<SkVector>* tangents) {
3033 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)) {
3034 return;
3035 }
3036 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)) {
3037 return;
3038 }
3039 SkScalar roots[2];
3040 SkScalar A = pts[2].fY;
3041 SkScalar B = pts[1].fY * w - y * w + y;
3042 SkScalar C = pts[0].fY;
3043 A += C - 2 * B; // A = a + c - 2*(b*w - yCept*w + yCept)
3044 B -= C; // B = b*w - w * yCept + yCept - a
3045 C -= y;
3046 int n = SkFindUnitQuadRoots(A, 2 * B, C, roots);
3047 for (int index = 0; index < n; ++index) {
3048 SkScalar t = roots[index];
3049 SkScalar xt = conic_eval_numerator(&pts[0].fX, w, t) / conic_eval_denominator(w, t);
3050 if (!SkScalarNearlyEqual(x, xt)) {
3051 continue;
3052 }
3053 SkConic conic(pts, w);
3054 tangents->push(conic.evalTangentAt(t));
3055 }
3056}
3057
3058static void tangent_quad(const SkPoint pts[], SkScalar x, SkScalar y,
3059 SkTDArray<SkVector>* tangents) {
3060 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)) {
3061 return;
3062 }
3063 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)) {
3064 return;
3065 }
3066 SkScalar roots[2];
3067 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
3068 2 * (pts[1].fY - pts[0].fY),
3069 pts[0].fY - y,
3070 roots);
3071 for (int index = 0; index < n; ++index) {
3072 SkScalar t = roots[index];
3073 SkScalar C = pts[0].fX;
3074 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
3075 SkScalar B = 2 * (pts[1].fX - C);
3076 SkScalar xt = (A * t + B) * t + C;
3077 if (!SkScalarNearlyEqual(x, xt)) {
3078 continue;
3079 }
3080 tangents->push(SkEvalQuadTangentAt(pts, t));
3081 }
3082}
3083
3084static void tangent_line(const SkPoint pts[], SkScalar x, SkScalar y,
3085 SkTDArray<SkVector>* tangents) {
3086 SkScalar y0 = pts[0].fY;
3087 SkScalar y1 = pts[1].fY;
3088 if (!between(y0, y, y1)) {
3089 return;
3090 }
3091 SkScalar x0 = pts[0].fX;
3092 SkScalar x1 = pts[1].fX;
3093 if (!between(x0, x, x1)) {
3094 return;
3095 }
3096 SkScalar dx = x1 - x0;
3097 SkScalar dy = y1 - y0;
3098 if (!SkScalarNearlyEqual((x - x0) * dy, dx * (y - y0))) {
3099 return;
3100 }
3101 SkVector v;
3102 v.set(dx, dy);
3103 tangents->push(v);
3104}
3105
reed@google.com4db592c2013-10-30 17:39:43 +00003106static bool contains_inclusive(const SkRect& r, SkScalar x, SkScalar y) {
3107 return r.fLeft <= x && x <= r.fRight && r.fTop <= y && y <= r.fBottom;
3108}
3109
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003110bool SkPath::contains(SkScalar x, SkScalar y) const {
3111 bool isInverse = this->isInverseFillType();
3112 if (this->isEmpty()) {
3113 return isInverse;
3114 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003115
reed@google.com4db592c2013-10-30 17:39:43 +00003116 if (!contains_inclusive(this->getBounds(), x, y)) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003117 return isInverse;
3118 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003119
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003120 SkPath::Iter iter(*this, true);
3121 bool done = false;
3122 int w = 0;
caryclark9aacd902015-12-14 08:38:09 -08003123 int onCurveCount = 0;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003124 do {
3125 SkPoint pts[4];
3126 switch (iter.next(pts, false)) {
3127 case SkPath::kMove_Verb:
3128 case SkPath::kClose_Verb:
3129 break;
3130 case SkPath::kLine_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003131 w += winding_line(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003132 break;
3133 case SkPath::kQuad_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003134 w += winding_quad(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003135 break;
reed@google.com277c3f82013-05-31 15:17:50 +00003136 case SkPath::kConic_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003137 w += winding_conic(pts, x, y, iter.conicWeight(), &onCurveCount);
reed@google.com277c3f82013-05-31 15:17:50 +00003138 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003139 case SkPath::kCubic_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003140 w += winding_cubic(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003141 break;
3142 case SkPath::kDone_Verb:
3143 done = true;
3144 break;
reed@google.com277c3f82013-05-31 15:17:50 +00003145 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003146 } while (!done);
caryclark9aacd902015-12-14 08:38:09 -08003147 bool evenOddFill = SkPath::kEvenOdd_FillType == this->getFillType()
3148 || SkPath::kInverseEvenOdd_FillType == this->getFillType();
3149 if (evenOddFill) {
3150 w &= 1;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003151 }
caryclark9aacd902015-12-14 08:38:09 -08003152 if (w) {
3153 return !isInverse;
3154 }
3155 if (onCurveCount <= 1) {
3156 return SkToBool(onCurveCount) ^ isInverse;
3157 }
3158 if ((onCurveCount & 1) || evenOddFill) {
3159 return SkToBool(onCurveCount & 1) ^ isInverse;
3160 }
3161 // If the point touches an even number of curves, and the fill is winding, check for
3162 // coincidence. Count coincidence as places where the on curve points have identical tangents.
3163 iter.setPath(*this, true);
caryclark9cb5d752015-12-18 04:35:24 -08003164 done = false;
caryclark9aacd902015-12-14 08:38:09 -08003165 SkTDArray<SkVector> tangents;
3166 do {
3167 SkPoint pts[4];
3168 int oldCount = tangents.count();
3169 switch (iter.next(pts, false)) {
3170 case SkPath::kMove_Verb:
3171 case SkPath::kClose_Verb:
3172 break;
3173 case SkPath::kLine_Verb:
3174 tangent_line(pts, x, y, &tangents);
3175 break;
3176 case SkPath::kQuad_Verb:
3177 tangent_quad(pts, x, y, &tangents);
3178 break;
3179 case SkPath::kConic_Verb:
3180 tangent_conic(pts, x, y, iter.conicWeight(), &tangents);
3181 break;
3182 case SkPath::kCubic_Verb:
3183 tangent_cubic(pts, x, y, &tangents);
3184 break;
3185 case SkPath::kDone_Verb:
3186 done = true;
3187 break;
3188 }
3189 if (tangents.count() > oldCount) {
3190 int last = tangents.count() - 1;
3191 const SkVector& tangent = tangents[last];
3192 if (SkScalarNearlyZero(tangent.lengthSqd())) {
3193 tangents.remove(last);
3194 } else {
3195 for (int index = 0; index < last; ++index) {
3196 const SkVector& test = tangents[index];
3197 if (SkScalarNearlyZero(test.cross(tangent))
caryclark9cb5d752015-12-18 04:35:24 -08003198 && SkScalarSignAsInt(tangent.fX * test.fX) <= 0
3199 && SkScalarSignAsInt(tangent.fY * test.fY) <= 0) {
caryclark9aacd902015-12-14 08:38:09 -08003200 tangents.remove(last);
3201 tangents.removeShuffle(index);
3202 break;
3203 }
3204 }
3205 }
3206 }
3207 } while (!done);
3208 return SkToBool(tangents.count()) ^ isInverse;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003209}
fmalitaaa0df4e2015-12-01 09:13:23 -08003210
3211int SkPath::ConvertConicToQuads(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2,
3212 SkScalar w, SkPoint pts[], int pow2) {
3213 const SkConic conic(p0, p1, p2, w);
3214 return conic.chopIntoQuadsPOW2(pts, pow2);
3215}