blob: 0cb5853b772999a04e955cff6b834ba513407bc8 [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
bsalomon1978ce22016-05-31 10:42:16 -07008#include <cmath>
djsollen@google.com94e75ee2012-06-08 18:30:46 +00009#include "SkBuffer.h"
caryclark9aacd902015-12-14 08:38:09 -080010#include "SkCubicClipper.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
Mike Reeda99b6ce2017-02-04 11:04:26 -050017static float poly_eval(float A, float B, float C, float t) {
18 return (A * t + B) * t + C;
19}
20
21static float poly_eval(float A, float B, float C, float D, float t) {
22 return ((A * t + B) * t + C) * t + D;
23}
24
reed@android.com8a1c16f2008-12-17 15:59:43 +000025////////////////////////////////////////////////////////////////////////////
26
reed@google.com3563c9e2011-11-14 19:34:57 +000027/**
28 * Path.bounds is defined to be the bounds of all the control points.
29 * If we called bounds.join(r) we would skip r if r was empty, which breaks
30 * our promise. Hence we have a custom joiner that doesn't look at emptiness
31 */
32static void joinNoEmptyChecks(SkRect* dst, const SkRect& src) {
33 dst->fLeft = SkMinScalar(dst->fLeft, src.fLeft);
34 dst->fTop = SkMinScalar(dst->fTop, src.fTop);
35 dst->fRight = SkMaxScalar(dst->fRight, src.fRight);
36 dst->fBottom = SkMaxScalar(dst->fBottom, src.fBottom);
37}
38
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000039static bool is_degenerate(const SkPath& path) {
40 SkPath::Iter iter(path, false);
41 SkPoint pts[4];
42 return SkPath::kDone_Verb == iter.next(pts);
43}
44
bsalomon@google.com30c174b2012-11-13 14:36:42 +000045class SkAutoDisableDirectionCheck {
46public:
47 SkAutoDisableDirectionCheck(SkPath* path) : fPath(path) {
herb9f4dbca2015-09-28 11:05:47 -070048 fSaved = static_cast<SkPathPriv::FirstDirection>(fPath->fFirstDirection.load());
bsalomon@google.com30c174b2012-11-13 14:36:42 +000049 }
50
51 ~SkAutoDisableDirectionCheck() {
reed026beb52015-06-10 14:23:15 -070052 fPath->fFirstDirection = fSaved;
bsalomon@google.com30c174b2012-11-13 14:36:42 +000053 }
54
55private:
reed026beb52015-06-10 14:23:15 -070056 SkPath* fPath;
57 SkPathPriv::FirstDirection fSaved;
bsalomon@google.com30c174b2012-11-13 14:36:42 +000058};
commit-bot@chromium.orge61a86c2013-11-18 16:03:59 +000059#define SkAutoDisableDirectionCheck(...) SK_REQUIRE_LOCAL_VAR(SkAutoDisableDirectionCheck)
bsalomon@google.com30c174b2012-11-13 14:36:42 +000060
reed@android.com8a1c16f2008-12-17 15:59:43 +000061/* This guy's constructor/destructor bracket a path editing operation. It is
62 used when we know the bounds of the amount we are going to add to the path
63 (usually a new contour, but not required).
reed@google.comabf15c12011-01-18 20:35:51 +000064
reed@android.com8a1c16f2008-12-17 15:59:43 +000065 It captures some state about the path up front (i.e. if it already has a
robertphillips@google.comca0c8382013-09-26 12:18:23 +000066 cached bounds), and then if it can, it updates the cache bounds explicitly,
reed@android.comd252db02009-04-01 18:31:44 +000067 avoiding the need to revisit all of the points in getBounds().
reed@google.comabf15c12011-01-18 20:35:51 +000068
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000069 It also notes if the path was originally degenerate, and if so, sets
70 isConvex to true. Thus it can only be used if the contour being added is
robertphillips@google.com466310d2013-12-03 16:43:54 +000071 convex.
reed@android.com8a1c16f2008-12-17 15:59:43 +000072 */
73class SkAutoPathBoundsUpdate {
74public:
75 SkAutoPathBoundsUpdate(SkPath* path, const SkRect& r) : fRect(r) {
76 this->init(path);
77 }
78
79 SkAutoPathBoundsUpdate(SkPath* path, SkScalar left, SkScalar top,
80 SkScalar right, SkScalar bottom) {
81 fRect.set(left, top, right, bottom);
82 this->init(path);
83 }
reed@google.comabf15c12011-01-18 20:35:51 +000084
reed@android.com8a1c16f2008-12-17 15:59:43 +000085 ~SkAutoPathBoundsUpdate() {
reed@google.com44699382013-10-31 17:28:30 +000086 fPath->setConvexity(fDegenerate ? SkPath::kConvex_Convexity
87 : SkPath::kUnknown_Convexity);
robertphillips@google.comca0c8382013-09-26 12:18:23 +000088 if (fEmpty || fHasValidBounds) {
89 fPath->setBounds(fRect);
reed@android.com8a1c16f2008-12-17 15:59:43 +000090 }
91 }
reed@google.comabf15c12011-01-18 20:35:51 +000092
reed@android.com8a1c16f2008-12-17 15:59:43 +000093private:
reed@android.com6b82d1a2009-06-03 02:35:01 +000094 SkPath* fPath;
95 SkRect fRect;
robertphillips@google.comca0c8382013-09-26 12:18:23 +000096 bool fHasValidBounds;
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000097 bool fDegenerate;
reed@android.com6b82d1a2009-06-03 02:35:01 +000098 bool fEmpty;
reed@google.comabf15c12011-01-18 20:35:51 +000099
reed@android.com6b82d1a2009-06-03 02:35:01 +0000100 void init(SkPath* path) {
robertphillips@google.comca0c8382013-09-26 12:18:23 +0000101 // Cannot use fRect for our bounds unless we know it is sorted
102 fRect.sort();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000103 fPath = path;
reed@google.coma8790de2012-10-24 21:04:04 +0000104 // Mark the path's bounds as dirty if (1) they are, or (2) the path
105 // is non-finite, and therefore its bounds are not meaningful
robertphillips@google.comca0c8382013-09-26 12:18:23 +0000106 fHasValidBounds = path->hasComputedBounds() && path->isFinite();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000107 fEmpty = path->isEmpty();
robertphillips@google.comca0c8382013-09-26 12:18:23 +0000108 if (fHasValidBounds && !fEmpty) {
109 joinNoEmptyChecks(&fRect, fPath->getBounds());
110 }
111 fDegenerate = is_degenerate(*path);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000112 }
113};
commit-bot@chromium.orge61a86c2013-11-18 16:03:59 +0000114#define SkAutoPathBoundsUpdate(...) SK_REQUIRE_LOCAL_VAR(SkAutoPathBoundsUpdate)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000115
reed@android.com8a1c16f2008-12-17 15:59:43 +0000116////////////////////////////////////////////////////////////////////////////
117
118/*
119 Stores the verbs and points as they are given to us, with exceptions:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000120 - we only record "Close" if it was immediately preceeded by Move | Line | Quad | Cubic
reed@android.com8a1c16f2008-12-17 15:59:43 +0000121 - we insert a Move(0,0) if Line | Quad | Cubic is our first command
122
123 The iterator does more cleanup, especially if forceClose == true
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000124 1. If we encounter degenerate segments, remove them
125 2. if we encounter Close, return a cons'd up Line() first (if the curr-pt != start-pt)
126 3. if we encounter Move without a preceeding Close, and forceClose is true, goto #2
127 4. if we encounter Line | Quad | Cubic after Close, cons up a Move
reed@android.com8a1c16f2008-12-17 15:59:43 +0000128*/
129
130////////////////////////////////////////////////////////////////////////////
131
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000132// flag to require a moveTo if we begin with something else, like lineTo etc.
133#define INITIAL_LASTMOVETOINDEX_VALUE ~0
134
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000135SkPath::SkPath()
djsollen523cda32015-02-17 08:06:32 -0800136 : fPathRef(SkPathRef::CreateEmpty()) {
bungeman@google.coma5809a32013-06-21 15:13:34 +0000137 this->resetFields();
jvanverthb3eb6872014-10-24 07:12:51 -0700138 fIsVolatile = false;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000139}
140
141void SkPath::resetFields() {
142 //fPathRef is assumed to have been emptied by the caller.
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000143 fLastMoveToIndex = INITIAL_LASTMOVETOINDEX_VALUE;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000144 fFillType = kWinding_FillType;
reed@google.com04863fa2011-05-15 04:08:24 +0000145 fConvexity = kUnknown_Convexity;
reed026beb52015-06-10 14:23:15 -0700146 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000147
148 // We don't touch Android's fSourcePath. It's used to track texture garbage collection, so we
halcanary96fcdcc2015-08-27 07:41:13 -0700149 // don't want to muck with it if it's been set to something non-nullptr.
reed@android.com6b82d1a2009-06-03 02:35:01 +0000150}
reed@android.com8a1c16f2008-12-17 15:59:43 +0000151
bungeman@google.coma5809a32013-06-21 15:13:34 +0000152SkPath::SkPath(const SkPath& that)
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000153 : fPathRef(SkRef(that.fPathRef.get())) {
bungeman@google.coma5809a32013-06-21 15:13:34 +0000154 this->copyFields(that);
155 SkDEBUGCODE(that.validate();)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000156}
157
158SkPath::~SkPath() {
159 SkDEBUGCODE(this->validate();)
160}
161
bungeman@google.coma5809a32013-06-21 15:13:34 +0000162SkPath& SkPath::operator=(const SkPath& that) {
163 SkDEBUGCODE(that.validate();)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000164
bungeman@google.coma5809a32013-06-21 15:13:34 +0000165 if (this != &that) {
166 fPathRef.reset(SkRef(that.fPathRef.get()));
167 this->copyFields(that);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000168 }
169 SkDEBUGCODE(this->validate();)
170 return *this;
171}
172
bungeman@google.coma5809a32013-06-21 15:13:34 +0000173void SkPath::copyFields(const SkPath& that) {
174 //fPathRef is assumed to have been set by the caller.
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000175 fLastMoveToIndex = that.fLastMoveToIndex;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000176 fFillType = that.fFillType;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000177 fConvexity = that.fConvexity;
herb9f4dbca2015-09-28 11:05:47 -0700178 // Simulate fFirstDirection = that.fFirstDirection;
179 fFirstDirection.store(that.fFirstDirection.load());
jvanverthb3eb6872014-10-24 07:12:51 -0700180 fIsVolatile = that.fIsVolatile;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000181}
182
djsollen@google.com9c1a9672013-08-09 13:49:13 +0000183bool operator==(const SkPath& a, const SkPath& b) {
reed@android.com6b82d1a2009-06-03 02:35:01 +0000184 // note: don't need to look at isConvex or bounds, since just comparing the
185 // raw data is sufficient.
reed@android.com3abec1d2009-03-02 05:36:20 +0000186 return &a == &b ||
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000187 (a.fFillType == b.fFillType && *a.fPathRef.get() == *b.fPathRef.get());
reed@android.com3abec1d2009-03-02 05:36:20 +0000188}
189
bungeman@google.coma5809a32013-06-21 15:13:34 +0000190void SkPath::swap(SkPath& that) {
bungeman@google.coma5809a32013-06-21 15:13:34 +0000191 if (this != &that) {
bungeman77a53de2015-10-01 12:28:49 -0700192 fPathRef.swap(that.fPathRef);
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000193 SkTSwap<int>(fLastMoveToIndex, that.fLastMoveToIndex);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000194 SkTSwap<uint8_t>(fFillType, that.fFillType);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000195 SkTSwap<uint8_t>(fConvexity, that.fConvexity);
herb9f4dbca2015-09-28 11:05:47 -0700196 // Simulate SkTSwap<uint8_t>(fFirstDirection, that.fFirstDirection);
197 uint8_t temp = fFirstDirection;
198 fFirstDirection.store(that.fFirstDirection.load());
199 that.fFirstDirection.store(temp);
jvanverthb3eb6872014-10-24 07:12:51 -0700200 SkTSwap<SkBool8>(fIsVolatile, that.fIsVolatile);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000201 }
202}
203
caryclark8e7b19d2016-02-18 04:11:48 -0800204bool SkPath::isInterpolatable(const SkPath& compare) const {
205 int count = fPathRef->countVerbs();
206 if (count != compare.fPathRef->countVerbs()) {
207 return false;
208 }
209 if (!count) {
210 return true;
211 }
212 if (memcmp(fPathRef->verbsMemBegin(), compare.fPathRef->verbsMemBegin(),
213 count)) {
214 return false;
215 }
caryclarkdcd1fcc2016-02-18 09:00:01 -0800216 return !fPathRef->countWeights() ||
217 !SkToBool(memcmp(fPathRef->conicWeights(), compare.fPathRef->conicWeights(),
caryclark8e7b19d2016-02-18 04:11:48 -0800218 fPathRef->countWeights() * sizeof(*fPathRef->conicWeights())));
219}
220
221bool SkPath::interpolate(const SkPath& ending, SkScalar weight, SkPath* out) const {
222 int verbCount = fPathRef->countVerbs();
223 if (verbCount != ending.fPathRef->countVerbs()) {
224 return false;
225 }
caryclarka1105382016-02-18 06:13:25 -0800226 if (!verbCount) {
227 return true;
228 }
caryclark8e7b19d2016-02-18 04:11:48 -0800229 out->reset();
230 out->addPath(*this);
bungeman6bd52842016-10-27 09:30:08 -0700231 fPathRef->interpolate(*ending.fPathRef, weight, out->fPathRef.get());
caryclark8e7b19d2016-02-18 04:11:48 -0800232 return true;
233}
234
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000235static inline bool check_edge_against_rect(const SkPoint& p0,
236 const SkPoint& p1,
237 const SkRect& rect,
reed026beb52015-06-10 14:23:15 -0700238 SkPathPriv::FirstDirection dir) {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000239 const SkPoint* edgeBegin;
240 SkVector v;
reed026beb52015-06-10 14:23:15 -0700241 if (SkPathPriv::kCW_FirstDirection == dir) {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000242 v = p1 - p0;
243 edgeBegin = &p0;
244 } else {
245 v = p0 - p1;
246 edgeBegin = &p1;
247 }
248 if (v.fX || v.fY) {
249 // check the cross product of v with the vec from edgeBegin to each rect corner
Mike Reeda99b6ce2017-02-04 11:04:26 -0500250 SkScalar yL = v.fY * (rect.fLeft - edgeBegin->fX);
251 SkScalar xT = v.fX * (rect.fTop - edgeBegin->fY);
252 SkScalar yR = v.fY * (rect.fRight - edgeBegin->fX);
253 SkScalar xB = v.fX * (rect.fBottom - edgeBegin->fY);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000254 if ((xT < yL) || (xT < yR) || (xB < yL) || (xB < yR)) {
255 return false;
256 }
257 }
258 return true;
259}
260
261bool SkPath::conservativelyContainsRect(const SkRect& rect) const {
262 // This only handles non-degenerate convex paths currently.
263 if (kConvex_Convexity != this->getConvexity()) {
264 return false;
265 }
skia.committer@gmail.comcec8de62012-11-14 02:01:22 +0000266
reed026beb52015-06-10 14:23:15 -0700267 SkPathPriv::FirstDirection direction;
268 if (!SkPathPriv::CheapComputeFirstDirection(*this, &direction)) {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000269 return false;
270 }
271
272 SkPoint firstPt;
273 SkPoint prevPt;
Lee Salzmana19f0242017-01-12 13:06:21 -0500274 SkPath::Iter iter(*this, true);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000275 SkPath::Verb verb;
276 SkPoint pts[4];
277 SkDEBUGCODE(int moveCnt = 0;)
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000278 SkDEBUGCODE(int segmentCount = 0;)
279 SkDEBUGCODE(int closeCount = 0;)
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000280
Lee Salzmana19f0242017-01-12 13:06:21 -0500281 while ((verb = iter.next(pts, true, true)) != kDone_Verb) {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000282 int nextPt = -1;
283 switch (verb) {
284 case kMove_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000285 SkASSERT(!segmentCount && !closeCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000286 SkDEBUGCODE(++moveCnt);
287 firstPt = prevPt = pts[0];
288 break;
289 case kLine_Verb:
290 nextPt = 1;
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000291 SkASSERT(moveCnt && !closeCount);
292 SkDEBUGCODE(++segmentCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000293 break;
294 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000295 case kConic_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000296 SkASSERT(moveCnt && !closeCount);
297 SkDEBUGCODE(++segmentCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000298 nextPt = 2;
299 break;
300 case kCubic_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000301 SkASSERT(moveCnt && !closeCount);
302 SkDEBUGCODE(++segmentCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000303 nextPt = 3;
304 break;
305 case kClose_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000306 SkDEBUGCODE(++closeCount;)
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000307 break;
308 default:
309 SkDEBUGFAIL("unknown verb");
310 }
311 if (-1 != nextPt) {
reed220f9262014-12-17 08:21:04 -0800312 if (SkPath::kConic_Verb == verb) {
313 SkConic orig;
314 orig.set(pts, iter.conicWeight());
315 SkPoint quadPts[5];
316 int count = orig.chopIntoQuadsPOW2(quadPts, 1);
djsollenf2b340f2016-01-29 08:51:04 -0800317 SkASSERT_RELEASE(2 == count);
reed220f9262014-12-17 08:21:04 -0800318
319 if (!check_edge_against_rect(quadPts[0], quadPts[2], rect, direction)) {
320 return false;
321 }
322 if (!check_edge_against_rect(quadPts[2], quadPts[4], rect, direction)) {
323 return false;
324 }
325 } else {
326 if (!check_edge_against_rect(prevPt, pts[nextPt], rect, direction)) {
327 return false;
328 }
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000329 }
330 prevPt = pts[nextPt];
331 }
332 }
333
334 return check_edge_against_rect(prevPt, firstPt, rect, direction);
335}
336
robertphillips@google.com7101abe2013-10-29 22:45:37 +0000337uint32_t SkPath::getGenerationID() const {
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000338 uint32_t genID = fPathRef->genID();
djsollen523cda32015-02-17 08:06:32 -0800339#ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000340 SkASSERT((unsigned)fFillType < (1 << (32 - kPathRefGenIDBitCnt)));
341 genID |= static_cast<uint32_t>(fFillType) << kPathRefGenIDBitCnt;
342#endif
343 return genID;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000344}
djsollen@google.come63793a2012-03-21 15:39:03 +0000345
reed@android.com8a1c16f2008-12-17 15:59:43 +0000346void SkPath::reset() {
347 SkDEBUGCODE(this->validate();)
348
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000349 fPathRef.reset(SkPathRef::CreateEmpty());
bungeman@google.coma5809a32013-06-21 15:13:34 +0000350 this->resetFields();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000351}
352
353void SkPath::rewind() {
354 SkDEBUGCODE(this->validate();)
355
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000356 SkPathRef::Rewind(&fPathRef);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000357 this->resetFields();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000358}
359
fsb1475b02016-01-20 09:51:07 -0800360bool SkPath::isLastContourClosed() const {
361 int verbCount = fPathRef->countVerbs();
362 if (0 == verbCount) {
363 return false;
364 }
365 return kClose_Verb == fPathRef->atVerb(verbCount - 1);
366}
367
reed@google.com7e6c4d12012-05-10 14:05:43 +0000368bool SkPath::isLine(SkPoint line[2]) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000369 int verbCount = fPathRef->countVerbs();
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000370
commit-bot@chromium.orga62efcc2013-08-05 13:23:13 +0000371 if (2 == verbCount) {
372 SkASSERT(kMove_Verb == fPathRef->atVerb(0));
373 if (kLine_Verb == fPathRef->atVerb(1)) {
374 SkASSERT(2 == fPathRef->countPoints());
reed@google.com7e6c4d12012-05-10 14:05:43 +0000375 if (line) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000376 const SkPoint* pts = fPathRef->points();
reed@google.com7e6c4d12012-05-10 14:05:43 +0000377 line[0] = pts[0];
378 line[1] = pts[1];
379 }
380 return true;
381 }
382 }
383 return false;
384}
385
caryclark@google.comf1316942011-07-26 19:54:45 +0000386/*
387 Determines if path is a rect by keeping track of changes in direction
388 and looking for a loop either clockwise or counterclockwise.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000389
caryclark@google.comf1316942011-07-26 19:54:45 +0000390 The direction is computed such that:
391 0: vertical up
caryclark@google.comf68154a2012-11-21 15:18:06 +0000392 1: horizontal left
caryclark@google.comf1316942011-07-26 19:54:45 +0000393 2: vertical down
caryclark@google.comf68154a2012-11-21 15:18:06 +0000394 3: horizontal right
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000395
caryclark@google.comf1316942011-07-26 19:54:45 +0000396A rectangle cycles up/right/down/left or up/left/down/right.
397
398The test fails if:
399 The path is closed, and followed by a line.
400 A second move creates a new endpoint.
401 A diagonal line is parsed.
402 There's more than four changes of direction.
403 There's a discontinuity on the line (e.g., a move in the middle)
404 The line reverses direction.
caryclark@google.comf1316942011-07-26 19:54:45 +0000405 The path contains a quadratic or cubic.
406 The path contains fewer than four points.
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000407 *The rectangle doesn't complete a cycle.
408 *The final point isn't equal to the first point.
409
410 *These last two conditions we relax if we have a 3-edge path that would
411 form a rectangle if it were closed (as we do when we fill a path)
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000412
caryclark@google.comf1316942011-07-26 19:54:45 +0000413It's OK if the path has:
414 Several colinear line segments composing a rectangle side.
415 Single points on the rectangle side.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000416
caryclark@google.comf1316942011-07-26 19:54:45 +0000417The direction takes advantage of the corners found since opposite sides
418must travel in opposite directions.
419
420FIXME: Allow colinear quads and cubics to be treated like lines.
421FIXME: If the API passes fill-only, return true if the filled stroke
422 is a rectangle, though the caller failed to close the path.
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000423
424 first,last,next direction state-machine:
425 0x1 is set if the segment is horizontal
426 0x2 is set if the segment is moving to the right or down
427 thus:
428 two directions are opposites iff (dirA ^ dirB) == 0x2
429 two directions are perpendicular iff (dirA ^ dirB) == 0x1
skia.committer@gmail.comf5e67c12014-01-16 07:01:48 +0000430
caryclark@google.comf1316942011-07-26 19:54:45 +0000431 */
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000432static int rect_make_dir(SkScalar dx, SkScalar dy) {
433 return ((0 != dx) << 0) | ((dx > 0 || dy > 0) << 1);
434}
caryclark@google.comf68154a2012-11-21 15:18:06 +0000435bool SkPath::isRectContour(bool allowPartial, int* currVerb, const SkPoint** ptsPtr,
436 bool* isClosed, Direction* direction) const {
caryclark@google.comf1316942011-07-26 19:54:45 +0000437 int corners = 0;
438 SkPoint first, last;
caryclark@google.com56f233a2012-11-19 13:06:06 +0000439 const SkPoint* pts = *ptsPtr;
halcanary96fcdcc2015-08-27 07:41:13 -0700440 const SkPoint* savePts = nullptr;
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000441 first.set(0, 0);
442 last.set(0, 0);
443 int firstDirection = 0;
444 int lastDirection = 0;
445 int nextDirection = 0;
446 bool closedOrMoved = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000447 bool autoClose = false;
caryclark95bc5f32015-04-08 08:34:15 -0700448 bool insertClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000449 int verbCnt = fPathRef->countVerbs();
caryclark@google.com56f233a2012-11-19 13:06:06 +0000450 while (*currVerb < verbCnt && (!allowPartial || !autoClose)) {
caryclark95bc5f32015-04-08 08:34:15 -0700451 uint8_t verb = insertClose ? (uint8_t) kClose_Verb : fPathRef->atVerb(*currVerb);
452 switch (verb) {
caryclark@google.comf1316942011-07-26 19:54:45 +0000453 case kClose_Verb:
caryclark@google.com56f233a2012-11-19 13:06:06 +0000454 savePts = pts;
455 pts = *ptsPtr;
caryclark@google.comf1316942011-07-26 19:54:45 +0000456 autoClose = true;
caryclark95bc5f32015-04-08 08:34:15 -0700457 insertClose = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000458 case kLine_Verb: {
459 SkScalar left = last.fX;
460 SkScalar top = last.fY;
461 SkScalar right = pts->fX;
462 SkScalar bottom = pts->fY;
463 ++pts;
464 if (left != right && top != bottom) {
465 return false; // diagonal
466 }
467 if (left == right && top == bottom) {
468 break; // single point on side OK
469 }
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000470 nextDirection = rect_make_dir(right - left, bottom - top);
caryclark@google.comf1316942011-07-26 19:54:45 +0000471 if (0 == corners) {
472 firstDirection = nextDirection;
473 first = last;
474 last = pts[-1];
475 corners = 1;
476 closedOrMoved = false;
477 break;
478 }
479 if (closedOrMoved) {
480 return false; // closed followed by a line
481 }
caryclark@google.combfe90372012-11-21 13:56:20 +0000482 if (autoClose && nextDirection == firstDirection) {
483 break; // colinear with first
484 }
caryclark@google.comf1316942011-07-26 19:54:45 +0000485 closedOrMoved = autoClose;
486 if (lastDirection != nextDirection) {
487 if (++corners > 4) {
488 return false; // too many direction changes
489 }
490 }
491 last = pts[-1];
492 if (lastDirection == nextDirection) {
493 break; // colinear segment
494 }
495 // Possible values for corners are 2, 3, and 4.
496 // When corners == 3, nextDirection opposes firstDirection.
497 // Otherwise, nextDirection at corner 2 opposes corner 4.
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000498 int turn = firstDirection ^ (corners - 1);
caryclark@google.comf1316942011-07-26 19:54:45 +0000499 int directionCycle = 3 == corners ? 0 : nextDirection ^ turn;
500 if ((directionCycle ^ turn) != nextDirection) {
501 return false; // direction didn't follow cycle
502 }
503 break;
504 }
505 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000506 case kConic_Verb:
caryclark@google.comf1316942011-07-26 19:54:45 +0000507 case kCubic_Verb:
508 return false; // quadratic, cubic not allowed
509 case kMove_Verb:
caryclark95bc5f32015-04-08 08:34:15 -0700510 if (allowPartial && !autoClose && firstDirection) {
511 insertClose = true;
512 *currVerb -= 1; // try move again afterwards
513 goto addMissingClose;
514 }
caryclark@google.comf1316942011-07-26 19:54:45 +0000515 last = *pts++;
516 closedOrMoved = true;
517 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000518 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000519 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000520 break;
caryclark@google.comf1316942011-07-26 19:54:45 +0000521 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000522 *currVerb += 1;
caryclark@google.comf1316942011-07-26 19:54:45 +0000523 lastDirection = nextDirection;
caryclark95bc5f32015-04-08 08:34:15 -0700524addMissingClose:
525 ;
caryclark@google.comf1316942011-07-26 19:54:45 +0000526 }
527 // Success if 4 corners and first point equals last
caryclark@google.combfe90372012-11-21 13:56:20 +0000528 bool result = 4 == corners && (first == last || autoClose);
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000529 if (!result) {
530 // check if we are just an incomplete rectangle, in which case we can
531 // return true, but not claim to be closed.
532 // e.g.
533 // 3 sided rectangle
534 // 4 sided but the last edge is not long enough to reach the start
535 //
536 SkScalar closeX = first.x() - last.x();
537 SkScalar closeY = first.y() - last.y();
538 if (closeX && closeY) {
539 return false; // we're diagonal, abort (can we ever reach this?)
540 }
541 int closeDirection = rect_make_dir(closeX, closeY);
542 // make sure the close-segment doesn't double-back on itself
543 if (3 == corners || (4 == corners && closeDirection == lastDirection)) {
544 result = true;
545 autoClose = false; // we are not closed
546 }
547 }
caryclark@google.combfe90372012-11-21 13:56:20 +0000548 if (savePts) {
549 *ptsPtr = savePts;
550 }
caryclark@google.comf68154a2012-11-21 15:18:06 +0000551 if (result && isClosed) {
552 *isClosed = autoClose;
553 }
554 if (result && direction) {
sugoi@google.com12b4e272012-12-06 20:13:11 +0000555 *direction = firstDirection == ((lastDirection + 1) & 3) ? kCCW_Direction : kCW_Direction;
caryclark@google.comf68154a2012-11-21 15:18:06 +0000556 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000557 return result;
558}
559
robertphillips4f662e62014-12-29 14:06:51 -0800560bool SkPath::isRect(SkRect* rect, bool* isClosed, Direction* direction) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000561 SkDEBUGCODE(this->validate();)
562 int currVerb = 0;
563 const SkPoint* pts = fPathRef->points();
robertphillipsfe7c4272014-12-29 11:36:39 -0800564 const SkPoint* first = pts;
robertphillips4f662e62014-12-29 14:06:51 -0800565 if (!this->isRectContour(false, &currVerb, &pts, isClosed, direction)) {
robertphillipsfe7c4272014-12-29 11:36:39 -0800566 return false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000567 }
robertphillipsfe7c4272014-12-29 11:36:39 -0800568 if (rect) {
robertphillips4f662e62014-12-29 14:06:51 -0800569 int32_t num = SkToS32(pts - first);
570 if (num) {
571 rect->set(first, num);
robertphillipsfe7c4272014-12-29 11:36:39 -0800572 } else {
573 // 'pts' isn't updated for open rects
574 *rect = this->getBounds();
575 }
576 }
577 return true;
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000578}
skia.committer@gmail.com020b25b2013-06-22 07:00:58 +0000579
caryclark95bc5f32015-04-08 08:34:15 -0700580bool SkPath::isNestedFillRects(SkRect rects[2], Direction dirs[2]) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000581 SkDEBUGCODE(this->validate();)
582 int currVerb = 0;
583 const SkPoint* pts = fPathRef->points();
584 const SkPoint* first = pts;
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000585 Direction testDirs[2];
halcanary96fcdcc2015-08-27 07:41:13 -0700586 if (!isRectContour(true, &currVerb, &pts, nullptr, &testDirs[0])) {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000587 return false;
588 }
589 const SkPoint* last = pts;
590 SkRect testRects[2];
caryclark95bc5f32015-04-08 08:34:15 -0700591 bool isClosed;
592 if (isRectContour(false, &currVerb, &pts, &isClosed, &testDirs[1])) {
scroggo@google.com614f9e32013-05-09 18:05:32 +0000593 testRects[0].set(first, SkToS32(last - first));
caryclark95bc5f32015-04-08 08:34:15 -0700594 if (!isClosed) {
595 pts = fPathRef->points() + fPathRef->countPoints();
596 }
scroggo@google.com614f9e32013-05-09 18:05:32 +0000597 testRects[1].set(last, SkToS32(pts - last));
caryclark@google.com56f233a2012-11-19 13:06:06 +0000598 if (testRects[0].contains(testRects[1])) {
599 if (rects) {
600 rects[0] = testRects[0];
601 rects[1] = testRects[1];
602 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000603 if (dirs) {
604 dirs[0] = testDirs[0];
605 dirs[1] = testDirs[1];
606 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000607 return true;
608 }
609 if (testRects[1].contains(testRects[0])) {
610 if (rects) {
611 rects[0] = testRects[1];
612 rects[1] = testRects[0];
613 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000614 if (dirs) {
615 dirs[0] = testDirs[1];
616 dirs[1] = testDirs[0];
617 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000618 return true;
619 }
620 }
621 return false;
622}
623
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000624int SkPath::countPoints() const {
625 return fPathRef->countPoints();
626}
627
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000628int SkPath::getPoints(SkPoint dst[], int max) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000629 SkDEBUGCODE(this->validate();)
630
631 SkASSERT(max >= 0);
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000632 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000633 int count = SkMin32(max, fPathRef->countPoints());
mtklein8bf5d172015-12-09 13:15:07 -0800634 sk_careful_memcpy(dst, fPathRef->points(), count * sizeof(SkPoint));
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000635 return fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000636}
637
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000638SkPoint SkPath::getPoint(int index) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000639 if ((unsigned)index < (unsigned)fPathRef->countPoints()) {
640 return fPathRef->atPoint(index);
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000641 }
642 return SkPoint::Make(0, 0);
643}
644
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000645int SkPath::countVerbs() const {
646 return fPathRef->countVerbs();
647}
648
649static inline void copy_verbs_reverse(uint8_t* inorderDst,
650 const uint8_t* reversedSrc,
651 int count) {
652 for (int i = 0; i < count; ++i) {
653 inorderDst[i] = reversedSrc[~i];
654 }
655}
656
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000657int SkPath::getVerbs(uint8_t dst[], int max) const {
658 SkDEBUGCODE(this->validate();)
659
660 SkASSERT(max >= 0);
661 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000662 int count = SkMin32(max, fPathRef->countVerbs());
663 copy_verbs_reverse(dst, fPathRef->verbs(), count);
664 return fPathRef->countVerbs();
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000665}
666
reed@google.com294dd7b2011-10-11 11:58:32 +0000667bool SkPath::getLastPt(SkPoint* lastPt) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000668 SkDEBUGCODE(this->validate();)
669
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000670 int count = fPathRef->countPoints();
reed@google.com294dd7b2011-10-11 11:58:32 +0000671 if (count > 0) {
672 if (lastPt) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000673 *lastPt = fPathRef->atPoint(count - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000674 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000675 return true;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000676 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000677 if (lastPt) {
678 lastPt->set(0, 0);
679 }
680 return false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000681}
682
caryclarkaec25102015-04-29 08:28:30 -0700683void SkPath::setPt(int index, SkScalar x, SkScalar y) {
684 SkDEBUGCODE(this->validate();)
685
686 int count = fPathRef->countPoints();
687 if (count <= index) {
688 return;
689 } else {
690 SkPathRef::Editor ed(&fPathRef);
691 ed.atPoint(index)->set(x, y);
692 }
693}
694
reed@android.com8a1c16f2008-12-17 15:59:43 +0000695void SkPath::setLastPt(SkScalar x, SkScalar y) {
696 SkDEBUGCODE(this->validate();)
697
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000698 int count = fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000699 if (count == 0) {
700 this->moveTo(x, y);
701 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000702 SkPathRef::Editor ed(&fPathRef);
703 ed.atPoint(count-1)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000704 }
705}
706
reed@google.com04863fa2011-05-15 04:08:24 +0000707void SkPath::setConvexity(Convexity c) {
708 if (fConvexity != c) {
709 fConvexity = c;
reed@google.com04863fa2011-05-15 04:08:24 +0000710 }
711}
712
reed@android.com8a1c16f2008-12-17 15:59:43 +0000713//////////////////////////////////////////////////////////////////////////////
714// Construction methods
715
reed026beb52015-06-10 14:23:15 -0700716#define DIRTY_AFTER_EDIT \
717 do { \
718 fConvexity = kUnknown_Convexity; \
719 fFirstDirection = SkPathPriv::kUnknown_FirstDirection; \
reed@google.comb54455e2011-05-16 14:16:04 +0000720 } while (0)
721
reed@android.com8a1c16f2008-12-17 15:59:43 +0000722void SkPath::incReserve(U16CPU inc) {
723 SkDEBUGCODE(this->validate();)
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000724 SkPathRef::Editor(&fPathRef, inc, inc);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000725 SkDEBUGCODE(this->validate();)
726}
727
728void SkPath::moveTo(SkScalar x, SkScalar y) {
729 SkDEBUGCODE(this->validate();)
730
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000731 SkPathRef::Editor ed(&fPathRef);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000732
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000733 // remember our index
734 fLastMoveToIndex = fPathRef->countPoints();
735
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000736 ed.growForVerb(kMove_Verb)->set(x, y);
bsalomonb17c1292014-08-28 14:04:55 -0700737
738 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000739}
740
741void SkPath::rMoveTo(SkScalar x, SkScalar y) {
742 SkPoint pt;
743 this->getLastPt(&pt);
744 this->moveTo(pt.fX + x, pt.fY + y);
745}
746
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000747void SkPath::injectMoveToIfNeeded() {
748 if (fLastMoveToIndex < 0) {
749 SkScalar x, y;
750 if (fPathRef->countVerbs() == 0) {
751 x = y = 0;
752 } else {
753 const SkPoint& pt = fPathRef->atPoint(~fLastMoveToIndex);
754 x = pt.fX;
755 y = pt.fY;
756 }
757 this->moveTo(x, y);
758 }
759}
760
reed@android.com8a1c16f2008-12-17 15:59:43 +0000761void SkPath::lineTo(SkScalar x, SkScalar y) {
762 SkDEBUGCODE(this->validate();)
763
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000764 this->injectMoveToIfNeeded();
765
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000766 SkPathRef::Editor ed(&fPathRef);
767 ed.growForVerb(kLine_Verb)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000768
reed@google.comb54455e2011-05-16 14:16:04 +0000769 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000770}
771
772void SkPath::rLineTo(SkScalar x, SkScalar y) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000773 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000774 SkPoint pt;
775 this->getLastPt(&pt);
776 this->lineTo(pt.fX + x, pt.fY + y);
777}
778
779void SkPath::quadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
780 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000781
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000782 this->injectMoveToIfNeeded();
783
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000784 SkPathRef::Editor ed(&fPathRef);
785 SkPoint* pts = ed.growForVerb(kQuad_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000786 pts[0].set(x1, y1);
787 pts[1].set(x2, y2);
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000788
reed@google.comb54455e2011-05-16 14:16:04 +0000789 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000790}
791
792void SkPath::rQuadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000793 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000794 SkPoint pt;
795 this->getLastPt(&pt);
796 this->quadTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2);
797}
798
reed@google.com277c3f82013-05-31 15:17:50 +0000799void SkPath::conicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
800 SkScalar w) {
801 // check for <= 0 or NaN with this test
802 if (!(w > 0)) {
803 this->lineTo(x2, y2);
804 } else if (!SkScalarIsFinite(w)) {
805 this->lineTo(x1, y1);
806 this->lineTo(x2, y2);
807 } else if (SK_Scalar1 == w) {
808 this->quadTo(x1, y1, x2, y2);
809 } else {
810 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000811
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000812 this->injectMoveToIfNeeded();
813
reed@google.com277c3f82013-05-31 15:17:50 +0000814 SkPathRef::Editor ed(&fPathRef);
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000815 SkPoint* pts = ed.growForVerb(kConic_Verb, w);
reed@google.com277c3f82013-05-31 15:17:50 +0000816 pts[0].set(x1, y1);
817 pts[1].set(x2, y2);
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000818
reed@google.com277c3f82013-05-31 15:17:50 +0000819 DIRTY_AFTER_EDIT;
820 }
821}
822
823void SkPath::rConicTo(SkScalar dx1, SkScalar dy1, SkScalar dx2, SkScalar dy2,
824 SkScalar w) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000825 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@google.com277c3f82013-05-31 15:17:50 +0000826 SkPoint pt;
827 this->getLastPt(&pt);
828 this->conicTo(pt.fX + dx1, pt.fY + dy1, pt.fX + dx2, pt.fY + dy2, w);
829}
830
reed@android.com8a1c16f2008-12-17 15:59:43 +0000831void SkPath::cubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
832 SkScalar x3, SkScalar y3) {
833 SkDEBUGCODE(this->validate();)
834
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000835 this->injectMoveToIfNeeded();
836
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000837 SkPathRef::Editor ed(&fPathRef);
838 SkPoint* pts = ed.growForVerb(kCubic_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000839 pts[0].set(x1, y1);
840 pts[1].set(x2, y2);
841 pts[2].set(x3, y3);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000842
reed@google.comb54455e2011-05-16 14:16:04 +0000843 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000844}
845
846void SkPath::rCubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
847 SkScalar x3, SkScalar y3) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000848 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000849 SkPoint pt;
850 this->getLastPt(&pt);
851 this->cubicTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2,
852 pt.fX + x3, pt.fY + y3);
853}
854
855void SkPath::close() {
856 SkDEBUGCODE(this->validate();)
857
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000858 int count = fPathRef->countVerbs();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000859 if (count > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000860 switch (fPathRef->atVerb(count - 1)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000861 case kLine_Verb:
862 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000863 case kConic_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +0000864 case kCubic_Verb:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000865 case kMove_Verb: {
866 SkPathRef::Editor ed(&fPathRef);
867 ed.growForVerb(kClose_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000868 break;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000869 }
reed@google.com277c3f82013-05-31 15:17:50 +0000870 case kClose_Verb:
reed@google.comfa2f2a42013-05-30 15:29:48 +0000871 // don't add a close if it's the first verb or a repeat
reed@google.com7950a9e2013-05-30 14:57:55 +0000872 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000873 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000874 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000875 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000876 }
877 }
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000878
879 // signal that we need a moveTo to follow us (unless we're done)
880#if 0
881 if (fLastMoveToIndex >= 0) {
882 fLastMoveToIndex = ~fLastMoveToIndex;
883 }
884#else
885 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
886#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000887}
888
889///////////////////////////////////////////////////////////////////////////////
reed@google.comabf15c12011-01-18 20:35:51 +0000890
fmalitac08d53e2015-11-17 09:53:29 -0800891namespace {
892
893template <unsigned N>
894class PointIterator {
895public:
896 PointIterator(SkPath::Direction dir, unsigned startIndex)
897 : fCurrent(startIndex % N)
898 , fAdvance(dir == SkPath::kCW_Direction ? 1 : N - 1) { }
899
900 const SkPoint& current() const {
901 SkASSERT(fCurrent < N);
902 return fPts[fCurrent];
903 }
904
905 const SkPoint& next() {
906 fCurrent = (fCurrent + fAdvance) % N;
907 return this->current();
908 }
909
910protected:
911 SkPoint fPts[N];
912
913private:
914 unsigned fCurrent;
915 unsigned fAdvance;
916};
917
918class RectPointIterator : public PointIterator<4> {
919public:
920 RectPointIterator(const SkRect& rect, SkPath::Direction dir, unsigned startIndex)
921 : PointIterator(dir, startIndex) {
922
923 fPts[0] = SkPoint::Make(rect.fLeft, rect.fTop);
924 fPts[1] = SkPoint::Make(rect.fRight, rect.fTop);
925 fPts[2] = SkPoint::Make(rect.fRight, rect.fBottom);
926 fPts[3] = SkPoint::Make(rect.fLeft, rect.fBottom);
927 }
928};
929
930class OvalPointIterator : public PointIterator<4> {
931public:
932 OvalPointIterator(const SkRect& oval, SkPath::Direction dir, unsigned startIndex)
933 : PointIterator(dir, startIndex) {
934
935 const SkScalar cx = oval.centerX();
936 const SkScalar cy = oval.centerY();
937
938 fPts[0] = SkPoint::Make(cx, oval.fTop);
939 fPts[1] = SkPoint::Make(oval.fRight, cy);
940 fPts[2] = SkPoint::Make(cx, oval.fBottom);
941 fPts[3] = SkPoint::Make(oval.fLeft, cy);
942 }
943};
944
945class RRectPointIterator : public PointIterator<8> {
946public:
947 RRectPointIterator(const SkRRect& rrect, SkPath::Direction dir, unsigned startIndex)
948 : PointIterator(dir, startIndex) {
949
950 const SkRect& bounds = rrect.getBounds();
951 const SkScalar L = bounds.fLeft;
952 const SkScalar T = bounds.fTop;
953 const SkScalar R = bounds.fRight;
954 const SkScalar B = bounds.fBottom;
955
956 fPts[0] = SkPoint::Make(L + rrect.radii(SkRRect::kUpperLeft_Corner).fX, T);
957 fPts[1] = SkPoint::Make(R - rrect.radii(SkRRect::kUpperRight_Corner).fX, T);
958 fPts[2] = SkPoint::Make(R, T + rrect.radii(SkRRect::kUpperRight_Corner).fY);
959 fPts[3] = SkPoint::Make(R, B - rrect.radii(SkRRect::kLowerRight_Corner).fY);
960 fPts[4] = SkPoint::Make(R - rrect.radii(SkRRect::kLowerRight_Corner).fX, B);
961 fPts[5] = SkPoint::Make(L + rrect.radii(SkRRect::kLowerLeft_Corner).fX, B);
962 fPts[6] = SkPoint::Make(L, B - rrect.radii(SkRRect::kLowerLeft_Corner).fY);
963 fPts[7] = SkPoint::Make(L, T + rrect.radii(SkRRect::kUpperLeft_Corner).fY);
964 }
965};
966
967} // anonymous namespace
968
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000969static void assert_known_direction(int dir) {
970 SkASSERT(SkPath::kCW_Direction == dir || SkPath::kCCW_Direction == dir);
971}
972
reed@android.com8a1c16f2008-12-17 15:59:43 +0000973void SkPath::addRect(const SkRect& rect, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -0800974 this->addRect(rect, dir, 0);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000975}
976
977void SkPath::addRect(SkScalar left, SkScalar top, SkScalar right,
978 SkScalar bottom, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -0800979 this->addRect(SkRect::MakeLTRB(left, top, right, bottom), dir, 0);
980}
981
982void SkPath::addRect(const SkRect &rect, Direction dir, unsigned startIndex) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000983 assert_known_direction(dir);
reed026beb52015-06-10 14:23:15 -0700984 fFirstDirection = this->hasOnlyMoveTos() ?
fmalitac08d53e2015-11-17 09:53:29 -0800985 (SkPathPriv::FirstDirection)dir : SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000986 SkAutoDisableDirectionCheck addc(this);
fmalitac08d53e2015-11-17 09:53:29 -0800987 SkAutoPathBoundsUpdate apbu(this, rect);
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000988
fmalitac08d53e2015-11-17 09:53:29 -0800989 SkDEBUGCODE(int initialVerbCount = this->countVerbs());
reed@android.com8a1c16f2008-12-17 15:59:43 +0000990
fmalitac08d53e2015-11-17 09:53:29 -0800991 const int kVerbs = 5; // moveTo + 3x lineTo + close
992 this->incReserve(kVerbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000993
fmalitac08d53e2015-11-17 09:53:29 -0800994 RectPointIterator iter(rect, dir, startIndex);
995
996 this->moveTo(iter.current());
997 this->lineTo(iter.next());
998 this->lineTo(iter.next());
999 this->lineTo(iter.next());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001000 this->close();
fmalitac08d53e2015-11-17 09:53:29 -08001001
1002 SkASSERT(this->countVerbs() == initialVerbCount + kVerbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001003}
1004
reed@google.com744faba2012-05-29 19:54:52 +00001005void SkPath::addPoly(const SkPoint pts[], int count, bool close) {
1006 SkDEBUGCODE(this->validate();)
1007 if (count <= 0) {
1008 return;
1009 }
1010
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +00001011 fLastMoveToIndex = fPathRef->countPoints();
1012
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001013 // +close makes room for the extra kClose_Verb
1014 SkPathRef::Editor ed(&fPathRef, count+close, count);
1015
1016 ed.growForVerb(kMove_Verb)->set(pts[0].fX, pts[0].fY);
reed@google.com744faba2012-05-29 19:54:52 +00001017 if (count > 1) {
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001018 SkPoint* p = ed.growForRepeatedVerb(kLine_Verb, count - 1);
1019 memcpy(p, &pts[1], (count-1) * sizeof(SkPoint));
reed@google.com744faba2012-05-29 19:54:52 +00001020 }
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001021
reed@google.com744faba2012-05-29 19:54:52 +00001022 if (close) {
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001023 ed.growForVerb(kClose_Verb);
caryclark63c684a2015-02-25 09:04:04 -08001024 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
reed@google.com744faba2012-05-29 19:54:52 +00001025 }
1026
reed@google.com744faba2012-05-29 19:54:52 +00001027 DIRTY_AFTER_EDIT;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001028 SkDEBUGCODE(this->validate();)
reed@google.com744faba2012-05-29 19:54:52 +00001029}
1030
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001031#include "SkGeometry.h"
1032
reedf90ea012015-01-29 12:03:58 -08001033static bool arc_is_lone_point(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
1034 SkPoint* pt) {
1035 if (0 == sweepAngle && (0 == startAngle || SkIntToScalar(360) == startAngle)) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001036 // Chrome uses this path to move into and out of ovals. If not
1037 // treated as a special case the moves can distort the oval's
1038 // bounding box (and break the circle special case).
reedf90ea012015-01-29 12:03:58 -08001039 pt->set(oval.fRight, oval.centerY());
1040 return true;
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001041 } else if (0 == oval.width() && 0 == oval.height()) {
1042 // Chrome will sometimes create 0 radius round rects. Having degenerate
1043 // quad segments in the path prevents the path from being recognized as
1044 // a rect.
1045 // TODO: optimizing the case where only one of width or height is zero
1046 // should also be considered. This case, however, doesn't seem to be
1047 // as common as the single point case.
reedf90ea012015-01-29 12:03:58 -08001048 pt->set(oval.fRight, oval.fTop);
1049 return true;
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001050 }
reedf90ea012015-01-29 12:03:58 -08001051 return false;
1052}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001053
reedd5d27d92015-02-09 13:54:43 -08001054// Return the unit vectors pointing at the start/stop points for the given start/sweep angles
1055//
1056static void angles_to_unit_vectors(SkScalar startAngle, SkScalar sweepAngle,
1057 SkVector* startV, SkVector* stopV, SkRotationDirection* dir) {
1058 startV->fY = SkScalarSinCos(SkDegreesToRadians(startAngle), &startV->fX);
1059 stopV->fY = SkScalarSinCos(SkDegreesToRadians(startAngle + sweepAngle), &stopV->fX);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001060
1061 /* If the sweep angle is nearly (but less than) 360, then due to precision
reedd5d27d92015-02-09 13:54:43 -08001062 loss in radians-conversion and/or sin/cos, we may end up with coincident
1063 vectors, which will fool SkBuildQuadArc into doing nothing (bad) instead
1064 of drawing a nearly complete circle (good).
1065 e.g. canvas.drawArc(0, 359.99, ...)
1066 -vs- canvas.drawArc(0, 359.9, ...)
1067 We try to detect this edge case, and tweak the stop vector
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001068 */
reedd5d27d92015-02-09 13:54:43 -08001069 if (*startV == *stopV) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001070 SkScalar sw = SkScalarAbs(sweepAngle);
1071 if (sw < SkIntToScalar(360) && sw > SkIntToScalar(359)) {
1072 SkScalar stopRad = SkDegreesToRadians(startAngle + sweepAngle);
1073 // make a guess at a tiny angle (in radians) to tweak by
1074 SkScalar deltaRad = SkScalarCopySign(SK_Scalar1/512, sweepAngle);
1075 // not sure how much will be enough, so we use a loop
1076 do {
1077 stopRad -= deltaRad;
reedd5d27d92015-02-09 13:54:43 -08001078 stopV->fY = SkScalarSinCos(stopRad, &stopV->fX);
1079 } while (*startV == *stopV);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001080 }
1081 }
reedd5d27d92015-02-09 13:54:43 -08001082 *dir = sweepAngle > 0 ? kCW_SkRotationDirection : kCCW_SkRotationDirection;
1083}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001084
reed9e779d42015-02-17 11:43:14 -08001085/**
1086 * If this returns 0, then the caller should just line-to the singlePt, else it should
1087 * ignore singlePt and append the specified number of conics.
1088 */
reedd5d27d92015-02-09 13:54:43 -08001089static int build_arc_conics(const SkRect& oval, const SkVector& start, const SkVector& stop,
reed9e779d42015-02-17 11:43:14 -08001090 SkRotationDirection dir, SkConic conics[SkConic::kMaxConicsForArc],
1091 SkPoint* singlePt) {
reedd5d27d92015-02-09 13:54:43 -08001092 SkMatrix matrix;
1093
1094 matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height()));
1095 matrix.postTranslate(oval.centerX(), oval.centerY());
1096
reed9e779d42015-02-17 11:43:14 -08001097 int count = SkConic::BuildUnitArc(start, stop, dir, &matrix, conics);
1098 if (0 == count) {
xidachen95e34a32016-10-19 10:24:28 -07001099 matrix.mapXY(stop.x(), stop.y(), singlePt);
reed9e779d42015-02-17 11:43:14 -08001100 }
1101 return count;
reedd5d27d92015-02-09 13:54:43 -08001102}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001103
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001104void SkPath::addRoundRect(const SkRect& rect, const SkScalar radii[],
reed@android.com8a1c16f2008-12-17 15:59:43 +00001105 Direction dir) {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001106 SkRRect rrect;
1107 rrect.setRectRadii(rect, (const SkVector*) radii);
1108 this->addRRect(rrect, dir);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001109}
1110
reed@google.com4ed0fb72012-12-12 20:48:18 +00001111void SkPath::addRRect(const SkRRect& rrect, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -08001112 // legacy start indices: 6 (CW) and 7(CCW)
1113 this->addRRect(rrect, dir, dir == kCW_Direction ? 6 : 7);
1114}
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001115
fmalitac08d53e2015-11-17 09:53:29 -08001116void SkPath::addRRect(const SkRRect &rrect, Direction dir, unsigned startIndex) {
1117 assert_known_direction(dir);
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001118
fmalitac08d53e2015-11-17 09:53:29 -08001119 if (rrect.isEmpty()) {
1120 return;
reed1b28a3a2014-12-17 14:42:09 -08001121 }
fmalitac08d53e2015-11-17 09:53:29 -08001122
caryclarkda707bf2015-11-19 14:47:43 -08001123 bool isRRect = hasOnlyMoveTos();
fmalitac08d53e2015-11-17 09:53:29 -08001124 const SkRect& bounds = rrect.getBounds();
1125
1126 if (rrect.isRect()) {
1127 // degenerate(rect) => radii points are collapsing
1128 this->addRect(bounds, dir, (startIndex + 1) / 2);
1129 } else if (rrect.isOval()) {
1130 // degenerate(oval) => line points are collapsing
1131 this->addOval(bounds, dir, startIndex / 2);
1132 } else {
1133 fFirstDirection = this->hasOnlyMoveTos() ?
1134 (SkPathPriv::FirstDirection)dir : SkPathPriv::kUnknown_FirstDirection;
1135
1136 SkAutoPathBoundsUpdate apbu(this, bounds);
1137 SkAutoDisableDirectionCheck addc(this);
1138
1139 // we start with a conic on odd indices when moving CW vs. even indices when moving CCW
1140 const bool startsWithConic = ((startIndex & 1) == (dir == kCW_Direction));
1141 const SkScalar weight = SK_ScalarRoot2Over2;
1142
1143 SkDEBUGCODE(int initialVerbCount = this->countVerbs());
1144 const int kVerbs = startsWithConic
1145 ? 9 // moveTo + 4x conicTo + 3x lineTo + close
1146 : 10; // moveTo + 4x lineTo + 4x conicTo + close
1147 this->incReserve(kVerbs);
1148
1149 RRectPointIterator rrectIter(rrect, dir, startIndex);
1150 // Corner iterator indices follow the collapsed radii model,
1151 // adjusted such that the start pt is "behind" the radii start pt.
1152 const unsigned rectStartIndex = startIndex / 2 + (dir == kCW_Direction ? 0 : 1);
1153 RectPointIterator rectIter(bounds, dir, rectStartIndex);
1154
1155 this->moveTo(rrectIter.current());
1156 if (startsWithConic) {
1157 for (unsigned i = 0; i < 3; ++i) {
1158 this->conicTo(rectIter.next(), rrectIter.next(), weight);
1159 this->lineTo(rrectIter.next());
1160 }
1161 this->conicTo(rectIter.next(), rrectIter.next(), weight);
1162 // final lineTo handled by close().
1163 } else {
1164 for (unsigned i = 0; i < 4; ++i) {
1165 this->lineTo(rrectIter.next());
1166 this->conicTo(rectIter.next(), rrectIter.next(), weight);
1167 }
1168 }
1169 this->close();
1170
caryclarkda707bf2015-11-19 14:47:43 -08001171 SkPathRef::Editor ed(&fPathRef);
bsalomon78d58d12016-05-27 09:17:04 -07001172 ed.setIsRRect(isRRect, dir, startIndex % 8);
caryclarkda707bf2015-11-19 14:47:43 -08001173
fmalitac08d53e2015-11-17 09:53:29 -08001174 SkASSERT(this->countVerbs() == initialVerbCount + kVerbs);
1175 }
1176
1177 SkDEBUGCODE(fPathRef->validate();)
reed@google.com4ed0fb72012-12-12 20:48:18 +00001178}
1179
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001180bool SkPath::hasOnlyMoveTos() const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001181 int count = fPathRef->countVerbs();
1182 const uint8_t* verbs = const_cast<const SkPathRef*>(fPathRef.get())->verbsMemBegin();
1183 for (int i = 0; i < count; ++i) {
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001184 if (*verbs == kLine_Verb ||
1185 *verbs == kQuad_Verb ||
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001186 *verbs == kConic_Verb ||
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001187 *verbs == kCubic_Verb) {
1188 return false;
1189 }
1190 ++verbs;
1191 }
1192 return true;
1193}
1194
caryclarkd49a86a2016-02-22 12:44:54 -08001195bool SkPath::isZeroLength() const {
1196 int count = fPathRef->countPoints();
1197 if (count < 2) {
1198 return true;
1199 }
1200 const SkPoint* pts = fPathRef.get()->points();
1201 const SkPoint& first = *pts;
1202 for (int index = 1; index < count; ++index) {
1203 if (first != pts[index]) {
1204 return false;
1205 }
1206 }
1207 return true;
1208}
1209
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001210void SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
1211 Direction dir) {
1212 assert_known_direction(dir);
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001213
humper@google.com75e3ca12013-04-08 21:44:11 +00001214 if (rx < 0 || ry < 0) {
humper@google.com75e3ca12013-04-08 21:44:11 +00001215 return;
1216 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001217
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001218 SkRRect rrect;
1219 rrect.setRectXY(rect, rx, ry);
1220 this->addRRect(rrect, dir);
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001221}
1222
reed@android.com8a1c16f2008-12-17 15:59:43 +00001223void SkPath::addOval(const SkRect& oval, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -08001224 // legacy start index: 1
1225 this->addOval(oval, dir, 1);
1226}
1227
1228void SkPath::addOval(const SkRect &oval, Direction dir, unsigned startPointIndex) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +00001229 assert_known_direction(dir);
1230
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001231 /* If addOval() is called after previous moveTo(),
1232 this path is still marked as an oval. This is used to
1233 fit into WebKit's calling sequences.
1234 We can't simply check isEmpty() in this case, as additional
1235 moveTo() would mark the path non empty.
1236 */
robertphillips@google.com466310d2013-12-03 16:43:54 +00001237 bool isOval = hasOnlyMoveTos();
1238 if (isOval) {
reed026beb52015-06-10 14:23:15 -07001239 fFirstDirection = (SkPathPriv::FirstDirection)dir;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001240 } else {
reed026beb52015-06-10 14:23:15 -07001241 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001242 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001243
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001244 SkAutoDisableDirectionCheck addc(this);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001245 SkAutoPathBoundsUpdate apbu(this, oval);
1246
fmalitac08d53e2015-11-17 09:53:29 -08001247 SkDEBUGCODE(int initialVerbCount = this->countVerbs());
1248 const int kVerbs = 6; // moveTo + 4x conicTo + close
1249 this->incReserve(kVerbs);
1250
1251 OvalPointIterator ovalIter(oval, dir, startPointIndex);
1252 // The corner iterator pts are tracking "behind" the oval/radii pts.
1253 RectPointIterator rectIter(oval, dir, startPointIndex + (dir == kCW_Direction ? 0 : 1));
reed220f9262014-12-17 08:21:04 -08001254 const SkScalar weight = SK_ScalarRoot2Over2;
1255
fmalitac08d53e2015-11-17 09:53:29 -08001256 this->moveTo(ovalIter.current());
1257 for (unsigned i = 0; i < 4; ++i) {
1258 this->conicTo(rectIter.next(), ovalIter.next(), weight);
reed220f9262014-12-17 08:21:04 -08001259 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001260 this->close();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001261
fmalitac08d53e2015-11-17 09:53:29 -08001262 SkASSERT(this->countVerbs() == initialVerbCount + kVerbs);
1263
robertphillips@google.com466310d2013-12-03 16:43:54 +00001264 SkPathRef::Editor ed(&fPathRef);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001265
bsalomon78d58d12016-05-27 09:17:04 -07001266 ed.setIsOval(isOval, kCCW_Direction == dir, startPointIndex % 4);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001267}
1268
reed@android.com8a1c16f2008-12-17 15:59:43 +00001269void SkPath::addCircle(SkScalar x, SkScalar y, SkScalar r, Direction dir) {
1270 if (r > 0) {
fmalitac08d53e2015-11-17 09:53:29 -08001271 this->addOval(SkRect::MakeLTRB(x - r, y - r, x + r, y + r), dir);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001272 }
1273}
1274
reed@android.com8a1c16f2008-12-17 15:59:43 +00001275void SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
1276 bool forceMoveTo) {
1277 if (oval.width() < 0 || oval.height() < 0) {
1278 return;
1279 }
1280
reedf90ea012015-01-29 12:03:58 -08001281 if (fPathRef->countVerbs() == 0) {
1282 forceMoveTo = true;
1283 }
1284
1285 SkPoint lonePt;
1286 if (arc_is_lone_point(oval, startAngle, sweepAngle, &lonePt)) {
1287 forceMoveTo ? this->moveTo(lonePt) : this->lineTo(lonePt);
1288 return;
1289 }
1290
reedd5d27d92015-02-09 13:54:43 -08001291 SkVector startV, stopV;
1292 SkRotationDirection dir;
1293 angles_to_unit_vectors(startAngle, sweepAngle, &startV, &stopV, &dir);
1294
reed9e779d42015-02-17 11:43:14 -08001295 SkPoint singlePt;
xidachen6069dda2016-10-06 05:42:23 -07001296
1297 // At this point, we know that the arc is not a lone point, but startV == stopV
1298 // indicates that the sweepAngle is too small such that angles_to_unit_vectors
1299 // cannot handle it.
1300 if (startV == stopV) {
1301 SkScalar endAngle = SkDegreesToRadians(startAngle + sweepAngle);
1302 SkScalar radiusX = oval.width() / 2;
1303 SkScalar radiusY = oval.height() / 2;
1304 // We cannot use SkScalarSinCos function in the next line because
1305 // SkScalarSinCos has a threshold *SkScalarNearlyZero*. When sin(startAngle)
1306 // is 0 and sweepAngle is very small and radius is huge, the expected
Mike Kleine54c75f2016-10-13 14:18:09 -04001307 // behavior here is to draw a line. But calling SkScalarSinCos will
xidachen6069dda2016-10-06 05:42:23 -07001308 // make sin(endAngle) to be 0 which will then draw a dot.
1309 singlePt.set(oval.centerX() + radiusX * sk_float_cos(endAngle),
1310 oval.centerY() + radiusY * sk_float_sin(endAngle));
1311 forceMoveTo ? this->moveTo(singlePt) : this->lineTo(singlePt);
1312 return;
1313 }
1314
reedd5d27d92015-02-09 13:54:43 -08001315 SkConic conics[SkConic::kMaxConicsForArc];
reed9e779d42015-02-17 11:43:14 -08001316 int count = build_arc_conics(oval, startV, stopV, dir, conics, &singlePt);
reedd5d27d92015-02-09 13:54:43 -08001317 if (count) {
1318 this->incReserve(count * 2 + 1);
1319 const SkPoint& pt = conics[0].fPts[0];
1320 forceMoveTo ? this->moveTo(pt) : this->lineTo(pt);
1321 for (int i = 0; i < count; ++i) {
1322 this->conicTo(conics[i].fPts[1], conics[i].fPts[2], conics[i].fW);
1323 }
reed9e779d42015-02-17 11:43:14 -08001324 } else {
1325 forceMoveTo ? this->moveTo(singlePt) : this->lineTo(singlePt);
reedd5d27d92015-02-09 13:54:43 -08001326 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001327}
1328
caryclark55d49052016-01-23 05:07:04 -08001329// This converts the SVG arc to conics.
1330// Partly adapted from Niko's code in kdelibs/kdecore/svgicons.
1331// Then transcribed from webkit/chrome's SVGPathNormalizer::decomposeArcToCubic()
1332// See also SVG implementation notes:
1333// http://www.w3.org/TR/SVG/implnote.html#ArcConversionEndpointToCenter
1334// Note that arcSweep bool value is flipped from the original implementation.
1335void SkPath::arcTo(SkScalar rx, SkScalar ry, SkScalar angle, SkPath::ArcSize arcLarge,
1336 SkPath::Direction arcSweep, SkScalar x, SkScalar y) {
caryclarkf1d41512016-02-09 10:30:22 -08001337 this->injectMoveToIfNeeded();
caryclark55d49052016-01-23 05:07:04 -08001338 SkPoint srcPts[2];
1339 this->getLastPt(&srcPts[0]);
1340 // If rx = 0 or ry = 0 then this arc is treated as a straight line segment (a "lineto")
1341 // joining the endpoints.
1342 // http://www.w3.org/TR/SVG/implnote.html#ArcOutOfRangeParameters
1343 if (!rx || !ry) {
caryclarkfc752532016-01-25 05:39:04 -08001344 this->lineTo(x, y);
caryclark55d49052016-01-23 05:07:04 -08001345 return;
1346 }
1347 // If the current point and target point for the arc are identical, it should be treated as a
1348 // zero length path. This ensures continuity in animations.
1349 srcPts[1].set(x, y);
1350 if (srcPts[0] == srcPts[1]) {
caryclarkfc752532016-01-25 05:39:04 -08001351 this->lineTo(x, y);
caryclark55d49052016-01-23 05:07:04 -08001352 return;
1353 }
1354 rx = SkScalarAbs(rx);
1355 ry = SkScalarAbs(ry);
1356 SkVector midPointDistance = srcPts[0] - srcPts[1];
1357 midPointDistance *= 0.5f;
1358
1359 SkMatrix pointTransform;
1360 pointTransform.setRotate(-angle);
1361
1362 SkPoint transformedMidPoint;
1363 pointTransform.mapPoints(&transformedMidPoint, &midPointDistance, 1);
1364 SkScalar squareRx = rx * rx;
1365 SkScalar squareRy = ry * ry;
1366 SkScalar squareX = transformedMidPoint.fX * transformedMidPoint.fX;
1367 SkScalar squareY = transformedMidPoint.fY * transformedMidPoint.fY;
1368
1369 // Check if the radii are big enough to draw the arc, scale radii if not.
1370 // http://www.w3.org/TR/SVG/implnote.html#ArcCorrectionOutOfRangeRadii
1371 SkScalar radiiScale = squareX / squareRx + squareY / squareRy;
1372 if (radiiScale > 1) {
1373 radiiScale = SkScalarSqrt(radiiScale);
1374 rx *= radiiScale;
1375 ry *= radiiScale;
1376 }
1377
1378 pointTransform.setScale(1 / rx, 1 / ry);
1379 pointTransform.preRotate(-angle);
1380
1381 SkPoint unitPts[2];
1382 pointTransform.mapPoints(unitPts, srcPts, (int) SK_ARRAY_COUNT(unitPts));
1383 SkVector delta = unitPts[1] - unitPts[0];
1384
1385 SkScalar d = delta.fX * delta.fX + delta.fY * delta.fY;
1386 SkScalar scaleFactorSquared = SkTMax(1 / d - 0.25f, 0.f);
1387
1388 SkScalar scaleFactor = SkScalarSqrt(scaleFactorSquared);
1389 if (SkToBool(arcSweep) != SkToBool(arcLarge)) { // flipped from the original implementation
1390 scaleFactor = -scaleFactor;
1391 }
1392 delta.scale(scaleFactor);
1393 SkPoint centerPoint = unitPts[0] + unitPts[1];
1394 centerPoint *= 0.5f;
1395 centerPoint.offset(-delta.fY, delta.fX);
1396 unitPts[0] -= centerPoint;
1397 unitPts[1] -= centerPoint;
1398 SkScalar theta1 = SkScalarATan2(unitPts[0].fY, unitPts[0].fX);
1399 SkScalar theta2 = SkScalarATan2(unitPts[1].fY, unitPts[1].fX);
1400 SkScalar thetaArc = theta2 - theta1;
1401 if (thetaArc < 0 && !arcSweep) { // arcSweep flipped from the original implementation
1402 thetaArc += SK_ScalarPI * 2;
1403 } else if (thetaArc > 0 && arcSweep) { // arcSweep flipped from the original implementation
1404 thetaArc -= SK_ScalarPI * 2;
1405 }
1406 pointTransform.setRotate(angle);
1407 pointTransform.preScale(rx, ry);
1408
1409 int segments = SkScalarCeilToInt(SkScalarAbs(thetaArc / (SK_ScalarPI / 2)));
1410 SkScalar thetaWidth = thetaArc / segments;
1411 SkScalar t = SkScalarTan(0.5f * thetaWidth);
1412 if (!SkScalarIsFinite(t)) {
1413 return;
1414 }
1415 SkScalar startTheta = theta1;
1416 SkScalar w = SkScalarSqrt(SK_ScalarHalf + SkScalarCos(thetaWidth) * SK_ScalarHalf);
1417 for (int i = 0; i < segments; ++i) {
1418 SkScalar endTheta = startTheta + thetaWidth;
1419 SkScalar cosEndTheta, sinEndTheta = SkScalarSinCos(endTheta, &cosEndTheta);
1420
1421 unitPts[1].set(cosEndTheta, sinEndTheta);
1422 unitPts[1] += centerPoint;
1423 unitPts[0] = unitPts[1];
1424 unitPts[0].offset(t * sinEndTheta, -t * cosEndTheta);
1425 SkPoint mapped[2];
1426 pointTransform.mapPoints(mapped, unitPts, (int) SK_ARRAY_COUNT(unitPts));
1427 this->conicTo(mapped[0], mapped[1], w);
1428 startTheta = endTheta;
1429 }
1430}
1431
1432void SkPath::rArcTo(SkScalar rx, SkScalar ry, SkScalar xAxisRotate, SkPath::ArcSize largeArc,
1433 SkPath::Direction sweep, SkScalar dx, SkScalar dy) {
1434 SkPoint currentPoint;
1435 this->getLastPt(&currentPoint);
1436 this->arcTo(rx, ry, xAxisRotate, largeArc, sweep, currentPoint.fX + dx, currentPoint.fY + dy);
1437}
1438
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001439void SkPath::addArc(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001440 if (oval.isEmpty() || 0 == sweepAngle) {
1441 return;
1442 }
1443
1444 const SkScalar kFullCircleAngle = SkIntToScalar(360);
1445
1446 if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
bsalomon1978ce22016-05-31 10:42:16 -07001447 // We can treat the arc as an oval if it begins at one of our legal starting positions.
1448 // See SkPath::addOval() docs.
1449 SkScalar startOver90 = startAngle / 90.f;
1450 SkScalar startOver90I = SkScalarRoundToScalar(startOver90);
1451 SkScalar error = startOver90 - startOver90I;
1452 if (SkScalarNearlyEqual(error, 0)) {
1453 // Index 1 is at startAngle == 0.
1454 SkScalar startIndex = std::fmod(startOver90I + 1.f, 4.f);
1455 startIndex = startIndex < 0 ? startIndex + 4.f : startIndex;
1456 this->addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction,
1457 (unsigned) startIndex);
1458 return;
1459 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001460 }
bsalomon1978ce22016-05-31 10:42:16 -07001461 this->arcTo(oval, startAngle, sweepAngle, true);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001462}
1463
1464/*
1465 Need to handle the case when the angle is sharp, and our computed end-points
1466 for the arc go behind pt1 and/or p2...
1467*/
reedc7789042015-01-29 12:59:11 -08001468void SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2, SkScalar radius) {
reeda8b326c2014-12-09 11:50:32 -08001469 if (radius == 0) {
1470 this->lineTo(x1, y1);
1471 return;
1472 }
1473
1474 SkVector before, after;
reed@google.comabf15c12011-01-18 20:35:51 +00001475
reed@android.com8a1c16f2008-12-17 15:59:43 +00001476 // need to know our prev pt so we can construct tangent vectors
1477 {
1478 SkPoint start;
1479 this->getLastPt(&start);
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001480 // Handle degenerate cases by adding a line to the first point and
1481 // bailing out.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001482 before.setNormalize(x1 - start.fX, y1 - start.fY);
1483 after.setNormalize(x2 - x1, y2 - y1);
1484 }
reed@google.comabf15c12011-01-18 20:35:51 +00001485
reed@android.com8a1c16f2008-12-17 15:59:43 +00001486 SkScalar cosh = SkPoint::DotProduct(before, after);
1487 SkScalar sinh = SkPoint::CrossProduct(before, after);
1488
1489 if (SkScalarNearlyZero(sinh)) { // angle is too tight
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001490 this->lineTo(x1, y1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001491 return;
1492 }
reed@google.comabf15c12011-01-18 20:35:51 +00001493
Mike Reeda99b6ce2017-02-04 11:04:26 -05001494 SkScalar dist = SkScalarAbs(radius * (1 - cosh) / sinh);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001495
Mike Reeda99b6ce2017-02-04 11:04:26 -05001496 SkScalar xx = x1 - dist * before.fX;
1497 SkScalar yy = y1 - dist * before.fY;
caryclark88651ae2016-01-20 11:55:11 -08001498 after.setLength(dist);
1499 this->lineTo(xx, yy);
1500 SkScalar weight = SkScalarSqrt(SK_ScalarHalf + cosh * SK_ScalarHalf);
1501 this->conicTo(x1, y1, x1 + after.fX, y1 + after.fY, weight);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001502}
1503
1504///////////////////////////////////////////////////////////////////////////////
1505
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001506void SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy, AddPathMode mode) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001507 SkMatrix matrix;
1508
1509 matrix.setTranslate(dx, dy);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001510 this->addPath(path, matrix, mode);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001511}
1512
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001513void SkPath::addPath(const SkPath& path, const SkMatrix& matrix, AddPathMode mode) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001514 SkPathRef::Editor(&fPathRef, path.countVerbs(), path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001515
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001516 RawIter iter(path);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001517 SkPoint pts[4];
1518 Verb verb;
1519
1520 SkMatrix::MapPtsProc proc = matrix.getMapPtsProc();
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001521 bool firstVerb = true;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001522 while ((verb = iter.next(pts)) != kDone_Verb) {
1523 switch (verb) {
1524 case kMove_Verb:
1525 proc(matrix, &pts[0], &pts[0], 1);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001526 if (firstVerb && mode == kExtend_AddPathMode && !isEmpty()) {
1527 injectMoveToIfNeeded(); // In case last contour is closed
1528 this->lineTo(pts[0]);
1529 } else {
1530 this->moveTo(pts[0]);
1531 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001532 break;
1533 case kLine_Verb:
1534 proc(matrix, &pts[1], &pts[1], 1);
1535 this->lineTo(pts[1]);
1536 break;
1537 case kQuad_Verb:
1538 proc(matrix, &pts[1], &pts[1], 2);
1539 this->quadTo(pts[1], pts[2]);
1540 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001541 case kConic_Verb:
1542 proc(matrix, &pts[1], &pts[1], 2);
1543 this->conicTo(pts[1], pts[2], iter.conicWeight());
1544 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001545 case kCubic_Verb:
1546 proc(matrix, &pts[1], &pts[1], 3);
1547 this->cubicTo(pts[1], pts[2], pts[3]);
1548 break;
1549 case kClose_Verb:
1550 this->close();
1551 break;
1552 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001553 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001554 }
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001555 firstVerb = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001556 }
1557}
1558
1559///////////////////////////////////////////////////////////////////////////////
1560
reed@google.com277c3f82013-05-31 15:17:50 +00001561static int pts_in_verb(unsigned verb) {
1562 static const uint8_t gPtsInVerb[] = {
1563 1, // kMove
1564 1, // kLine
1565 2, // kQuad
1566 2, // kConic
1567 3, // kCubic
1568 0, // kClose
1569 0 // kDone
1570 };
1571
1572 SkASSERT(verb < SK_ARRAY_COUNT(gPtsInVerb));
1573 return gPtsInVerb[verb];
1574}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001575
reed@android.com8a1c16f2008-12-17 15:59:43 +00001576// ignore the last point of the 1st contour
1577void SkPath::reversePathTo(const SkPath& path) {
caryclark51c56782016-11-07 05:09:28 -08001578 const uint8_t* verbs = path.fPathRef->verbsMemBegin(); // points at the last verb
1579 if (!verbs) { // empty path returns nullptr
reed@android.com8a1c16f2008-12-17 15:59:43 +00001580 return;
1581 }
caryclark51c56782016-11-07 05:09:28 -08001582 const uint8_t* verbsEnd = path.fPathRef->verbs() - 1; // points just past the first verb
1583 SkASSERT(verbsEnd[0] == kMove_Verb);
1584 const SkPoint* pts = path.fPathRef->pointsEnd() - 1;
1585 const SkScalar* conicWeights = path.fPathRef->conicWeightsEnd();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001586
caryclark51c56782016-11-07 05:09:28 -08001587 while (verbs < verbsEnd) {
1588 uint8_t v = *verbs++;
1589 pts -= pts_in_verb(v);
1590 switch (v) {
1591 case kMove_Verb:
1592 // if the path has multiple contours, stop after reversing the last
1593 return;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001594 case kLine_Verb:
caryclark51c56782016-11-07 05:09:28 -08001595 this->lineTo(pts[0]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001596 break;
1597 case kQuad_Verb:
caryclark51c56782016-11-07 05:09:28 -08001598 this->quadTo(pts[1], pts[0]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001599 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001600 case kConic_Verb:
caryclark51c56782016-11-07 05:09:28 -08001601 this->conicTo(pts[1], pts[0], *--conicWeights);
reed@google.com277c3f82013-05-31 15:17:50 +00001602 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001603 case kCubic_Verb:
caryclark51c56782016-11-07 05:09:28 -08001604 this->cubicTo(pts[2], pts[1], pts[0]);
1605 break;
1606 case kClose_Verb:
1607 SkASSERT(verbs - path.fPathRef->verbsMemBegin() == 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001608 break;
1609 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001610 SkDEBUGFAIL("bad verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001611 break;
1612 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001613 }
1614}
1615
reed@google.com63d73742012-01-10 15:33:12 +00001616void SkPath::reverseAddPath(const SkPath& src) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001617 SkPathRef::Editor ed(&fPathRef, src.fPathRef->countPoints(), src.fPathRef->countVerbs());
reed@google.com63d73742012-01-10 15:33:12 +00001618
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001619 const SkPoint* pts = src.fPathRef->pointsEnd();
1620 // we will iterator through src's verbs backwards
1621 const uint8_t* verbs = src.fPathRef->verbsMemBegin(); // points at the last verb
1622 const uint8_t* verbsEnd = src.fPathRef->verbs(); // points just past the first verb
reed@google.com277c3f82013-05-31 15:17:50 +00001623 const SkScalar* conicWeights = src.fPathRef->conicWeightsEnd();
reed@google.com63d73742012-01-10 15:33:12 +00001624
1625 bool needMove = true;
1626 bool needClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001627 while (verbs < verbsEnd) {
1628 uint8_t v = *(verbs++);
reed@google.com277c3f82013-05-31 15:17:50 +00001629 int n = pts_in_verb(v);
reed@google.com63d73742012-01-10 15:33:12 +00001630
1631 if (needMove) {
1632 --pts;
1633 this->moveTo(pts->fX, pts->fY);
1634 needMove = false;
1635 }
1636 pts -= n;
1637 switch (v) {
1638 case kMove_Verb:
1639 if (needClose) {
1640 this->close();
1641 needClose = false;
1642 }
1643 needMove = true;
1644 pts += 1; // so we see the point in "if (needMove)" above
1645 break;
1646 case kLine_Verb:
1647 this->lineTo(pts[0]);
1648 break;
1649 case kQuad_Verb:
1650 this->quadTo(pts[1], pts[0]);
1651 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001652 case kConic_Verb:
1653 this->conicTo(pts[1], pts[0], *--conicWeights);
1654 break;
reed@google.com63d73742012-01-10 15:33:12 +00001655 case kCubic_Verb:
1656 this->cubicTo(pts[2], pts[1], pts[0]);
1657 break;
1658 case kClose_Verb:
1659 needClose = true;
1660 break;
1661 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00001662 SkDEBUGFAIL("unexpected verb");
reed@google.com63d73742012-01-10 15:33:12 +00001663 }
1664 }
1665}
1666
reed@android.com8a1c16f2008-12-17 15:59:43 +00001667///////////////////////////////////////////////////////////////////////////////
1668
1669void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
1670 SkMatrix matrix;
1671
1672 matrix.setTranslate(dx, dy);
1673 this->transform(matrix, dst);
1674}
1675
reed@android.com8a1c16f2008-12-17 15:59:43 +00001676static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
1677 int level = 2) {
1678 if (--level >= 0) {
1679 SkPoint tmp[7];
1680
1681 SkChopCubicAtHalf(pts, tmp);
1682 subdivide_cubic_to(path, &tmp[0], level);
1683 subdivide_cubic_to(path, &tmp[3], level);
1684 } else {
1685 path->cubicTo(pts[1], pts[2], pts[3]);
1686 }
1687}
1688
1689void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
1690 SkDEBUGCODE(this->validate();)
halcanary96fcdcc2015-08-27 07:41:13 -07001691 if (dst == nullptr) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001692 dst = (SkPath*)this;
1693 }
1694
tomhudson@google.com8d430182011-06-06 19:11:19 +00001695 if (matrix.hasPerspective()) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001696 SkPath tmp;
1697 tmp.fFillType = fFillType;
1698
1699 SkPath::Iter iter(*this, false);
1700 SkPoint pts[4];
1701 SkPath::Verb verb;
1702
reed@google.com4a3b7142012-05-16 17:16:46 +00001703 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001704 switch (verb) {
1705 case kMove_Verb:
1706 tmp.moveTo(pts[0]);
1707 break;
1708 case kLine_Verb:
1709 tmp.lineTo(pts[1]);
1710 break;
1711 case kQuad_Verb:
reed220f9262014-12-17 08:21:04 -08001712 // promote the quad to a conic
1713 tmp.conicTo(pts[1], pts[2],
1714 SkConic::TransformW(pts, SK_Scalar1, matrix));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001715 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001716 case kConic_Verb:
reed220f9262014-12-17 08:21:04 -08001717 tmp.conicTo(pts[1], pts[2],
1718 SkConic::TransformW(pts, iter.conicWeight(), matrix));
reed@google.com277c3f82013-05-31 15:17:50 +00001719 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001720 case kCubic_Verb:
1721 subdivide_cubic_to(&tmp, pts);
1722 break;
1723 case kClose_Verb:
1724 tmp.close();
1725 break;
1726 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001727 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001728 break;
1729 }
1730 }
1731
1732 dst->swap(tmp);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001733 SkPathRef::Editor ed(&dst->fPathRef);
1734 matrix.mapPoints(ed.points(), ed.pathRef()->countPoints());
reed026beb52015-06-10 14:23:15 -07001735 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001736 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001737 SkPathRef::CreateTransformedCopy(&dst->fPathRef, *fPathRef.get(), matrix);
1738
reed@android.com8a1c16f2008-12-17 15:59:43 +00001739 if (this != dst) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001740 dst->fFillType = fFillType;
reed@google.com2a6f8ab2011-10-25 18:41:23 +00001741 dst->fConvexity = fConvexity;
jvanverthb3eb6872014-10-24 07:12:51 -07001742 dst->fIsVolatile = fIsVolatile;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001743 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001744
reed026beb52015-06-10 14:23:15 -07001745 if (SkPathPriv::kUnknown_FirstDirection == fFirstDirection) {
1746 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001747 } else {
1748 SkScalar det2x2 =
Mike Reeda99b6ce2017-02-04 11:04:26 -05001749 matrix.get(SkMatrix::kMScaleX) * matrix.get(SkMatrix::kMScaleY) -
1750 matrix.get(SkMatrix::kMSkewX) * matrix.get(SkMatrix::kMSkewY);
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001751 if (det2x2 < 0) {
herb9f4dbca2015-09-28 11:05:47 -07001752 dst->fFirstDirection = SkPathPriv::OppositeFirstDirection(
1753 (SkPathPriv::FirstDirection)fFirstDirection.load());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001754 } else if (det2x2 > 0) {
herb9f4dbca2015-09-28 11:05:47 -07001755 dst->fFirstDirection = fFirstDirection.load();
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001756 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001757 dst->fConvexity = kUnknown_Convexity;
reed026beb52015-06-10 14:23:15 -07001758 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001759 }
1760 }
1761
reed@android.com8a1c16f2008-12-17 15:59:43 +00001762 SkDEBUGCODE(dst->validate();)
1763 }
1764}
1765
reed@android.com8a1c16f2008-12-17 15:59:43 +00001766///////////////////////////////////////////////////////////////////////////////
1767///////////////////////////////////////////////////////////////////////////////
1768
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001769enum SegmentState {
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001770 kEmptyContour_SegmentState, // The current contour is empty. We may be
1771 // starting processing or we may have just
1772 // closed a contour.
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001773 kAfterMove_SegmentState, // We have seen a move, but nothing else.
1774 kAfterPrimitive_SegmentState // We have seen a primitive but not yet
1775 // closed the path. Also the initial state.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001776};
1777
1778SkPath::Iter::Iter() {
1779#ifdef SK_DEBUG
halcanary96fcdcc2015-08-27 07:41:13 -07001780 fPts = nullptr;
1781 fConicWeights = nullptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001782 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001783 fForceClose = fCloseLine = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001784 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001785#endif
1786 // need to init enough to make next() harmlessly return kDone_Verb
halcanary96fcdcc2015-08-27 07:41:13 -07001787 fVerbs = nullptr;
1788 fVerbStop = nullptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001789 fNeedClose = false;
1790}
1791
1792SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
1793 this->setPath(path, forceClose);
1794}
1795
1796void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001797 fPts = path.fPathRef->points();
1798 fVerbs = path.fPathRef->verbs();
1799 fVerbStop = path.fPathRef->verbsMemBegin();
caryclark69424422016-10-04 13:06:17 -07001800 fConicWeights = path.fPathRef->conicWeights();
1801 if (fConicWeights) {
1802 fConicWeights -= 1; // begin one behind
1803 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001804 fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org72785c42011-12-29 21:03:28 +00001805 fMoveTo.fX = fMoveTo.fY = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001806 fForceClose = SkToU8(forceClose);
1807 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001808 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001809}
1810
1811bool SkPath::Iter::isClosedContour() const {
halcanary96fcdcc2015-08-27 07:41:13 -07001812 if (fVerbs == nullptr || fVerbs == fVerbStop) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001813 return false;
1814 }
1815 if (fForceClose) {
1816 return true;
1817 }
1818
1819 const uint8_t* verbs = fVerbs;
1820 const uint8_t* stop = fVerbStop;
reed@google.comabf15c12011-01-18 20:35:51 +00001821
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001822 if (kMove_Verb == *(verbs - 1)) {
1823 verbs -= 1; // skip the initial moveto
reed@android.com8a1c16f2008-12-17 15:59:43 +00001824 }
1825
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001826 while (verbs > stop) {
1827 // verbs points one beyond the current verb, decrement first.
1828 unsigned v = *(--verbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001829 if (kMove_Verb == v) {
1830 break;
1831 }
1832 if (kClose_Verb == v) {
1833 return true;
1834 }
1835 }
1836 return false;
1837}
1838
1839SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001840 SkASSERT(pts);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001841 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001842 // A special case: if both points are NaN, SkPoint::operation== returns
1843 // false, but the iterator expects that they are treated as the same.
1844 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001845 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1846 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001847 return kClose_Verb;
1848 }
1849
reed@google.com9e25dbf2012-05-15 17:05:38 +00001850 pts[0] = fLastPt;
1851 pts[1] = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001852 fLastPt = fMoveTo;
1853 fCloseLine = true;
1854 return kLine_Verb;
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +00001855 } else {
1856 pts[0] = fMoveTo;
1857 return kClose_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001858 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001859}
1860
reed@google.com9e25dbf2012-05-15 17:05:38 +00001861const SkPoint& SkPath::Iter::cons_moveTo() {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001862 if (fSegmentState == kAfterMove_SegmentState) {
1863 // Set the first return pt to the move pt
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001864 fSegmentState = kAfterPrimitive_SegmentState;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001865 return fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001866 } else {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001867 SkASSERT(fSegmentState == kAfterPrimitive_SegmentState);
1868 // Set the first return pt to the last pt of the previous primitive.
reed@google.com9e25dbf2012-05-15 17:05:38 +00001869 return fPts[-1];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001870 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001871}
1872
caryclarke8c56662015-07-14 11:19:26 -07001873void SkPath::Iter::consumeDegenerateSegments(bool exact) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001874 // We need to step over anything that will not move the current draw point
1875 // forward before the next move is seen
1876 const uint8_t* lastMoveVerb = 0;
1877 const SkPoint* lastMovePt = 0;
halcanary96fcdcc2015-08-27 07:41:13 -07001878 const SkScalar* lastMoveWeight = nullptr;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001879 SkPoint lastPt = fLastPt;
1880 while (fVerbs != fVerbStop) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001881 unsigned verb = *(fVerbs - 1); // fVerbs is one beyond the current verb
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001882 switch (verb) {
1883 case kMove_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001884 // Keep a record of this most recent move
1885 lastMoveVerb = fVerbs;
1886 lastMovePt = fPts;
robertphillipsb8de1f42015-02-23 11:17:01 -08001887 lastMoveWeight = fConicWeights;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001888 lastPt = fPts[0];
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001889 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001890 fPts++;
1891 break;
1892
1893 case kClose_Verb:
schenney@chromium.org7e963602012-06-13 17:05:43 +00001894 // A close when we are in a segment is always valid except when it
1895 // follows a move which follows a segment.
1896 if (fSegmentState == kAfterPrimitive_SegmentState && !lastMoveVerb) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001897 return;
1898 }
1899 // A close at any other time must be ignored
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001900 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001901 break;
1902
1903 case kLine_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001904 if (!IsLineDegenerate(lastPt, fPts[0], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001905 if (lastMoveVerb) {
1906 fVerbs = lastMoveVerb;
1907 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001908 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001909 return;
1910 }
1911 return;
1912 }
1913 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001914 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001915 fPts++;
1916 break;
1917
reed@google.com277c3f82013-05-31 15:17:50 +00001918 case kConic_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001919 case kQuad_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001920 if (!IsQuadDegenerate(lastPt, fPts[0], fPts[1], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001921 if (lastMoveVerb) {
1922 fVerbs = lastMoveVerb;
1923 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001924 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001925 return;
1926 }
1927 return;
1928 }
1929 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001930 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001931 fPts += 2;
reed@google.com277c3f82013-05-31 15:17:50 +00001932 fConicWeights += (kConic_Verb == verb);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001933 break;
1934
1935 case kCubic_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001936 if (!IsCubicDegenerate(lastPt, fPts[0], fPts[1], fPts[2], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001937 if (lastMoveVerb) {
1938 fVerbs = lastMoveVerb;
1939 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001940 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001941 return;
1942 }
1943 return;
1944 }
1945 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001946 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001947 fPts += 3;
1948 break;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001949
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001950 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001951 SkDEBUGFAIL("Should never see kDone_Verb");
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001952 }
1953 }
1954}
1955
reed@google.com4a3b7142012-05-16 17:16:46 +00001956SkPath::Verb SkPath::Iter::doNext(SkPoint ptsParam[4]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001957 SkASSERT(ptsParam);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001958
reed@android.com8a1c16f2008-12-17 15:59:43 +00001959 if (fVerbs == fVerbStop) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001960 // Close the curve if requested and if there is some curve to close
1961 if (fNeedClose && fSegmentState == kAfterPrimitive_SegmentState) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001962 if (kLine_Verb == this->autoClose(ptsParam)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001963 return kLine_Verb;
1964 }
1965 fNeedClose = false;
1966 return kClose_Verb;
1967 }
1968 return kDone_Verb;
1969 }
1970
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001971 // fVerbs is one beyond the current verb, decrement first
1972 unsigned verb = *(--fVerbs);
reed@google.com9e25dbf2012-05-15 17:05:38 +00001973 const SkPoint* SK_RESTRICT srcPts = fPts;
1974 SkPoint* SK_RESTRICT pts = ptsParam;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001975
1976 switch (verb) {
1977 case kMove_Verb:
1978 if (fNeedClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001979 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001980 verb = this->autoClose(pts);
1981 if (verb == kClose_Verb) {
1982 fNeedClose = false;
1983 }
1984 return (Verb)verb;
1985 }
1986 if (fVerbs == fVerbStop) { // might be a trailing moveto
1987 return kDone_Verb;
1988 }
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001989 fMoveTo = *srcPts;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001990 pts[0] = *srcPts;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001991 srcPts += 1;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001992 fSegmentState = kAfterMove_SegmentState;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001993 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001994 fNeedClose = fForceClose;
1995 break;
1996 case kLine_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001997 pts[0] = this->cons_moveTo();
1998 pts[1] = srcPts[0];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001999 fLastPt = srcPts[0];
2000 fCloseLine = false;
2001 srcPts += 1;
2002 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002003 case kConic_Verb:
2004 fConicWeights += 1;
2005 // fall-through
reed@android.com8a1c16f2008-12-17 15:59:43 +00002006 case kQuad_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00002007 pts[0] = this->cons_moveTo();
2008 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002009 fLastPt = srcPts[1];
2010 srcPts += 2;
2011 break;
2012 case kCubic_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00002013 pts[0] = this->cons_moveTo();
2014 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002015 fLastPt = srcPts[2];
2016 srcPts += 3;
2017 break;
2018 case kClose_Verb:
2019 verb = this->autoClose(pts);
2020 if (verb == kLine_Verb) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002021 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00002022 } else {
2023 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00002024 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002025 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002026 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002027 break;
2028 }
2029 fPts = srcPts;
2030 return (Verb)verb;
2031}
2032
2033///////////////////////////////////////////////////////////////////////////////
2034
reed@android.com8a1c16f2008-12-17 15:59:43 +00002035/*
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002036 Format in compressed buffer: [ptCount, verbCount, pts[], verbs[]]
reed@android.com8a1c16f2008-12-17 15:59:43 +00002037*/
2038
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002039size_t SkPath::writeToMemory(void* storage) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002040 SkDEBUGCODE(this->validate();)
2041
halcanary96fcdcc2015-08-27 07:41:13 -07002042 if (nullptr == storage) {
caryclark69006412016-02-17 12:16:27 -08002043 const int byteCount = sizeof(int32_t) * 2 + fPathRef->writeSize();
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002044 return SkAlign4(byteCount);
2045 }
2046
2047 SkWBuffer buffer(storage);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002048
robertphillips@google.com466310d2013-12-03 16:43:54 +00002049 int32_t packed = (fConvexity << kConvexity_SerializationShift) |
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002050 (fFillType << kFillType_SerializationShift) |
reed026beb52015-06-10 14:23:15 -07002051 (fFirstDirection << kDirection_SerializationShift) |
reed8f086022015-06-11 14:22:19 -07002052 (fIsVolatile << kIsVolatile_SerializationShift) |
2053 kCurrent_Version;
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002054
robertphillips@google.com2972bb52012-08-07 17:32:51 +00002055 buffer.write32(packed);
caryclark69006412016-02-17 12:16:27 -08002056 buffer.write32(fLastMoveToIndex);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002057
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002058 fPathRef->writeToBuffer(&buffer);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002059
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002060 buffer.padToAlign4();
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002061 return buffer.pos();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002062}
2063
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002064size_t SkPath::readFromMemory(const void* storage, size_t length) {
Mike Reed1026ccf2017-01-08 14:35:29 -05002065 SkRBuffer buffer(storage, length);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002066
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00002067 int32_t packed;
2068 if (!buffer.readS32(&packed)) {
2069 return 0;
2070 }
2071
reed8f086022015-06-11 14:22:19 -07002072 unsigned version = packed & 0xFF;
caryclark69006412016-02-17 12:16:27 -08002073 if (version >= kPathPrivLastMoveToIndex_Version && !buffer.readS32(&fLastMoveToIndex)) {
2074 return 0;
2075 }
mtklein1b249332015-07-07 12:21:21 -07002076
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002077 fConvexity = (packed >> kConvexity_SerializationShift) & 0xFF;
robertphillips7070a3c2016-06-28 04:54:54 -07002078 fFillType = (packed >> kFillType_SerializationShift) & 0x3;
reed8f086022015-06-11 14:22:19 -07002079 uint8_t dir = (packed >> kDirection_SerializationShift) & 0x3;
jvanverthb3eb6872014-10-24 07:12:51 -07002080 fIsVolatile = (packed >> kIsVolatile_SerializationShift) & 0x1;
commit-bot@chromium.orgfed2ab62014-01-23 15:16:05 +00002081 SkPathRef* pathRef = SkPathRef::CreateFromBuffer(&buffer);
ajumaf8aec582016-01-13 13:46:31 -08002082 if (!pathRef) {
2083 return 0;
2084 }
2085
2086 fPathRef.reset(pathRef);
2087 SkDEBUGCODE(this->validate();)
2088 buffer.skipToAlign4();
reed@google.comabf15c12011-01-18 20:35:51 +00002089
reed8f086022015-06-11 14:22:19 -07002090 // compatibility check
2091 if (version < kPathPrivFirstDirection_Version) {
2092 switch (dir) { // old values
2093 case 0:
2094 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
2095 break;
2096 case 1:
2097 fFirstDirection = SkPathPriv::kCW_FirstDirection;
2098 break;
2099 case 2:
2100 fFirstDirection = SkPathPriv::kCCW_FirstDirection;
2101 break;
2102 default:
2103 SkASSERT(false);
2104 }
2105 } else {
2106 fFirstDirection = dir;
2107 }
2108
ajumaf8aec582016-01-13 13:46:31 -08002109 return buffer.pos();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002110}
2111
2112///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00002113
reede05fed02014-12-15 07:59:53 -08002114#include "SkStringUtils.h"
caryclark66a5d8b2014-06-24 08:30:15 -07002115#include "SkStream.h"
reed@google.com51bbe752013-01-17 22:07:50 +00002116
reed@google.com51bbe752013-01-17 22:07:50 +00002117static void append_params(SkString* str, const char label[], const SkPoint pts[],
reede05fed02014-12-15 07:59:53 -08002118 int count, SkScalarAsStringType strType, SkScalar conicWeight = -1) {
reed@google.com51bbe752013-01-17 22:07:50 +00002119 str->append(label);
2120 str->append("(");
skia.committer@gmail.com15dd3002013-01-18 07:07:28 +00002121
reed@google.com51bbe752013-01-17 22:07:50 +00002122 const SkScalar* values = &pts[0].fX;
2123 count *= 2;
2124
2125 for (int i = 0; i < count; ++i) {
reede05fed02014-12-15 07:59:53 -08002126 SkAppendScalar(str, values[i], strType);
reed@google.com51bbe752013-01-17 22:07:50 +00002127 if (i < count - 1) {
2128 str->append(", ");
2129 }
2130 }
reed@google.com277c3f82013-05-31 15:17:50 +00002131 if (conicWeight >= 0) {
2132 str->append(", ");
reede05fed02014-12-15 07:59:53 -08002133 SkAppendScalar(str, conicWeight, strType);
reed@google.com277c3f82013-05-31 15:17:50 +00002134 }
caryclark08fa28c2014-10-23 13:08:56 -07002135 str->append(");");
reede05fed02014-12-15 07:59:53 -08002136 if (kHex_SkScalarAsStringType == strType) {
caryclark08fa28c2014-10-23 13:08:56 -07002137 str->append(" // ");
2138 for (int i = 0; i < count; ++i) {
reede05fed02014-12-15 07:59:53 -08002139 SkAppendScalarDec(str, values[i]);
caryclark08fa28c2014-10-23 13:08:56 -07002140 if (i < count - 1) {
2141 str->append(", ");
2142 }
2143 }
2144 if (conicWeight >= 0) {
2145 str->append(", ");
reede05fed02014-12-15 07:59:53 -08002146 SkAppendScalarDec(str, conicWeight);
caryclark08fa28c2014-10-23 13:08:56 -07002147 }
2148 }
2149 str->append("\n");
reed@google.com51bbe752013-01-17 22:07:50 +00002150}
2151
caryclarke9562592014-09-15 09:26:09 -07002152void SkPath::dump(SkWStream* wStream, bool forceClose, bool dumpAsHex) const {
reede05fed02014-12-15 07:59:53 -08002153 SkScalarAsStringType asType = dumpAsHex ? kHex_SkScalarAsStringType : kDec_SkScalarAsStringType;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002154 Iter iter(*this, forceClose);
2155 SkPoint pts[4];
2156 Verb verb;
2157
reed@google.com51bbe752013-01-17 22:07:50 +00002158 SkString builder;
Cary Clark9f67f042016-12-16 11:32:54 -05002159 char const * const gFillTypeStrs[] = {
2160 "Winding",
2161 "EvenOdd",
2162 "InverseWinding",
2163 "InverseEvenOdd",
2164 };
2165 builder.printf("path.setFillType(SkPath::k%s_FillType);\n",
2166 gFillTypeStrs[(int) this->getFillType()]);
reed@google.com4a3b7142012-05-16 17:16:46 +00002167 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002168 switch (verb) {
2169 case kMove_Verb:
reede05fed02014-12-15 07:59:53 -08002170 append_params(&builder, "path.moveTo", &pts[0], 1, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002171 break;
2172 case kLine_Verb:
reede05fed02014-12-15 07:59:53 -08002173 append_params(&builder, "path.lineTo", &pts[1], 1, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002174 break;
2175 case kQuad_Verb:
reede05fed02014-12-15 07:59:53 -08002176 append_params(&builder, "path.quadTo", &pts[1], 2, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002177 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002178 case kConic_Verb:
reede05fed02014-12-15 07:59:53 -08002179 append_params(&builder, "path.conicTo", &pts[1], 2, asType, iter.conicWeight());
reed@google.com277c3f82013-05-31 15:17:50 +00002180 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002181 case kCubic_Verb:
reede05fed02014-12-15 07:59:53 -08002182 append_params(&builder, "path.cubicTo", &pts[1], 3, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002183 break;
2184 case kClose_Verb:
caryclark66a5d8b2014-06-24 08:30:15 -07002185 builder.append("path.close();\n");
reed@android.com8a1c16f2008-12-17 15:59:43 +00002186 break;
2187 default:
2188 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
2189 verb = kDone_Verb; // stop the loop
2190 break;
2191 }
caryclark1049f122015-04-20 08:31:59 -07002192 if (!wStream && builder.size()) {
2193 SkDebugf("%s", builder.c_str());
2194 builder.reset();
2195 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002196 }
caryclark66a5d8b2014-06-24 08:30:15 -07002197 if (wStream) {
2198 wStream->writeText(builder.c_str());
caryclark66a5d8b2014-06-24 08:30:15 -07002199 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002200}
2201
reed@android.come522ca52009-11-23 20:10:41 +00002202void SkPath::dump() const {
halcanary96fcdcc2015-08-27 07:41:13 -07002203 this->dump(nullptr, false, false);
caryclarke9562592014-09-15 09:26:09 -07002204}
2205
2206void SkPath::dumpHex() const {
halcanary96fcdcc2015-08-27 07:41:13 -07002207 this->dump(nullptr, false, true);
reed@android.come522ca52009-11-23 20:10:41 +00002208}
2209
2210#ifdef SK_DEBUG
2211void SkPath::validate() const {
reed@android.come522ca52009-11-23 20:10:41 +00002212 SkASSERT((fFillType & ~3) == 0);
reed@google.comabf15c12011-01-18 20:35:51 +00002213
djsollen@google.com077348c2012-10-22 20:23:32 +00002214#ifdef SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002215 if (!fBoundsIsDirty) {
2216 SkRect bounds;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002217
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002218 bool isFinite = compute_pt_bounds(&bounds, *fPathRef.get());
robertphillips@google.com5d8d1862012-08-15 14:36:41 +00002219 SkASSERT(SkToBool(fIsFinite) == isFinite);
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002220
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002221 if (fPathRef->countPoints() <= 1) {
reed@android.come522ca52009-11-23 20:10:41 +00002222 // if we're empty, fBounds may be empty but translated, so we can't
2223 // necessarily compare to bounds directly
2224 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
2225 // be [2, 2, 2, 2]
2226 SkASSERT(bounds.isEmpty());
2227 SkASSERT(fBounds.isEmpty());
2228 } else {
reed@google.comeac52bd2011-11-14 18:13:59 +00002229 if (bounds.isEmpty()) {
2230 SkASSERT(fBounds.isEmpty());
2231 } else {
reed@google.com3563c9e2011-11-14 19:34:57 +00002232 if (!fBounds.isEmpty()) {
2233 SkASSERT(fBounds.contains(bounds));
2234 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002235 }
reed@android.come522ca52009-11-23 20:10:41 +00002236 }
2237 }
djsollen@google.com077348c2012-10-22 20:23:32 +00002238#endif // SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002239}
djsollen@google.com077348c2012-10-22 20:23:32 +00002240#endif // SK_DEBUG
reed@android.come522ca52009-11-23 20:10:41 +00002241
reed@google.com04863fa2011-05-15 04:08:24 +00002242///////////////////////////////////////////////////////////////////////////////
2243
reed@google.com0b7b9822011-05-16 12:29:27 +00002244static int sign(SkScalar x) { return x < 0; }
2245#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00002246
robertphillipsc506e302014-09-16 09:43:31 -07002247enum DirChange {
2248 kLeft_DirChange,
2249 kRight_DirChange,
2250 kStraight_DirChange,
2251 kBackwards_DirChange,
2252
2253 kInvalid_DirChange
2254};
2255
2256
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002257static bool almost_equal(SkScalar compA, SkScalar compB) {
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002258 // The error epsilon was empirically derived; worse case round rects
2259 // with a mid point outset by 2x float epsilon in tests had an error
2260 // of 12.
2261 const int epsilon = 16;
2262 if (!SkScalarIsFinite(compA) || !SkScalarIsFinite(compB)) {
2263 return false;
2264 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002265 // no need to check for small numbers because SkPath::Iter has removed degenerate values
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002266 int aBits = SkFloatAs2sCompliment(compA);
2267 int bBits = SkFloatAs2sCompliment(compB);
2268 return aBits < bBits + epsilon && bBits < aBits + epsilon;
reed@google.com04863fa2011-05-15 04:08:24 +00002269}
2270
caryclarkb4216502015-03-02 13:02:34 -08002271static bool approximately_zero_when_compared_to(double x, double y) {
2272 return x == 0 || fabs(x) < fabs(y * FLT_EPSILON);
robertphillipsc506e302014-09-16 09:43:31 -07002273}
2274
caryclarkb4216502015-03-02 13:02:34 -08002275
reed@google.com04863fa2011-05-15 04:08:24 +00002276// only valid for a single contour
2277struct Convexicator {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002278 Convexicator()
2279 : fPtCount(0)
2280 , fConvexity(SkPath::kConvex_Convexity)
reed026beb52015-06-10 14:23:15 -07002281 , fFirstDirection(SkPathPriv::kUnknown_FirstDirection)
caryclark5ccef572015-03-02 10:07:56 -08002282 , fIsFinite(true)
2283 , fIsCurve(false) {
robertphillipsc506e302014-09-16 09:43:31 -07002284 fExpectedDir = kInvalid_DirChange;
reed@google.com04863fa2011-05-15 04:08:24 +00002285 // warnings
djsollen2f124632016-04-29 13:53:05 -07002286 fPriorPt.set(0,0);
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002287 fLastPt.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002288 fCurrPt.set(0, 0);
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002289 fLastVec.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002290 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00002291
2292 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00002293 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00002294 }
2295
2296 SkPath::Convexity getConvexity() const { return fConvexity; }
2297
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002298 /** The direction returned is only valid if the path is determined convex */
reed026beb52015-06-10 14:23:15 -07002299 SkPathPriv::FirstDirection getFirstDirection() const { return fFirstDirection; }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002300
reed@google.com04863fa2011-05-15 04:08:24 +00002301 void addPt(const SkPoint& pt) {
caryclarkd3d1a982014-12-08 04:57:38 -08002302 if (SkPath::kConcave_Convexity == fConvexity || !fIsFinite) {
reed@google.com04863fa2011-05-15 04:08:24 +00002303 return;
2304 }
2305
2306 if (0 == fPtCount) {
2307 fCurrPt = pt;
2308 ++fPtCount;
2309 } else {
2310 SkVector vec = pt - fCurrPt;
caryclarkd3d1a982014-12-08 04:57:38 -08002311 SkScalar lengthSqd = vec.lengthSqd();
2312 if (!SkScalarIsFinite(lengthSqd)) {
2313 fIsFinite = false;
caryclarke8c56662015-07-14 11:19:26 -07002314 } else if (lengthSqd) {
caryclarkb4216502015-03-02 13:02:34 -08002315 fPriorPt = fLastPt;
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002316 fLastPt = fCurrPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002317 fCurrPt = pt;
2318 if (++fPtCount == 2) {
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002319 fFirstVec = fLastVec = vec;
reed@google.com04863fa2011-05-15 04:08:24 +00002320 } else {
2321 SkASSERT(fPtCount > 2);
2322 this->addVec(vec);
2323 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002324
reed@google.com85b6e392011-05-15 20:25:17 +00002325 int sx = sign(vec.fX);
2326 int sy = sign(vec.fY);
2327 fDx += (sx != fSx);
2328 fDy += (sy != fSy);
2329 fSx = sx;
2330 fSy = sy;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002331
reed@google.com85b6e392011-05-15 20:25:17 +00002332 if (fDx > 3 || fDy > 3) {
2333 fConvexity = SkPath::kConcave_Convexity;
2334 }
reed@google.com04863fa2011-05-15 04:08:24 +00002335 }
2336 }
2337 }
2338
2339 void close() {
2340 if (fPtCount > 2) {
2341 this->addVec(fFirstVec);
2342 }
2343 }
2344
caryclarkb4216502015-03-02 13:02:34 -08002345 DirChange directionChange(const SkVector& curVec) {
2346 SkScalar cross = SkPoint::CrossProduct(fLastVec, curVec);
2347
2348 SkScalar smallest = SkTMin(fCurrPt.fX, SkTMin(fCurrPt.fY, SkTMin(fLastPt.fX, fLastPt.fY)));
2349 SkScalar largest = SkTMax(fCurrPt.fX, SkTMax(fCurrPt.fY, SkTMax(fLastPt.fX, fLastPt.fY)));
2350 largest = SkTMax(largest, -smallest);
2351
2352 if (!almost_equal(largest, largest + cross)) {
2353 int sign = SkScalarSignAsInt(cross);
2354 if (sign) {
2355 return (1 == sign) ? kRight_DirChange : kLeft_DirChange;
2356 }
2357 }
2358
2359 if (cross) {
2360 double dLastVecX = SkScalarToDouble(fLastPt.fX) - SkScalarToDouble(fPriorPt.fX);
2361 double dLastVecY = SkScalarToDouble(fLastPt.fY) - SkScalarToDouble(fPriorPt.fY);
2362 double dCurrVecX = SkScalarToDouble(fCurrPt.fX) - SkScalarToDouble(fLastPt.fX);
2363 double dCurrVecY = SkScalarToDouble(fCurrPt.fY) - SkScalarToDouble(fLastPt.fY);
2364 double dCross = dLastVecX * dCurrVecY - dLastVecY * dCurrVecX;
2365 if (!approximately_zero_when_compared_to(dCross, SkScalarToDouble(largest))) {
2366 int sign = SkScalarSignAsInt(SkDoubleToScalar(dCross));
2367 if (sign) {
2368 return (1 == sign) ? kRight_DirChange : kLeft_DirChange;
2369 }
2370 }
2371 }
2372
2373 if (!SkScalarNearlyZero(fLastVec.lengthSqd(), SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
2374 !SkScalarNearlyZero(curVec.lengthSqd(), SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
2375 fLastVec.dot(curVec) < 0.0f) {
2376 return kBackwards_DirChange;
2377 }
2378
2379 return kStraight_DirChange;
2380 }
2381
2382
caryclarkd3d1a982014-12-08 04:57:38 -08002383 bool isFinite() const {
2384 return fIsFinite;
2385 }
2386
caryclark5ccef572015-03-02 10:07:56 -08002387 void setCurve(bool isCurve) {
2388 fIsCurve = isCurve;
2389 }
2390
reed@google.com04863fa2011-05-15 04:08:24 +00002391private:
2392 void addVec(const SkVector& vec) {
2393 SkASSERT(vec.fX || vec.fY);
caryclarkb4216502015-03-02 13:02:34 -08002394 DirChange dir = this->directionChange(vec);
robertphillipsc506e302014-09-16 09:43:31 -07002395 switch (dir) {
2396 case kLeft_DirChange: // fall through
2397 case kRight_DirChange:
2398 if (kInvalid_DirChange == fExpectedDir) {
2399 fExpectedDir = dir;
reed026beb52015-06-10 14:23:15 -07002400 fFirstDirection = (kRight_DirChange == dir) ? SkPathPriv::kCW_FirstDirection
2401 : SkPathPriv::kCCW_FirstDirection;
robertphillipsc506e302014-09-16 09:43:31 -07002402 } else if (dir != fExpectedDir) {
2403 fConvexity = SkPath::kConcave_Convexity;
reed026beb52015-06-10 14:23:15 -07002404 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
robertphillipsc506e302014-09-16 09:43:31 -07002405 }
2406 fLastVec = vec;
2407 break;
2408 case kStraight_DirChange:
2409 break;
2410 case kBackwards_DirChange:
caryclark5ccef572015-03-02 10:07:56 -08002411 if (fIsCurve) {
liyuqianbdabcc42016-11-03 11:40:19 -07002412 // If any of the subsequent dir is non-backward, it'll be concave.
2413 // Otherwise, it's still convex.
2414 fExpectedDir = dir;
caryclark5ccef572015-03-02 10:07:56 -08002415 }
robertphillipsc506e302014-09-16 09:43:31 -07002416 fLastVec = vec;
2417 break;
2418 case kInvalid_DirChange:
2419 SkFAIL("Use of invalid direction change flag");
2420 break;
reed@google.com04863fa2011-05-15 04:08:24 +00002421 }
2422 }
2423
caryclarkb4216502015-03-02 13:02:34 -08002424 SkPoint fPriorPt;
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002425 SkPoint fLastPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002426 SkPoint fCurrPt;
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002427 // fLastVec does not necessarily start at fLastPt. We only advance it when the cross product
2428 // value with the current vec is deemed to be of a significant value.
2429 SkVector fLastVec, fFirstVec;
reed@google.com04863fa2011-05-15 04:08:24 +00002430 int fPtCount; // non-degenerate points
robertphillipsc506e302014-09-16 09:43:31 -07002431 DirChange fExpectedDir;
reed@google.com04863fa2011-05-15 04:08:24 +00002432 SkPath::Convexity fConvexity;
reed026beb52015-06-10 14:23:15 -07002433 SkPathPriv::FirstDirection fFirstDirection;
reed@google.com0b7b9822011-05-16 12:29:27 +00002434 int fDx, fDy, fSx, fSy;
caryclarkd3d1a982014-12-08 04:57:38 -08002435 bool fIsFinite;
caryclark5ccef572015-03-02 10:07:56 -08002436 bool fIsCurve;
reed@google.com04863fa2011-05-15 04:08:24 +00002437};
2438
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002439SkPath::Convexity SkPath::internalGetConvexity() const {
2440 SkASSERT(kUnknown_Convexity == fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002441 SkPoint pts[4];
2442 SkPath::Verb verb;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002443 SkPath::Iter iter(*this, true);
reed@google.com04863fa2011-05-15 04:08:24 +00002444
2445 int contourCount = 0;
2446 int count;
2447 Convexicator state;
2448
caryclarkd3d1a982014-12-08 04:57:38 -08002449 if (!isFinite()) {
2450 return kUnknown_Convexity;
2451 }
caryclarke8c56662015-07-14 11:19:26 -07002452 while ((verb = iter.next(pts, true, true)) != SkPath::kDone_Verb) {
reed@google.com04863fa2011-05-15 04:08:24 +00002453 switch (verb) {
2454 case kMove_Verb:
2455 if (++contourCount > 1) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002456 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002457 return kConcave_Convexity;
2458 }
2459 pts[1] = pts[0];
caryclark5ccef572015-03-02 10:07:56 -08002460 // fall through
2461 case kLine_Verb:
reed@google.com04863fa2011-05-15 04:08:24 +00002462 count = 1;
caryclark5ccef572015-03-02 10:07:56 -08002463 state.setCurve(false);
reed@google.com04863fa2011-05-15 04:08:24 +00002464 break;
caryclark5ccef572015-03-02 10:07:56 -08002465 case kQuad_Verb:
2466 // fall through
2467 case kConic_Verb:
2468 // fall through
2469 case kCubic_Verb:
2470 count = 2 + (kCubic_Verb == verb);
2471 // As an additional enhancement, this could set curve true only
2472 // if the curve is nonlinear
2473 state.setCurve(true);
2474 break;
reed@google.com04863fa2011-05-15 04:08:24 +00002475 case kClose_Verb:
caryclark5ccef572015-03-02 10:07:56 -08002476 state.setCurve(false);
reed@google.com04863fa2011-05-15 04:08:24 +00002477 state.close();
2478 count = 0;
2479 break;
2480 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00002481 SkDEBUGFAIL("bad verb");
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002482 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002483 return kConcave_Convexity;
2484 }
2485
2486 for (int i = 1; i <= count; i++) {
2487 state.addPt(pts[i]);
2488 }
2489 // early exit
caryclarkd3d1a982014-12-08 04:57:38 -08002490 if (!state.isFinite()) {
2491 return kUnknown_Convexity;
2492 }
reed@google.com04863fa2011-05-15 04:08:24 +00002493 if (kConcave_Convexity == state.getConvexity()) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002494 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002495 return kConcave_Convexity;
2496 }
2497 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002498 fConvexity = state.getConvexity();
reed026beb52015-06-10 14:23:15 -07002499 if (kConvex_Convexity == fConvexity && SkPathPriv::kUnknown_FirstDirection == fFirstDirection) {
2500 fFirstDirection = state.getFirstDirection();
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002501 }
2502 return static_cast<Convexity>(fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002503}
reed@google.com69a99432012-01-10 18:00:10 +00002504
2505///////////////////////////////////////////////////////////////////////////////
2506
2507class ContourIter {
2508public:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002509 ContourIter(const SkPathRef& pathRef);
reed@google.com69a99432012-01-10 18:00:10 +00002510
2511 bool done() const { return fDone; }
2512 // if !done() then these may be called
2513 int count() const { return fCurrPtCount; }
2514 const SkPoint* pts() const { return fCurrPt; }
2515 void next();
2516
2517private:
2518 int fCurrPtCount;
2519 const SkPoint* fCurrPt;
2520 const uint8_t* fCurrVerb;
2521 const uint8_t* fStopVerbs;
reed@google.com277c3f82013-05-31 15:17:50 +00002522 const SkScalar* fCurrConicWeight;
reed@google.com69a99432012-01-10 18:00:10 +00002523 bool fDone;
reed@google.comd1ab9322012-01-10 18:40:03 +00002524 SkDEBUGCODE(int fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002525};
2526
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002527ContourIter::ContourIter(const SkPathRef& pathRef) {
2528 fStopVerbs = pathRef.verbsMemBegin();
reed@google.com69a99432012-01-10 18:00:10 +00002529 fDone = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002530 fCurrPt = pathRef.points();
2531 fCurrVerb = pathRef.verbs();
reed@google.com277c3f82013-05-31 15:17:50 +00002532 fCurrConicWeight = pathRef.conicWeights();
reed@google.com69a99432012-01-10 18:00:10 +00002533 fCurrPtCount = 0;
reed@google.comd1ab9322012-01-10 18:40:03 +00002534 SkDEBUGCODE(fContourCounter = 0;)
reed@google.com69a99432012-01-10 18:00:10 +00002535 this->next();
2536}
2537
2538void ContourIter::next() {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002539 if (fCurrVerb <= fStopVerbs) {
reed@google.com69a99432012-01-10 18:00:10 +00002540 fDone = true;
2541 }
2542 if (fDone) {
2543 return;
2544 }
2545
2546 // skip pts of prev contour
2547 fCurrPt += fCurrPtCount;
2548
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002549 SkASSERT(SkPath::kMove_Verb == fCurrVerb[~0]);
reed@google.com69a99432012-01-10 18:00:10 +00002550 int ptCount = 1; // moveTo
2551 const uint8_t* verbs = fCurrVerb;
2552
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002553 for (--verbs; verbs > fStopVerbs; --verbs) {
2554 switch (verbs[~0]) {
reed@google.com69a99432012-01-10 18:00:10 +00002555 case SkPath::kMove_Verb:
reed@google.com69a99432012-01-10 18:00:10 +00002556 goto CONTOUR_END;
2557 case SkPath::kLine_Verb:
2558 ptCount += 1;
2559 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002560 case SkPath::kConic_Verb:
2561 fCurrConicWeight += 1;
2562 // fall-through
reed@google.com69a99432012-01-10 18:00:10 +00002563 case SkPath::kQuad_Verb:
2564 ptCount += 2;
2565 break;
2566 case SkPath::kCubic_Verb:
2567 ptCount += 3;
2568 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002569 case SkPath::kClose_Verb:
2570 break;
2571 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00002572 SkDEBUGFAIL("unexpected verb");
reed@google.com69a99432012-01-10 18:00:10 +00002573 break;
2574 }
2575 }
2576CONTOUR_END:
2577 fCurrPtCount = ptCount;
2578 fCurrVerb = verbs;
reed@google.comd1ab9322012-01-10 18:40:03 +00002579 SkDEBUGCODE(++fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002580}
2581
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002582// returns cross product of (p1 - p0) and (p2 - p0)
reed@google.com69a99432012-01-10 18:00:10 +00002583static SkScalar cross_prod(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002584 SkScalar cross = SkPoint::CrossProduct(p1 - p0, p2 - p0);
2585 // We may get 0 when the above subtracts underflow. We expect this to be
2586 // very rare and lazily promote to double.
2587 if (0 == cross) {
2588 double p0x = SkScalarToDouble(p0.fX);
2589 double p0y = SkScalarToDouble(p0.fY);
2590
2591 double p1x = SkScalarToDouble(p1.fX);
2592 double p1y = SkScalarToDouble(p1.fY);
2593
2594 double p2x = SkScalarToDouble(p2.fX);
2595 double p2y = SkScalarToDouble(p2.fY);
2596
2597 cross = SkDoubleToScalar((p1x - p0x) * (p2y - p0y) -
2598 (p1y - p0y) * (p2x - p0x));
2599
2600 }
2601 return cross;
reed@google.com69a99432012-01-10 18:00:10 +00002602}
2603
reed@google.comc1ea60a2012-01-31 15:15:36 +00002604// Returns the first pt with the maximum Y coordinate
reed@google.com69a99432012-01-10 18:00:10 +00002605static int find_max_y(const SkPoint pts[], int count) {
2606 SkASSERT(count > 0);
2607 SkScalar max = pts[0].fY;
reed@google.comc1ea60a2012-01-31 15:15:36 +00002608 int firstIndex = 0;
reed@google.com69a99432012-01-10 18:00:10 +00002609 for (int i = 1; i < count; ++i) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002610 SkScalar y = pts[i].fY;
2611 if (y > max) {
2612 max = y;
2613 firstIndex = i;
reed@google.com69a99432012-01-10 18:00:10 +00002614 }
2615 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002616 return firstIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002617}
2618
reed@google.comcabaf1d2012-01-11 21:03:05 +00002619static int find_diff_pt(const SkPoint pts[], int index, int n, int inc) {
2620 int i = index;
2621 for (;;) {
2622 i = (i + inc) % n;
2623 if (i == index) { // we wrapped around, so abort
2624 break;
2625 }
2626 if (pts[index] != pts[i]) { // found a different point, success!
2627 break;
2628 }
2629 }
2630 return i;
2631}
2632
reed@google.comc1ea60a2012-01-31 15:15:36 +00002633/**
2634 * Starting at index, and moving forward (incrementing), find the xmin and
2635 * xmax of the contiguous points that have the same Y.
2636 */
2637static int find_min_max_x_at_y(const SkPoint pts[], int index, int n,
2638 int* maxIndexPtr) {
2639 const SkScalar y = pts[index].fY;
2640 SkScalar min = pts[index].fX;
2641 SkScalar max = min;
2642 int minIndex = index;
2643 int maxIndex = index;
2644 for (int i = index + 1; i < n; ++i) {
2645 if (pts[i].fY != y) {
2646 break;
2647 }
2648 SkScalar x = pts[i].fX;
2649 if (x < min) {
2650 min = x;
2651 minIndex = i;
2652 } else if (x > max) {
2653 max = x;
2654 maxIndex = i;
2655 }
2656 }
2657 *maxIndexPtr = maxIndex;
2658 return minIndex;
2659}
2660
reed026beb52015-06-10 14:23:15 -07002661static void crossToDir(SkScalar cross, SkPathPriv::FirstDirection* dir) {
2662 *dir = cross > 0 ? SkPathPriv::kCW_FirstDirection : SkPathPriv::kCCW_FirstDirection;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002663}
2664
reed@google.comac8543f2012-01-30 20:51:25 +00002665/*
2666 * We loop through all contours, and keep the computed cross-product of the
2667 * contour that contained the global y-max. If we just look at the first
2668 * contour, we may find one that is wound the opposite way (correctly) since
2669 * it is the interior of a hole (e.g. 'o'). Thus we must find the contour
2670 * that is outer most (or at least has the global y-max) before we can consider
2671 * its cross product.
2672 */
reed026beb52015-06-10 14:23:15 -07002673bool SkPathPriv::CheapComputeFirstDirection(const SkPath& path, FirstDirection* dir) {
herb9f4dbca2015-09-28 11:05:47 -07002674 if (kUnknown_FirstDirection != path.fFirstDirection.load()) {
2675 *dir = static_cast<FirstDirection>(path.fFirstDirection.load());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002676 return true;
2677 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002678
2679 // don't want to pay the cost for computing this if it
2680 // is unknown, so we don't call isConvex()
reed026beb52015-06-10 14:23:15 -07002681 if (SkPath::kConvex_Convexity == path.getConvexityOrUnknown()) {
2682 SkASSERT(kUnknown_FirstDirection == path.fFirstDirection);
herb9f4dbca2015-09-28 11:05:47 -07002683 *dir = static_cast<FirstDirection>(path.fFirstDirection.load());
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002684 return false;
2685 }
reed@google.com69a99432012-01-10 18:00:10 +00002686
reed026beb52015-06-10 14:23:15 -07002687 ContourIter iter(*path.fPathRef.get());
reed@google.com69a99432012-01-10 18:00:10 +00002688
reed@google.comac8543f2012-01-30 20:51:25 +00002689 // initialize with our logical y-min
reed026beb52015-06-10 14:23:15 -07002690 SkScalar ymax = path.getBounds().fTop;
reed@google.comac8543f2012-01-30 20:51:25 +00002691 SkScalar ymaxCross = 0;
2692
reed@google.com69a99432012-01-10 18:00:10 +00002693 for (; !iter.done(); iter.next()) {
2694 int n = iter.count();
reed@google.comcabaf1d2012-01-11 21:03:05 +00002695 if (n < 3) {
2696 continue;
2697 }
djsollen@google.come63793a2012-03-21 15:39:03 +00002698
reed@google.comcabaf1d2012-01-11 21:03:05 +00002699 const SkPoint* pts = iter.pts();
reed@google.com69a99432012-01-10 18:00:10 +00002700 SkScalar cross = 0;
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002701 int index = find_max_y(pts, n);
2702 if (pts[index].fY < ymax) {
2703 continue;
2704 }
2705
2706 // If there is more than 1 distinct point at the y-max, we take the
2707 // x-min and x-max of them and just subtract to compute the dir.
2708 if (pts[(index + 1) % n].fY == pts[index].fY) {
2709 int maxIndex;
2710 int minIndex = find_min_max_x_at_y(pts, index, n, &maxIndex);
2711 if (minIndex == maxIndex) {
2712 goto TRY_CROSSPROD;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002713 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002714 SkASSERT(pts[minIndex].fY == pts[index].fY);
2715 SkASSERT(pts[maxIndex].fY == pts[index].fY);
2716 SkASSERT(pts[minIndex].fX <= pts[maxIndex].fX);
2717 // we just subtract the indices, and let that auto-convert to
2718 // SkScalar, since we just want - or + to signal the direction.
2719 cross = minIndex - maxIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002720 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002721 TRY_CROSSPROD:
2722 // Find a next and prev index to use for the cross-product test,
2723 // but we try to find pts that form non-zero vectors from pts[index]
2724 //
2725 // Its possible that we can't find two non-degenerate vectors, so
2726 // we have to guard our search (e.g. all the pts could be in the
2727 // same place).
2728
2729 // we pass n - 1 instead of -1 so we don't foul up % operator by
2730 // passing it a negative LH argument.
2731 int prev = find_diff_pt(pts, index, n, n - 1);
2732 if (prev == index) {
2733 // completely degenerate, skip to next contour
reed@google.comac8543f2012-01-30 20:51:25 +00002734 continue;
2735 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002736 int next = find_diff_pt(pts, index, n, 1);
2737 SkASSERT(next != index);
2738 cross = cross_prod(pts[prev], pts[index], pts[next]);
2739 // if we get a zero and the points are horizontal, then we look at the spread in
2740 // x-direction. We really should continue to walk away from the degeneracy until
2741 // there is a divergence.
2742 if (0 == cross && pts[prev].fY == pts[index].fY && pts[next].fY == pts[index].fY) {
2743 // construct the subtract so we get the correct Direction below
2744 cross = pts[index].fX - pts[next].fX;
reed@google.com188bfcf2012-01-17 18:26:38 +00002745 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002746 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002747
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002748 if (cross) {
2749 // record our best guess so far
2750 ymax = pts[index].fY;
2751 ymaxCross = cross;
reed@google.com69a99432012-01-10 18:00:10 +00002752 }
2753 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002754 if (ymaxCross) {
2755 crossToDir(ymaxCross, dir);
reed026beb52015-06-10 14:23:15 -07002756 path.fFirstDirection = *dir;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002757 return true;
2758 } else {
2759 return false;
2760 }
reed@google.comac8543f2012-01-30 20:51:25 +00002761}
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002762
2763///////////////////////////////////////////////////////////////////////////////
2764
caryclark9aacd902015-12-14 08:38:09 -08002765static bool between(SkScalar a, SkScalar b, SkScalar c) {
2766 SkASSERT(((a <= b && b <= c) || (a >= b && b >= c)) == ((a - b) * (c - b) <= 0)
2767 || (SkScalarNearlyZero(a) && SkScalarNearlyZero(b) && SkScalarNearlyZero(c)));
2768 return (a - b) * (c - b) <= 0;
2769}
2770
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002771static SkScalar eval_cubic_pts(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2772 SkScalar t) {
2773 SkScalar A = c3 + 3*(c1 - c2) - c0;
2774 SkScalar B = 3*(c2 - c1 - c1 + c0);
2775 SkScalar C = 3*(c1 - c0);
2776 SkScalar D = c0;
Mike Reeda99b6ce2017-02-04 11:04:26 -05002777 return poly_eval(A, B, C, D, t);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002778}
2779
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002780template <size_t N> static void find_minmax(const SkPoint pts[],
2781 SkScalar* minPtr, SkScalar* maxPtr) {
2782 SkScalar min, max;
2783 min = max = pts[0].fX;
2784 for (size_t i = 1; i < N; ++i) {
2785 min = SkMinScalar(min, pts[i].fX);
2786 max = SkMaxScalar(max, pts[i].fX);
2787 }
2788 *minPtr = min;
2789 *maxPtr = max;
2790}
2791
caryclark9cb5d752015-12-18 04:35:24 -08002792static bool checkOnCurve(SkScalar x, SkScalar y, const SkPoint& start, const SkPoint& end) {
2793 if (start.fY == end.fY) {
2794 return between(start.fX, x, end.fX) && x != end.fX;
2795 } else {
2796 return x == start.fX && y == start.fY;
2797 }
2798}
2799
caryclark9aacd902015-12-14 08:38:09 -08002800static int winding_mono_cubic(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
caryclark9cb5d752015-12-18 04:35:24 -08002801 SkScalar y0 = pts[0].fY;
2802 SkScalar y3 = pts[3].fY;
2803
2804 int dir = 1;
2805 if (y0 > y3) {
2806 SkTSwap(y0, y3);
2807 dir = -1;
2808 }
2809 if (y < y0 || y > y3) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002810 return 0;
2811 }
caryclark9cb5d752015-12-18 04:35:24 -08002812 if (checkOnCurve(x, y, pts[0], pts[3])) {
2813 *onCurveCount += 1;
caryclark9aacd902015-12-14 08:38:09 -08002814 return 0;
2815 }
caryclark9cb5d752015-12-18 04:35:24 -08002816 if (y == y3) {
2817 return 0;
2818 }
2819
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002820 // quickreject or quickaccept
2821 SkScalar min, max;
2822 find_minmax<4>(pts, &min, &max);
2823 if (x < min) {
2824 return 0;
2825 }
2826 if (x > max) {
2827 return dir;
2828 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002829
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002830 // compute the actual x(t) value
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002831 SkScalar t;
mbarbella276e6332016-05-31 14:44:01 -07002832 if (!SkCubicClipper::ChopMonoAtY(pts, y, &t)) {
mbarbella99600d02016-06-01 15:39:47 -07002833 return 0;
mbarbella276e6332016-05-31 14:44:01 -07002834 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002835 SkScalar xt = eval_cubic_pts(pts[0].fX, pts[1].fX, pts[2].fX, pts[3].fX, t);
caryclark9aacd902015-12-14 08:38:09 -08002836 if (SkScalarNearlyEqual(xt, x)) {
2837 if (x != pts[3].fX || y != pts[3].fY) { // don't test end points; they're start points
2838 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08002839 return 0;
caryclark9aacd902015-12-14 08:38:09 -08002840 }
2841 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002842 return xt < x ? dir : 0;
2843}
2844
caryclark9aacd902015-12-14 08:38:09 -08002845static int winding_cubic(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002846 SkPoint dst[10];
2847 int n = SkChopCubicAtYExtrema(pts, dst);
2848 int w = 0;
2849 for (int i = 0; i <= n; ++i) {
caryclark9aacd902015-12-14 08:38:09 -08002850 w += winding_mono_cubic(&dst[i * 3], x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002851 }
2852 return w;
2853}
2854
caryclark9aacd902015-12-14 08:38:09 -08002855static double conic_eval_numerator(const SkScalar src[], SkScalar w, SkScalar t) {
2856 SkASSERT(src);
2857 SkASSERT(t >= 0 && t <= 1);
2858 SkScalar src2w = src[2] * w;
2859 SkScalar C = src[0];
2860 SkScalar A = src[4] - 2 * src2w + C;
2861 SkScalar B = 2 * (src2w - C);
Mike Reeda99b6ce2017-02-04 11:04:26 -05002862 return poly_eval(A, B, C, t);
caryclark9aacd902015-12-14 08:38:09 -08002863}
2864
2865
2866static double conic_eval_denominator(SkScalar w, SkScalar t) {
2867 SkScalar B = 2 * (w - 1);
2868 SkScalar C = 1;
2869 SkScalar A = -B;
Mike Reeda99b6ce2017-02-04 11:04:26 -05002870 return poly_eval(A, B, C, t);
caryclark9aacd902015-12-14 08:38:09 -08002871}
2872
2873static int winding_mono_conic(const SkConic& conic, SkScalar x, SkScalar y, int* onCurveCount) {
2874 const SkPoint* pts = conic.fPts;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002875 SkScalar y0 = pts[0].fY;
2876 SkScalar y2 = pts[2].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002877
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002878 int dir = 1;
2879 if (y0 > y2) {
2880 SkTSwap(y0, y2);
2881 dir = -1;
2882 }
caryclark9aacd902015-12-14 08:38:09 -08002883 if (y < y0 || y > y2) {
2884 return 0;
2885 }
caryclark9cb5d752015-12-18 04:35:24 -08002886 if (checkOnCurve(x, y, pts[0], pts[2])) {
2887 *onCurveCount += 1;
2888 return 0;
2889 }
caryclark9aacd902015-12-14 08:38:09 -08002890 if (y == y2) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002891 return 0;
2892 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002893
caryclark9aacd902015-12-14 08:38:09 -08002894 SkScalar roots[2];
2895 SkScalar A = pts[2].fY;
2896 SkScalar B = pts[1].fY * conic.fW - y * conic.fW + y;
2897 SkScalar C = pts[0].fY;
2898 A += C - 2 * B; // A = a + c - 2*(b*w - yCept*w + yCept)
2899 B -= C; // B = b*w - w * yCept + yCept - a
2900 C -= y;
2901 int n = SkFindUnitQuadRoots(A, 2 * B, C, roots);
2902 SkASSERT(n <= 1);
2903 SkScalar xt;
2904 if (0 == n) {
caryclark9cb5d752015-12-18 04:35:24 -08002905 // zero roots are returned only when y0 == y
2906 // Need [0] if dir == 1
2907 // and [2] if dir == -1
2908 xt = pts[1 - dir].fX;
caryclark9aacd902015-12-14 08:38:09 -08002909 } else {
2910 SkScalar t = roots[0];
2911 xt = conic_eval_numerator(&pts[0].fX, conic.fW, t) / conic_eval_denominator(conic.fW, t);
2912 }
2913 if (SkScalarNearlyEqual(xt, x)) {
2914 if (x != pts[2].fX || y != pts[2].fY) { // don't test end points; they're start points
2915 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08002916 return 0;
caryclark9aacd902015-12-14 08:38:09 -08002917 }
2918 }
2919 return xt < x ? dir : 0;
2920}
2921
2922static bool is_mono_quad(SkScalar y0, SkScalar y1, SkScalar y2) {
2923 // return SkScalarSignAsInt(y0 - y1) + SkScalarSignAsInt(y1 - y2) != 0;
2924 if (y0 == y1) {
2925 return true;
2926 }
2927 if (y0 < y1) {
2928 return y1 <= y2;
2929 } else {
2930 return y1 >= y2;
2931 }
2932}
2933
2934static int winding_conic(const SkPoint pts[], SkScalar x, SkScalar y, SkScalar weight,
2935 int* onCurveCount) {
2936 SkConic conic(pts, weight);
caryclark9aacd902015-12-14 08:38:09 -08002937 SkConic chopped[2];
caryclark1e1e5092016-01-06 08:27:44 -08002938 // If the data points are very large, the conic may not be monotonic but may also
2939 // fail to chop. Then, the chopper does not split the original conic in two.
caryclarke114dd62016-01-06 08:15:44 -08002940 bool isMono = is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY) || !conic.chopAtYExtrema(chopped);
2941 int w = winding_mono_conic(isMono ? conic : chopped[0], x, y, onCurveCount);
2942 if (!isMono) {
caryclark9aacd902015-12-14 08:38:09 -08002943 w += winding_mono_conic(chopped[1], x, y, onCurveCount);
2944 }
2945 return w;
2946}
2947
2948static int winding_mono_quad(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
2949 SkScalar y0 = pts[0].fY;
2950 SkScalar y2 = pts[2].fY;
2951
2952 int dir = 1;
2953 if (y0 > y2) {
2954 SkTSwap(y0, y2);
2955 dir = -1;
2956 }
2957 if (y < y0 || y > y2) {
2958 return 0;
2959 }
caryclark9cb5d752015-12-18 04:35:24 -08002960 if (checkOnCurve(x, y, pts[0], pts[2])) {
2961 *onCurveCount += 1;
2962 return 0;
2963 }
caryclark9aacd902015-12-14 08:38:09 -08002964 if (y == y2) {
caryclark9aacd902015-12-14 08:38:09 -08002965 return 0;
2966 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002967 // bounds check on X (not required. is it faster?)
2968#if 0
2969 if (pts[0].fX > x && pts[1].fX > x && pts[2].fX > x) {
2970 return 0;
2971 }
2972#endif
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002973
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002974 SkScalar roots[2];
2975 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
2976 2 * (pts[1].fY - pts[0].fY),
2977 pts[0].fY - y,
2978 roots);
2979 SkASSERT(n <= 1);
2980 SkScalar xt;
2981 if (0 == n) {
caryclark9cb5d752015-12-18 04:35:24 -08002982 // zero roots are returned only when y0 == y
2983 // Need [0] if dir == 1
2984 // and [2] if dir == -1
2985 xt = pts[1 - dir].fX;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002986 } else {
2987 SkScalar t = roots[0];
2988 SkScalar C = pts[0].fX;
2989 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
2990 SkScalar B = 2 * (pts[1].fX - C);
Mike Reeda99b6ce2017-02-04 11:04:26 -05002991 xt = poly_eval(A, B, C, t);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002992 }
caryclark9aacd902015-12-14 08:38:09 -08002993 if (SkScalarNearlyEqual(xt, x)) {
2994 if (x != pts[2].fX || y != pts[2].fY) { // don't test end points; they're start points
2995 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08002996 return 0;
caryclark9aacd902015-12-14 08:38:09 -08002997 }
2998 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002999 return xt < x ? dir : 0;
3000}
3001
caryclark9aacd902015-12-14 08:38:09 -08003002static int winding_quad(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003003 SkPoint dst[5];
3004 int n = 0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003005
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003006 if (!is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY)) {
3007 n = SkChopQuadAtYExtrema(pts, dst);
3008 pts = dst;
3009 }
caryclark9aacd902015-12-14 08:38:09 -08003010 int w = winding_mono_quad(pts, x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003011 if (n > 0) {
caryclark9aacd902015-12-14 08:38:09 -08003012 w += winding_mono_quad(&pts[2], x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003013 }
3014 return w;
3015}
3016
caryclark9aacd902015-12-14 08:38:09 -08003017static int winding_line(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003018 SkScalar x0 = pts[0].fX;
3019 SkScalar y0 = pts[0].fY;
3020 SkScalar x1 = pts[1].fX;
3021 SkScalar y1 = pts[1].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003022
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003023 SkScalar dy = y1 - y0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003024
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003025 int dir = 1;
3026 if (y0 > y1) {
3027 SkTSwap(y0, y1);
3028 dir = -1;
3029 }
caryclark9aacd902015-12-14 08:38:09 -08003030 if (y < y0 || y > y1) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003031 return 0;
3032 }
caryclark9cb5d752015-12-18 04:35:24 -08003033 if (checkOnCurve(x, y, pts[0], pts[1])) {
3034 *onCurveCount += 1;
3035 return 0;
3036 }
3037 if (y == y1) {
caryclark9aacd902015-12-14 08:38:09 -08003038 return 0;
3039 }
Mike Reeda99b6ce2017-02-04 11:04:26 -05003040 SkScalar cross = (x1 - x0) * (y - pts[0].fY) - dy * (x - x0);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003041
caryclark9aacd902015-12-14 08:38:09 -08003042 if (!cross) {
fsc91065d2015-12-17 09:03:27 -08003043 // zero cross means the point is on the line, and since the case where
3044 // y of the query point is at the end point is handled above, we can be
3045 // sure that we're on the line (excluding the end point) here
caryclark9cb5d752015-12-18 04:35:24 -08003046 if (x != x1 || y != pts[1].fY) {
3047 *onCurveCount += 1;
3048 }
caryclark9aacd902015-12-14 08:38:09 -08003049 dir = 0;
3050 } else if (SkScalarSignAsInt(cross) == dir) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003051 dir = 0;
3052 }
3053 return dir;
3054}
3055
caryclark9aacd902015-12-14 08:38:09 -08003056static void tangent_cubic(const SkPoint pts[], SkScalar x, SkScalar y,
3057 SkTDArray<SkVector>* tangents) {
3058 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)
3059 && !between(pts[2].fY, y, pts[3].fY)) {
3060 return;
3061 }
3062 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)
3063 && !between(pts[2].fX, x, pts[3].fX)) {
3064 return;
3065 }
3066 SkPoint dst[10];
3067 int n = SkChopCubicAtYExtrema(pts, dst);
3068 for (int i = 0; i <= n; ++i) {
3069 SkPoint* c = &dst[i * 3];
3070 SkScalar t;
mbarbella276e6332016-05-31 14:44:01 -07003071 if (!SkCubicClipper::ChopMonoAtY(c, y, &t)) {
mbarbella99600d02016-06-01 15:39:47 -07003072 continue;
mbarbella276e6332016-05-31 14:44:01 -07003073 }
caryclark9aacd902015-12-14 08:38:09 -08003074 SkScalar xt = eval_cubic_pts(c[0].fX, c[1].fX, c[2].fX, c[3].fX, t);
3075 if (!SkScalarNearlyEqual(x, xt)) {
3076 continue;
3077 }
3078 SkVector tangent;
3079 SkEvalCubicAt(c, t, nullptr, &tangent, nullptr);
3080 tangents->push(tangent);
3081 }
3082}
3083
3084static void tangent_conic(const SkPoint pts[], SkScalar x, SkScalar y, SkScalar w,
3085 SkTDArray<SkVector>* tangents) {
3086 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)) {
3087 return;
3088 }
3089 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)) {
3090 return;
3091 }
3092 SkScalar roots[2];
3093 SkScalar A = pts[2].fY;
3094 SkScalar B = pts[1].fY * w - y * w + y;
3095 SkScalar C = pts[0].fY;
3096 A += C - 2 * B; // A = a + c - 2*(b*w - yCept*w + yCept)
3097 B -= C; // B = b*w - w * yCept + yCept - a
3098 C -= y;
3099 int n = SkFindUnitQuadRoots(A, 2 * B, C, roots);
3100 for (int index = 0; index < n; ++index) {
3101 SkScalar t = roots[index];
3102 SkScalar xt = conic_eval_numerator(&pts[0].fX, w, t) / conic_eval_denominator(w, t);
3103 if (!SkScalarNearlyEqual(x, xt)) {
3104 continue;
3105 }
3106 SkConic conic(pts, w);
3107 tangents->push(conic.evalTangentAt(t));
3108 }
3109}
3110
3111static void tangent_quad(const SkPoint pts[], SkScalar x, SkScalar y,
3112 SkTDArray<SkVector>* tangents) {
3113 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)) {
3114 return;
3115 }
3116 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)) {
3117 return;
3118 }
3119 SkScalar roots[2];
3120 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
3121 2 * (pts[1].fY - pts[0].fY),
3122 pts[0].fY - y,
3123 roots);
3124 for (int index = 0; index < n; ++index) {
3125 SkScalar t = roots[index];
3126 SkScalar C = pts[0].fX;
3127 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
3128 SkScalar B = 2 * (pts[1].fX - C);
Mike Reeda99b6ce2017-02-04 11:04:26 -05003129 SkScalar xt = poly_eval(A, B, C, t);
caryclark9aacd902015-12-14 08:38:09 -08003130 if (!SkScalarNearlyEqual(x, xt)) {
3131 continue;
3132 }
3133 tangents->push(SkEvalQuadTangentAt(pts, t));
3134 }
3135}
3136
3137static void tangent_line(const SkPoint pts[], SkScalar x, SkScalar y,
3138 SkTDArray<SkVector>* tangents) {
3139 SkScalar y0 = pts[0].fY;
3140 SkScalar y1 = pts[1].fY;
3141 if (!between(y0, y, y1)) {
3142 return;
3143 }
3144 SkScalar x0 = pts[0].fX;
3145 SkScalar x1 = pts[1].fX;
3146 if (!between(x0, x, x1)) {
3147 return;
3148 }
3149 SkScalar dx = x1 - x0;
3150 SkScalar dy = y1 - y0;
3151 if (!SkScalarNearlyEqual((x - x0) * dy, dx * (y - y0))) {
3152 return;
3153 }
3154 SkVector v;
3155 v.set(dx, dy);
3156 tangents->push(v);
3157}
3158
reed@google.com4db592c2013-10-30 17:39:43 +00003159static bool contains_inclusive(const SkRect& r, SkScalar x, SkScalar y) {
3160 return r.fLeft <= x && x <= r.fRight && r.fTop <= y && y <= r.fBottom;
3161}
3162
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003163bool SkPath::contains(SkScalar x, SkScalar y) const {
3164 bool isInverse = this->isInverseFillType();
3165 if (this->isEmpty()) {
3166 return isInverse;
3167 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003168
reed@google.com4db592c2013-10-30 17:39:43 +00003169 if (!contains_inclusive(this->getBounds(), x, y)) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003170 return isInverse;
3171 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003172
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003173 SkPath::Iter iter(*this, true);
3174 bool done = false;
3175 int w = 0;
caryclark9aacd902015-12-14 08:38:09 -08003176 int onCurveCount = 0;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003177 do {
3178 SkPoint pts[4];
3179 switch (iter.next(pts, false)) {
3180 case SkPath::kMove_Verb:
3181 case SkPath::kClose_Verb:
3182 break;
3183 case SkPath::kLine_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003184 w += winding_line(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003185 break;
3186 case SkPath::kQuad_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003187 w += winding_quad(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003188 break;
reed@google.com277c3f82013-05-31 15:17:50 +00003189 case SkPath::kConic_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003190 w += winding_conic(pts, x, y, iter.conicWeight(), &onCurveCount);
reed@google.com277c3f82013-05-31 15:17:50 +00003191 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003192 case SkPath::kCubic_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003193 w += winding_cubic(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003194 break;
3195 case SkPath::kDone_Verb:
3196 done = true;
3197 break;
reed@google.com277c3f82013-05-31 15:17:50 +00003198 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003199 } while (!done);
caryclark9aacd902015-12-14 08:38:09 -08003200 bool evenOddFill = SkPath::kEvenOdd_FillType == this->getFillType()
3201 || SkPath::kInverseEvenOdd_FillType == this->getFillType();
3202 if (evenOddFill) {
3203 w &= 1;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003204 }
caryclark9aacd902015-12-14 08:38:09 -08003205 if (w) {
3206 return !isInverse;
3207 }
3208 if (onCurveCount <= 1) {
3209 return SkToBool(onCurveCount) ^ isInverse;
3210 }
3211 if ((onCurveCount & 1) || evenOddFill) {
3212 return SkToBool(onCurveCount & 1) ^ isInverse;
3213 }
halcanary9d524f22016-03-29 09:03:52 -07003214 // If the point touches an even number of curves, and the fill is winding, check for
caryclark9aacd902015-12-14 08:38:09 -08003215 // coincidence. Count coincidence as places where the on curve points have identical tangents.
3216 iter.setPath(*this, true);
caryclark9cb5d752015-12-18 04:35:24 -08003217 done = false;
caryclark9aacd902015-12-14 08:38:09 -08003218 SkTDArray<SkVector> tangents;
3219 do {
3220 SkPoint pts[4];
3221 int oldCount = tangents.count();
3222 switch (iter.next(pts, false)) {
3223 case SkPath::kMove_Verb:
3224 case SkPath::kClose_Verb:
3225 break;
3226 case SkPath::kLine_Verb:
3227 tangent_line(pts, x, y, &tangents);
3228 break;
3229 case SkPath::kQuad_Verb:
3230 tangent_quad(pts, x, y, &tangents);
3231 break;
3232 case SkPath::kConic_Verb:
3233 tangent_conic(pts, x, y, iter.conicWeight(), &tangents);
3234 break;
3235 case SkPath::kCubic_Verb:
3236 tangent_cubic(pts, x, y, &tangents);
3237 break;
3238 case SkPath::kDone_Verb:
3239 done = true;
3240 break;
3241 }
3242 if (tangents.count() > oldCount) {
3243 int last = tangents.count() - 1;
3244 const SkVector& tangent = tangents[last];
3245 if (SkScalarNearlyZero(tangent.lengthSqd())) {
3246 tangents.remove(last);
3247 } else {
3248 for (int index = 0; index < last; ++index) {
3249 const SkVector& test = tangents[index];
3250 if (SkScalarNearlyZero(test.cross(tangent))
caryclark9cb5d752015-12-18 04:35:24 -08003251 && SkScalarSignAsInt(tangent.fX * test.fX) <= 0
3252 && SkScalarSignAsInt(tangent.fY * test.fY) <= 0) {
caryclark9aacd902015-12-14 08:38:09 -08003253 tangents.remove(last);
3254 tangents.removeShuffle(index);
3255 break;
3256 }
3257 }
3258 }
3259 }
3260 } while (!done);
3261 return SkToBool(tangents.count()) ^ isInverse;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003262}
fmalitaaa0df4e2015-12-01 09:13:23 -08003263
3264int SkPath::ConvertConicToQuads(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2,
3265 SkScalar w, SkPoint pts[], int pow2) {
3266 const SkConic conic(p0, p1, p2, w);
3267 return conic.chopIntoQuadsPOW2(pts, pow2);
3268}
bsalomonedc743a2016-06-01 09:42:31 -07003269
3270bool SkPathPriv::IsSimpleClosedRect(const SkPath& path, SkRect* rect, SkPath::Direction* direction,
3271 unsigned* start) {
3272 if (path.getSegmentMasks() != SkPath::kLine_SegmentMask) {
3273 return false;
3274 }
3275 SkPath::RawIter iter(path);
3276 SkPoint verbPts[4];
3277 SkPath::Verb v;
3278 SkPoint rectPts[5];
3279 int rectPtCnt = 0;
3280 while ((v = iter.next(verbPts)) != SkPath::kDone_Verb) {
3281 switch (v) {
3282 case SkPath::kMove_Verb:
3283 if (0 != rectPtCnt) {
3284 return false;
3285 }
3286 rectPts[0] = verbPts[0];
3287 ++rectPtCnt;
3288 break;
3289 case SkPath::kLine_Verb:
3290 if (5 == rectPtCnt) {
3291 return false;
3292 }
3293 rectPts[rectPtCnt] = verbPts[1];
3294 ++rectPtCnt;
3295 break;
3296 case SkPath::kClose_Verb:
3297 if (4 == rectPtCnt) {
3298 rectPts[4] = rectPts[0];
3299 rectPtCnt = 5;
3300 }
3301 break;
3302 default:
3303 return false;
3304 }
3305 }
3306 if (rectPtCnt < 5) {
3307 return false;
3308 }
3309 if (rectPts[0] != rectPts[4]) {
3310 return false;
3311 }
bsalomon057ae8a2016-07-24 05:37:26 -07003312 // Check for two cases of rectangles: pts 0 and 3 form a vertical edge or a horizontal edge (
3313 // and pts 1 and 2 the opposite vertical or horizontal edge).
3314 bool vec03IsVertical;
3315 if (rectPts[0].fX == rectPts[3].fX && rectPts[1].fX == rectPts[2].fX &&
3316 rectPts[0].fY == rectPts[1].fY && rectPts[3].fY == rectPts[2].fY) {
3317 // Make sure it has non-zero width and height
3318 if (rectPts[0].fX == rectPts[1].fX || rectPts[0].fY == rectPts[3].fY) {
bsalomonedc743a2016-06-01 09:42:31 -07003319 return false;
3320 }
bsalomon057ae8a2016-07-24 05:37:26 -07003321 vec03IsVertical = true;
3322 } else if (rectPts[0].fY == rectPts[3].fY && rectPts[1].fY == rectPts[2].fY &&
3323 rectPts[0].fX == rectPts[1].fX && rectPts[3].fX == rectPts[2].fX) {
3324 // Make sure it has non-zero width and height
3325 if (rectPts[0].fY == rectPts[1].fY || rectPts[0].fX == rectPts[3].fX) {
3326 return false;
3327 }
3328 vec03IsVertical = false;
3329 } else {
bsalomonedc743a2016-06-01 09:42:31 -07003330 return false;
3331 }
bsalomon057ae8a2016-07-24 05:37:26 -07003332 // Set sortFlags so that it has the low bit set if pt index 0 is on right edge and second bit
3333 // set if it is on the bottom edge.
3334 unsigned sortFlags =
3335 ((rectPts[0].fX < rectPts[2].fX) ? 0b00 : 0b01) |
3336 ((rectPts[0].fY < rectPts[2].fY) ? 0b00 : 0b10);
3337 switch (sortFlags) {
3338 case 0b00:
3339 rect->set(rectPts[0].fX, rectPts[0].fY, rectPts[2].fX, rectPts[2].fY);
3340 *direction = vec03IsVertical ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
3341 *start = 0;
3342 break;
3343 case 0b01:
3344 rect->set(rectPts[2].fX, rectPts[0].fY, rectPts[0].fX, rectPts[2].fY);
3345 *direction = vec03IsVertical ? SkPath::kCCW_Direction : SkPath::kCW_Direction;
3346 *start = 1;
3347 break;
3348 case 0b10:
3349 rect->set(rectPts[0].fX, rectPts[2].fY, rectPts[2].fX, rectPts[0].fY);
3350 *direction = vec03IsVertical ? SkPath::kCCW_Direction : SkPath::kCW_Direction;
3351 *start = 3;
3352 break;
3353 case 0b11:
3354 rect->set(rectPts[2].fX, rectPts[2].fY, rectPts[0].fX, rectPts[0].fY);
3355 *direction = vec03IsVertical ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
3356 *start = 2;
3357 break;
bsalomonedc743a2016-06-01 09:42:31 -07003358 }
bsalomonedc743a2016-06-01 09:42:31 -07003359 return true;
3360}
bsalomon21af9ca2016-08-25 12:29:23 -07003361
3362void SkPathPriv::CreateDrawArcPath(SkPath* path, const SkRect& oval, SkScalar startAngle,
3363 SkScalar sweepAngle, bool useCenter, bool isFillNoPathEffect) {
3364 SkASSERT(!oval.isEmpty());
3365 SkASSERT(sweepAngle);
3366
3367 path->reset();
3368 path->setIsVolatile(true);
3369 path->setFillType(SkPath::kWinding_FillType);
3370 if (isFillNoPathEffect && SkScalarAbs(sweepAngle) >= 360.f) {
3371 path->addOval(oval);
3372 return;
3373 }
3374 if (useCenter) {
3375 path->moveTo(oval.centerX(), oval.centerY());
3376 }
3377 // Arc to mods at 360 and drawArc is not supposed to.
3378 bool forceMoveTo = !useCenter;
3379 while (sweepAngle <= -360.f) {
3380 path->arcTo(oval, startAngle, -180.f, forceMoveTo);
3381 startAngle -= 180.f;
3382 path->arcTo(oval, startAngle, -180.f, false);
3383 startAngle -= 180.f;
3384 forceMoveTo = false;
3385 sweepAngle += 360.f;
3386 }
3387 while (sweepAngle >= 360.f) {
3388 path->arcTo(oval, startAngle, 180.f, forceMoveTo);
3389 startAngle += 180.f;
3390 path->arcTo(oval, startAngle, 180.f, false);
3391 startAngle += 180.f;
3392 forceMoveTo = false;
3393 sweepAngle -= 360.f;
3394 }
3395 path->arcTo(oval, startAngle, sweepAngle, forceMoveTo);
3396 if (useCenter) {
3397 path->close();
3398 }
3399}
Mike Reed0d7dac82017-02-02 17:45:56 -08003400
3401///////////////////////////////////////////////////////////////////////////////////////////////////
3402#include "SkNx.h"
3403
3404static int compute_quad_extremas(const SkPoint src[3], SkPoint extremas[3]) {
3405 SkScalar ts[2];
3406 int n = SkFindQuadExtrema(src[0].fX, src[1].fX, src[2].fX, ts);
3407 n += SkFindQuadExtrema(src[0].fY, src[1].fY, src[2].fY, &ts[n]);
3408 SkASSERT(n >= 0 && n <= 2);
3409 for (int i = 0; i < n; ++i) {
3410 extremas[i] = SkEvalQuadAt(src, ts[i]);
3411 }
3412 extremas[n] = src[2];
3413 return n + 1;
3414}
3415
3416static int compute_conic_extremas(const SkPoint src[3], SkScalar w, SkPoint extremas[3]) {
3417 SkConic conic(src[0], src[1], src[2], w);
3418 SkScalar ts[2];
3419 int n = conic.findXExtrema(ts);
3420 n += conic.findYExtrema(&ts[n]);
3421 SkASSERT(n >= 0 && n <= 2);
3422 for (int i = 0; i < n; ++i) {
3423 extremas[i] = conic.evalAt(ts[i]);
3424 }
3425 extremas[n] = src[2];
3426 return n + 1;
3427}
3428
3429static int compute_cubic_extremas(const SkPoint src[3], SkPoint extremas[5]) {
3430 SkScalar ts[4];
3431 int n = SkFindCubicExtrema(src[0].fX, src[1].fX, src[2].fX, src[3].fX, ts);
3432 n += SkFindCubicExtrema(src[0].fY, src[1].fY, src[2].fY, src[3].fY, &ts[n]);
3433 SkASSERT(n >= 0 && n <= 4);
3434 for (int i = 0; i < n; ++i) {
3435 SkEvalCubicAt(src, ts[i], &extremas[i], nullptr, nullptr);
3436 }
3437 extremas[n] = src[3];
3438 return n + 1;
3439}
3440
Mike Reed8d3196b2017-02-03 11:34:13 -05003441SkRect SkPath::computeTightBounds() const {
3442 if (0 == this->countVerbs()) {
3443 return SkRect::MakeEmpty();
Mike Reed0d7dac82017-02-02 17:45:56 -08003444 }
3445
Mike Reed8d3196b2017-02-03 11:34:13 -05003446 if (this->getSegmentMasks() == SkPath::kLine_SegmentMask) {
3447 return this->getBounds();
Mike Reed0d7dac82017-02-02 17:45:56 -08003448 }
3449
3450 SkPoint extremas[5]; // big enough to hold worst-case curve type (cubic) extremas + 1
3451 SkPoint pts[4];
Mike Reed8d3196b2017-02-03 11:34:13 -05003452 SkPath::RawIter iter(*this);
Mike Reed0d7dac82017-02-02 17:45:56 -08003453
3454 // initial with the first MoveTo, so we don't have to check inside the switch
3455 Sk2s min, max;
Mike Reed8d3196b2017-02-03 11:34:13 -05003456 min = max = from_point(this->getPoint(0));
Mike Reed0d7dac82017-02-02 17:45:56 -08003457 for (;;) {
3458 int count = 0;
3459 switch (iter.next(pts)) {
3460 case SkPath::kMove_Verb:
3461 extremas[0] = pts[0];
3462 count = 1;
3463 break;
3464 case SkPath::kLine_Verb:
3465 extremas[0] = pts[1];
3466 count = 1;
3467 break;
3468 case SkPath::kQuad_Verb:
3469 count = compute_quad_extremas(pts, extremas);
3470 break;
3471 case SkPath::kConic_Verb:
3472 count = compute_conic_extremas(pts, iter.conicWeight(), extremas);
3473 break;
3474 case SkPath::kCubic_Verb:
3475 count = compute_cubic_extremas(pts, extremas);
3476 break;
3477 case SkPath::kClose_Verb:
3478 break;
3479 case SkPath::kDone_Verb:
3480 goto DONE;
3481 }
3482 for (int i = 0; i < count; ++i) {
3483 Sk2s tmp = from_point(extremas[i]);
3484 min = Sk2s::Min(min, tmp);
3485 max = Sk2s::Max(max, tmp);
3486 }
3487 }
3488DONE:
Mike Reed8d3196b2017-02-03 11:34:13 -05003489 SkRect bounds;
3490 min.store((SkPoint*)&bounds.fLeft);
3491 max.store((SkPoint*)&bounds.fRight);
3492 return bounds;
Mike Reed0d7dac82017-02-02 17:45:56 -08003493}