blob: 4c18e63b23971970d58646166743618fa5ac8760 [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
17////////////////////////////////////////////////////////////////////////////
18
reed@google.com3563c9e2011-11-14 19:34:57 +000019/**
20 * Path.bounds is defined to be the bounds of all the control points.
21 * If we called bounds.join(r) we would skip r if r was empty, which breaks
22 * our promise. Hence we have a custom joiner that doesn't look at emptiness
23 */
24static void joinNoEmptyChecks(SkRect* dst, const SkRect& src) {
25 dst->fLeft = SkMinScalar(dst->fLeft, src.fLeft);
26 dst->fTop = SkMinScalar(dst->fTop, src.fTop);
27 dst->fRight = SkMaxScalar(dst->fRight, src.fRight);
28 dst->fBottom = SkMaxScalar(dst->fBottom, src.fBottom);
29}
30
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000031static bool is_degenerate(const SkPath& path) {
32 SkPath::Iter iter(path, false);
33 SkPoint pts[4];
34 return SkPath::kDone_Verb == iter.next(pts);
35}
36
bsalomon@google.com30c174b2012-11-13 14:36:42 +000037class SkAutoDisableDirectionCheck {
38public:
39 SkAutoDisableDirectionCheck(SkPath* path) : fPath(path) {
herb9f4dbca2015-09-28 11:05:47 -070040 fSaved = static_cast<SkPathPriv::FirstDirection>(fPath->fFirstDirection.load());
bsalomon@google.com30c174b2012-11-13 14:36:42 +000041 }
42
43 ~SkAutoDisableDirectionCheck() {
reed026beb52015-06-10 14:23:15 -070044 fPath->fFirstDirection = fSaved;
bsalomon@google.com30c174b2012-11-13 14:36:42 +000045 }
46
47private:
reed026beb52015-06-10 14:23:15 -070048 SkPath* fPath;
49 SkPathPriv::FirstDirection fSaved;
bsalomon@google.com30c174b2012-11-13 14:36:42 +000050};
commit-bot@chromium.orge61a86c2013-11-18 16:03:59 +000051#define SkAutoDisableDirectionCheck(...) SK_REQUIRE_LOCAL_VAR(SkAutoDisableDirectionCheck)
bsalomon@google.com30c174b2012-11-13 14:36:42 +000052
reed@android.com8a1c16f2008-12-17 15:59:43 +000053/* This guy's constructor/destructor bracket a path editing operation. It is
54 used when we know the bounds of the amount we are going to add to the path
55 (usually a new contour, but not required).
reed@google.comabf15c12011-01-18 20:35:51 +000056
reed@android.com8a1c16f2008-12-17 15:59:43 +000057 It captures some state about the path up front (i.e. if it already has a
robertphillips@google.comca0c8382013-09-26 12:18:23 +000058 cached bounds), and then if it can, it updates the cache bounds explicitly,
reed@android.comd252db02009-04-01 18:31:44 +000059 avoiding the need to revisit all of the points in getBounds().
reed@google.comabf15c12011-01-18 20:35:51 +000060
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000061 It also notes if the path was originally degenerate, and if so, sets
62 isConvex to true. Thus it can only be used if the contour being added is
robertphillips@google.com466310d2013-12-03 16:43:54 +000063 convex.
reed@android.com8a1c16f2008-12-17 15:59:43 +000064 */
65class SkAutoPathBoundsUpdate {
66public:
67 SkAutoPathBoundsUpdate(SkPath* path, const SkRect& r) : fRect(r) {
68 this->init(path);
69 }
70
71 SkAutoPathBoundsUpdate(SkPath* path, SkScalar left, SkScalar top,
72 SkScalar right, SkScalar bottom) {
73 fRect.set(left, top, right, bottom);
74 this->init(path);
75 }
reed@google.comabf15c12011-01-18 20:35:51 +000076
reed@android.com8a1c16f2008-12-17 15:59:43 +000077 ~SkAutoPathBoundsUpdate() {
reed@google.com44699382013-10-31 17:28:30 +000078 fPath->setConvexity(fDegenerate ? SkPath::kConvex_Convexity
79 : SkPath::kUnknown_Convexity);
robertphillips@google.comca0c8382013-09-26 12:18:23 +000080 if (fEmpty || fHasValidBounds) {
81 fPath->setBounds(fRect);
reed@android.com8a1c16f2008-12-17 15:59:43 +000082 }
83 }
reed@google.comabf15c12011-01-18 20:35:51 +000084
reed@android.com8a1c16f2008-12-17 15:59:43 +000085private:
reed@android.com6b82d1a2009-06-03 02:35:01 +000086 SkPath* fPath;
87 SkRect fRect;
robertphillips@google.comca0c8382013-09-26 12:18:23 +000088 bool fHasValidBounds;
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000089 bool fDegenerate;
reed@android.com6b82d1a2009-06-03 02:35:01 +000090 bool fEmpty;
reed@google.comabf15c12011-01-18 20:35:51 +000091
reed@android.com6b82d1a2009-06-03 02:35:01 +000092 void init(SkPath* path) {
robertphillips@google.comca0c8382013-09-26 12:18:23 +000093 // Cannot use fRect for our bounds unless we know it is sorted
94 fRect.sort();
reed@android.com8a1c16f2008-12-17 15:59:43 +000095 fPath = path;
reed@google.coma8790de2012-10-24 21:04:04 +000096 // Mark the path's bounds as dirty if (1) they are, or (2) the path
97 // is non-finite, and therefore its bounds are not meaningful
robertphillips@google.comca0c8382013-09-26 12:18:23 +000098 fHasValidBounds = path->hasComputedBounds() && path->isFinite();
reed@android.com8a1c16f2008-12-17 15:59:43 +000099 fEmpty = path->isEmpty();
robertphillips@google.comca0c8382013-09-26 12:18:23 +0000100 if (fHasValidBounds && !fEmpty) {
101 joinNoEmptyChecks(&fRect, fPath->getBounds());
102 }
103 fDegenerate = is_degenerate(*path);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000104 }
105};
commit-bot@chromium.orge61a86c2013-11-18 16:03:59 +0000106#define SkAutoPathBoundsUpdate(...) SK_REQUIRE_LOCAL_VAR(SkAutoPathBoundsUpdate)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000107
reed@android.com8a1c16f2008-12-17 15:59:43 +0000108////////////////////////////////////////////////////////////////////////////
109
110/*
111 Stores the verbs and points as they are given to us, with exceptions:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000112 - we only record "Close" if it was immediately preceeded by Move | Line | Quad | Cubic
reed@android.com8a1c16f2008-12-17 15:59:43 +0000113 - we insert a Move(0,0) if Line | Quad | Cubic is our first command
114
115 The iterator does more cleanup, especially if forceClose == true
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000116 1. If we encounter degenerate segments, remove them
117 2. if we encounter Close, return a cons'd up Line() first (if the curr-pt != start-pt)
118 3. if we encounter Move without a preceeding Close, and forceClose is true, goto #2
119 4. if we encounter Line | Quad | Cubic after Close, cons up a Move
reed@android.com8a1c16f2008-12-17 15:59:43 +0000120*/
121
122////////////////////////////////////////////////////////////////////////////
123
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000124// flag to require a moveTo if we begin with something else, like lineTo etc.
125#define INITIAL_LASTMOVETOINDEX_VALUE ~0
126
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000127SkPath::SkPath()
djsollen523cda32015-02-17 08:06:32 -0800128 : fPathRef(SkPathRef::CreateEmpty()) {
bungeman@google.coma5809a32013-06-21 15:13:34 +0000129 this->resetFields();
jvanverthb3eb6872014-10-24 07:12:51 -0700130 fIsVolatile = false;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000131}
132
133void SkPath::resetFields() {
134 //fPathRef is assumed to have been emptied by the caller.
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000135 fLastMoveToIndex = INITIAL_LASTMOVETOINDEX_VALUE;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000136 fFillType = kWinding_FillType;
reed@google.com04863fa2011-05-15 04:08:24 +0000137 fConvexity = kUnknown_Convexity;
reed026beb52015-06-10 14:23:15 -0700138 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000139
140 // We don't touch Android's fSourcePath. It's used to track texture garbage collection, so we
halcanary96fcdcc2015-08-27 07:41:13 -0700141 // don't want to muck with it if it's been set to something non-nullptr.
reed@android.com6b82d1a2009-06-03 02:35:01 +0000142}
reed@android.com8a1c16f2008-12-17 15:59:43 +0000143
bungeman@google.coma5809a32013-06-21 15:13:34 +0000144SkPath::SkPath(const SkPath& that)
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000145 : fPathRef(SkRef(that.fPathRef.get())) {
bungeman@google.coma5809a32013-06-21 15:13:34 +0000146 this->copyFields(that);
147 SkDEBUGCODE(that.validate();)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000148}
149
150SkPath::~SkPath() {
151 SkDEBUGCODE(this->validate();)
152}
153
bungeman@google.coma5809a32013-06-21 15:13:34 +0000154SkPath& SkPath::operator=(const SkPath& that) {
155 SkDEBUGCODE(that.validate();)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000156
bungeman@google.coma5809a32013-06-21 15:13:34 +0000157 if (this != &that) {
158 fPathRef.reset(SkRef(that.fPathRef.get()));
159 this->copyFields(that);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000160 }
161 SkDEBUGCODE(this->validate();)
162 return *this;
163}
164
bungeman@google.coma5809a32013-06-21 15:13:34 +0000165void SkPath::copyFields(const SkPath& that) {
166 //fPathRef is assumed to have been set by the caller.
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000167 fLastMoveToIndex = that.fLastMoveToIndex;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000168 fFillType = that.fFillType;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000169 fConvexity = that.fConvexity;
herb9f4dbca2015-09-28 11:05:47 -0700170 // Simulate fFirstDirection = that.fFirstDirection;
171 fFirstDirection.store(that.fFirstDirection.load());
jvanverthb3eb6872014-10-24 07:12:51 -0700172 fIsVolatile = that.fIsVolatile;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000173}
174
djsollen@google.com9c1a9672013-08-09 13:49:13 +0000175bool operator==(const SkPath& a, const SkPath& b) {
reed@android.com6b82d1a2009-06-03 02:35:01 +0000176 // note: don't need to look at isConvex or bounds, since just comparing the
177 // raw data is sufficient.
reed@android.com3abec1d2009-03-02 05:36:20 +0000178 return &a == &b ||
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000179 (a.fFillType == b.fFillType && *a.fPathRef.get() == *b.fPathRef.get());
reed@android.com3abec1d2009-03-02 05:36:20 +0000180}
181
bungeman@google.coma5809a32013-06-21 15:13:34 +0000182void SkPath::swap(SkPath& that) {
bungeman@google.coma5809a32013-06-21 15:13:34 +0000183 if (this != &that) {
bungeman77a53de2015-10-01 12:28:49 -0700184 fPathRef.swap(that.fPathRef);
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000185 SkTSwap<int>(fLastMoveToIndex, that.fLastMoveToIndex);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000186 SkTSwap<uint8_t>(fFillType, that.fFillType);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000187 SkTSwap<uint8_t>(fConvexity, that.fConvexity);
herb9f4dbca2015-09-28 11:05:47 -0700188 // Simulate SkTSwap<uint8_t>(fFirstDirection, that.fFirstDirection);
189 uint8_t temp = fFirstDirection;
190 fFirstDirection.store(that.fFirstDirection.load());
191 that.fFirstDirection.store(temp);
jvanverthb3eb6872014-10-24 07:12:51 -0700192 SkTSwap<SkBool8>(fIsVolatile, that.fIsVolatile);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000193 }
194}
195
caryclark8e7b19d2016-02-18 04:11:48 -0800196bool SkPath::isInterpolatable(const SkPath& compare) const {
197 int count = fPathRef->countVerbs();
198 if (count != compare.fPathRef->countVerbs()) {
199 return false;
200 }
201 if (!count) {
202 return true;
203 }
204 if (memcmp(fPathRef->verbsMemBegin(), compare.fPathRef->verbsMemBegin(),
205 count)) {
206 return false;
207 }
caryclarkdcd1fcc2016-02-18 09:00:01 -0800208 return !fPathRef->countWeights() ||
209 !SkToBool(memcmp(fPathRef->conicWeights(), compare.fPathRef->conicWeights(),
caryclark8e7b19d2016-02-18 04:11:48 -0800210 fPathRef->countWeights() * sizeof(*fPathRef->conicWeights())));
211}
212
213bool SkPath::interpolate(const SkPath& ending, SkScalar weight, SkPath* out) const {
214 int verbCount = fPathRef->countVerbs();
215 if (verbCount != ending.fPathRef->countVerbs()) {
216 return false;
217 }
caryclarka1105382016-02-18 06:13:25 -0800218 if (!verbCount) {
219 return true;
220 }
caryclark8e7b19d2016-02-18 04:11:48 -0800221 out->reset();
222 out->addPath(*this);
bungeman6bd52842016-10-27 09:30:08 -0700223 fPathRef->interpolate(*ending.fPathRef, weight, out->fPathRef.get());
caryclark8e7b19d2016-02-18 04:11:48 -0800224 return true;
225}
226
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000227static inline bool check_edge_against_rect(const SkPoint& p0,
228 const SkPoint& p1,
229 const SkRect& rect,
reed026beb52015-06-10 14:23:15 -0700230 SkPathPriv::FirstDirection dir) {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000231 const SkPoint* edgeBegin;
232 SkVector v;
reed026beb52015-06-10 14:23:15 -0700233 if (SkPathPriv::kCW_FirstDirection == dir) {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000234 v = p1 - p0;
235 edgeBegin = &p0;
236 } else {
237 v = p0 - p1;
238 edgeBegin = &p1;
239 }
240 if (v.fX || v.fY) {
241 // check the cross product of v with the vec from edgeBegin to each rect corner
242 SkScalar yL = SkScalarMul(v.fY, rect.fLeft - edgeBegin->fX);
243 SkScalar xT = SkScalarMul(v.fX, rect.fTop - edgeBegin->fY);
244 SkScalar yR = SkScalarMul(v.fY, rect.fRight - edgeBegin->fX);
245 SkScalar xB = SkScalarMul(v.fX, rect.fBottom - edgeBegin->fY);
246 if ((xT < yL) || (xT < yR) || (xB < yL) || (xB < yR)) {
247 return false;
248 }
249 }
250 return true;
251}
252
253bool SkPath::conservativelyContainsRect(const SkRect& rect) const {
254 // This only handles non-degenerate convex paths currently.
255 if (kConvex_Convexity != this->getConvexity()) {
256 return false;
257 }
skia.committer@gmail.comcec8de62012-11-14 02:01:22 +0000258
reed026beb52015-06-10 14:23:15 -0700259 SkPathPriv::FirstDirection direction;
260 if (!SkPathPriv::CheapComputeFirstDirection(*this, &direction)) {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000261 return false;
262 }
263
264 SkPoint firstPt;
265 SkPoint prevPt;
Lee Salzmana19f0242017-01-12 13:06:21 -0500266 SkPath::Iter iter(*this, true);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000267 SkPath::Verb verb;
268 SkPoint pts[4];
269 SkDEBUGCODE(int moveCnt = 0;)
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000270 SkDEBUGCODE(int segmentCount = 0;)
271 SkDEBUGCODE(int closeCount = 0;)
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000272
Lee Salzmana19f0242017-01-12 13:06:21 -0500273 while ((verb = iter.next(pts, true, true)) != kDone_Verb) {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000274 int nextPt = -1;
275 switch (verb) {
276 case kMove_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000277 SkASSERT(!segmentCount && !closeCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000278 SkDEBUGCODE(++moveCnt);
279 firstPt = prevPt = pts[0];
280 break;
281 case kLine_Verb:
282 nextPt = 1;
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000283 SkASSERT(moveCnt && !closeCount);
284 SkDEBUGCODE(++segmentCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000285 break;
286 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000287 case kConic_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000288 SkASSERT(moveCnt && !closeCount);
289 SkDEBUGCODE(++segmentCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000290 nextPt = 2;
291 break;
292 case kCubic_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000293 SkASSERT(moveCnt && !closeCount);
294 SkDEBUGCODE(++segmentCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000295 nextPt = 3;
296 break;
297 case kClose_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000298 SkDEBUGCODE(++closeCount;)
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000299 break;
300 default:
301 SkDEBUGFAIL("unknown verb");
302 }
303 if (-1 != nextPt) {
reed220f9262014-12-17 08:21:04 -0800304 if (SkPath::kConic_Verb == verb) {
305 SkConic orig;
306 orig.set(pts, iter.conicWeight());
307 SkPoint quadPts[5];
308 int count = orig.chopIntoQuadsPOW2(quadPts, 1);
djsollenf2b340f2016-01-29 08:51:04 -0800309 SkASSERT_RELEASE(2 == count);
reed220f9262014-12-17 08:21:04 -0800310
311 if (!check_edge_against_rect(quadPts[0], quadPts[2], rect, direction)) {
312 return false;
313 }
314 if (!check_edge_against_rect(quadPts[2], quadPts[4], rect, direction)) {
315 return false;
316 }
317 } else {
318 if (!check_edge_against_rect(prevPt, pts[nextPt], rect, direction)) {
319 return false;
320 }
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000321 }
322 prevPt = pts[nextPt];
323 }
324 }
325
326 return check_edge_against_rect(prevPt, firstPt, rect, direction);
327}
328
robertphillips@google.com7101abe2013-10-29 22:45:37 +0000329uint32_t SkPath::getGenerationID() const {
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000330 uint32_t genID = fPathRef->genID();
djsollen523cda32015-02-17 08:06:32 -0800331#ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000332 SkASSERT((unsigned)fFillType < (1 << (32 - kPathRefGenIDBitCnt)));
333 genID |= static_cast<uint32_t>(fFillType) << kPathRefGenIDBitCnt;
334#endif
335 return genID;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000336}
djsollen@google.come63793a2012-03-21 15:39:03 +0000337
reed@android.com8a1c16f2008-12-17 15:59:43 +0000338void SkPath::reset() {
339 SkDEBUGCODE(this->validate();)
340
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000341 fPathRef.reset(SkPathRef::CreateEmpty());
bungeman@google.coma5809a32013-06-21 15:13:34 +0000342 this->resetFields();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000343}
344
345void SkPath::rewind() {
346 SkDEBUGCODE(this->validate();)
347
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000348 SkPathRef::Rewind(&fPathRef);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000349 this->resetFields();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000350}
351
fsb1475b02016-01-20 09:51:07 -0800352bool SkPath::isLastContourClosed() const {
353 int verbCount = fPathRef->countVerbs();
354 if (0 == verbCount) {
355 return false;
356 }
357 return kClose_Verb == fPathRef->atVerb(verbCount - 1);
358}
359
reed@google.com7e6c4d12012-05-10 14:05:43 +0000360bool SkPath::isLine(SkPoint line[2]) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000361 int verbCount = fPathRef->countVerbs();
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000362
commit-bot@chromium.orga62efcc2013-08-05 13:23:13 +0000363 if (2 == verbCount) {
364 SkASSERT(kMove_Verb == fPathRef->atVerb(0));
365 if (kLine_Verb == fPathRef->atVerb(1)) {
366 SkASSERT(2 == fPathRef->countPoints());
reed@google.com7e6c4d12012-05-10 14:05:43 +0000367 if (line) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000368 const SkPoint* pts = fPathRef->points();
reed@google.com7e6c4d12012-05-10 14:05:43 +0000369 line[0] = pts[0];
370 line[1] = pts[1];
371 }
372 return true;
373 }
374 }
375 return false;
376}
377
caryclark@google.comf1316942011-07-26 19:54:45 +0000378/*
379 Determines if path is a rect by keeping track of changes in direction
380 and looking for a loop either clockwise or counterclockwise.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000381
caryclark@google.comf1316942011-07-26 19:54:45 +0000382 The direction is computed such that:
383 0: vertical up
caryclark@google.comf68154a2012-11-21 15:18:06 +0000384 1: horizontal left
caryclark@google.comf1316942011-07-26 19:54:45 +0000385 2: vertical down
caryclark@google.comf68154a2012-11-21 15:18:06 +0000386 3: horizontal right
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000387
caryclark@google.comf1316942011-07-26 19:54:45 +0000388A rectangle cycles up/right/down/left or up/left/down/right.
389
390The test fails if:
391 The path is closed, and followed by a line.
392 A second move creates a new endpoint.
393 A diagonal line is parsed.
394 There's more than four changes of direction.
395 There's a discontinuity on the line (e.g., a move in the middle)
396 The line reverses direction.
caryclark@google.comf1316942011-07-26 19:54:45 +0000397 The path contains a quadratic or cubic.
398 The path contains fewer than four points.
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000399 *The rectangle doesn't complete a cycle.
400 *The final point isn't equal to the first point.
401
402 *These last two conditions we relax if we have a 3-edge path that would
403 form a rectangle if it were closed (as we do when we fill a path)
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000404
caryclark@google.comf1316942011-07-26 19:54:45 +0000405It's OK if the path has:
406 Several colinear line segments composing a rectangle side.
407 Single points on the rectangle side.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000408
caryclark@google.comf1316942011-07-26 19:54:45 +0000409The direction takes advantage of the corners found since opposite sides
410must travel in opposite directions.
411
412FIXME: Allow colinear quads and cubics to be treated like lines.
413FIXME: If the API passes fill-only, return true if the filled stroke
414 is a rectangle, though the caller failed to close the path.
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000415
416 first,last,next direction state-machine:
417 0x1 is set if the segment is horizontal
418 0x2 is set if the segment is moving to the right or down
419 thus:
420 two directions are opposites iff (dirA ^ dirB) == 0x2
421 two directions are perpendicular iff (dirA ^ dirB) == 0x1
skia.committer@gmail.comf5e67c12014-01-16 07:01:48 +0000422
caryclark@google.comf1316942011-07-26 19:54:45 +0000423 */
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000424static int rect_make_dir(SkScalar dx, SkScalar dy) {
425 return ((0 != dx) << 0) | ((dx > 0 || dy > 0) << 1);
426}
caryclark@google.comf68154a2012-11-21 15:18:06 +0000427bool SkPath::isRectContour(bool allowPartial, int* currVerb, const SkPoint** ptsPtr,
428 bool* isClosed, Direction* direction) const {
caryclark@google.comf1316942011-07-26 19:54:45 +0000429 int corners = 0;
430 SkPoint first, last;
caryclark@google.com56f233a2012-11-19 13:06:06 +0000431 const SkPoint* pts = *ptsPtr;
halcanary96fcdcc2015-08-27 07:41:13 -0700432 const SkPoint* savePts = nullptr;
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000433 first.set(0, 0);
434 last.set(0, 0);
435 int firstDirection = 0;
436 int lastDirection = 0;
437 int nextDirection = 0;
438 bool closedOrMoved = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000439 bool autoClose = false;
caryclark95bc5f32015-04-08 08:34:15 -0700440 bool insertClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000441 int verbCnt = fPathRef->countVerbs();
caryclark@google.com56f233a2012-11-19 13:06:06 +0000442 while (*currVerb < verbCnt && (!allowPartial || !autoClose)) {
caryclark95bc5f32015-04-08 08:34:15 -0700443 uint8_t verb = insertClose ? (uint8_t) kClose_Verb : fPathRef->atVerb(*currVerb);
444 switch (verb) {
caryclark@google.comf1316942011-07-26 19:54:45 +0000445 case kClose_Verb:
caryclark@google.com56f233a2012-11-19 13:06:06 +0000446 savePts = pts;
447 pts = *ptsPtr;
caryclark@google.comf1316942011-07-26 19:54:45 +0000448 autoClose = true;
caryclark95bc5f32015-04-08 08:34:15 -0700449 insertClose = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000450 case kLine_Verb: {
451 SkScalar left = last.fX;
452 SkScalar top = last.fY;
453 SkScalar right = pts->fX;
454 SkScalar bottom = pts->fY;
455 ++pts;
456 if (left != right && top != bottom) {
457 return false; // diagonal
458 }
459 if (left == right && top == bottom) {
460 break; // single point on side OK
461 }
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000462 nextDirection = rect_make_dir(right - left, bottom - top);
caryclark@google.comf1316942011-07-26 19:54:45 +0000463 if (0 == corners) {
464 firstDirection = nextDirection;
465 first = last;
466 last = pts[-1];
467 corners = 1;
468 closedOrMoved = false;
469 break;
470 }
471 if (closedOrMoved) {
472 return false; // closed followed by a line
473 }
caryclark@google.combfe90372012-11-21 13:56:20 +0000474 if (autoClose && nextDirection == firstDirection) {
475 break; // colinear with first
476 }
caryclark@google.comf1316942011-07-26 19:54:45 +0000477 closedOrMoved = autoClose;
478 if (lastDirection != nextDirection) {
479 if (++corners > 4) {
480 return false; // too many direction changes
481 }
482 }
483 last = pts[-1];
484 if (lastDirection == nextDirection) {
485 break; // colinear segment
486 }
487 // Possible values for corners are 2, 3, and 4.
488 // When corners == 3, nextDirection opposes firstDirection.
489 // Otherwise, nextDirection at corner 2 opposes corner 4.
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000490 int turn = firstDirection ^ (corners - 1);
caryclark@google.comf1316942011-07-26 19:54:45 +0000491 int directionCycle = 3 == corners ? 0 : nextDirection ^ turn;
492 if ((directionCycle ^ turn) != nextDirection) {
493 return false; // direction didn't follow cycle
494 }
495 break;
496 }
497 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000498 case kConic_Verb:
caryclark@google.comf1316942011-07-26 19:54:45 +0000499 case kCubic_Verb:
500 return false; // quadratic, cubic not allowed
501 case kMove_Verb:
caryclark95bc5f32015-04-08 08:34:15 -0700502 if (allowPartial && !autoClose && firstDirection) {
503 insertClose = true;
504 *currVerb -= 1; // try move again afterwards
505 goto addMissingClose;
506 }
caryclark@google.comf1316942011-07-26 19:54:45 +0000507 last = *pts++;
508 closedOrMoved = true;
509 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000510 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000511 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000512 break;
caryclark@google.comf1316942011-07-26 19:54:45 +0000513 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000514 *currVerb += 1;
caryclark@google.comf1316942011-07-26 19:54:45 +0000515 lastDirection = nextDirection;
caryclark95bc5f32015-04-08 08:34:15 -0700516addMissingClose:
517 ;
caryclark@google.comf1316942011-07-26 19:54:45 +0000518 }
519 // Success if 4 corners and first point equals last
caryclark@google.combfe90372012-11-21 13:56:20 +0000520 bool result = 4 == corners && (first == last || autoClose);
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000521 if (!result) {
522 // check if we are just an incomplete rectangle, in which case we can
523 // return true, but not claim to be closed.
524 // e.g.
525 // 3 sided rectangle
526 // 4 sided but the last edge is not long enough to reach the start
527 //
528 SkScalar closeX = first.x() - last.x();
529 SkScalar closeY = first.y() - last.y();
530 if (closeX && closeY) {
531 return false; // we're diagonal, abort (can we ever reach this?)
532 }
533 int closeDirection = rect_make_dir(closeX, closeY);
534 // make sure the close-segment doesn't double-back on itself
535 if (3 == corners || (4 == corners && closeDirection == lastDirection)) {
536 result = true;
537 autoClose = false; // we are not closed
538 }
539 }
caryclark@google.combfe90372012-11-21 13:56:20 +0000540 if (savePts) {
541 *ptsPtr = savePts;
542 }
caryclark@google.comf68154a2012-11-21 15:18:06 +0000543 if (result && isClosed) {
544 *isClosed = autoClose;
545 }
546 if (result && direction) {
sugoi@google.com12b4e272012-12-06 20:13:11 +0000547 *direction = firstDirection == ((lastDirection + 1) & 3) ? kCCW_Direction : kCW_Direction;
caryclark@google.comf68154a2012-11-21 15:18:06 +0000548 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000549 return result;
550}
551
robertphillips4f662e62014-12-29 14:06:51 -0800552bool SkPath::isRect(SkRect* rect, bool* isClosed, Direction* direction) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000553 SkDEBUGCODE(this->validate();)
554 int currVerb = 0;
555 const SkPoint* pts = fPathRef->points();
robertphillipsfe7c4272014-12-29 11:36:39 -0800556 const SkPoint* first = pts;
robertphillips4f662e62014-12-29 14:06:51 -0800557 if (!this->isRectContour(false, &currVerb, &pts, isClosed, direction)) {
robertphillipsfe7c4272014-12-29 11:36:39 -0800558 return false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000559 }
robertphillipsfe7c4272014-12-29 11:36:39 -0800560 if (rect) {
robertphillips4f662e62014-12-29 14:06:51 -0800561 int32_t num = SkToS32(pts - first);
562 if (num) {
563 rect->set(first, num);
robertphillipsfe7c4272014-12-29 11:36:39 -0800564 } else {
565 // 'pts' isn't updated for open rects
566 *rect = this->getBounds();
567 }
568 }
569 return true;
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000570}
skia.committer@gmail.com020b25b2013-06-22 07:00:58 +0000571
caryclark95bc5f32015-04-08 08:34:15 -0700572bool SkPath::isNestedFillRects(SkRect rects[2], Direction dirs[2]) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000573 SkDEBUGCODE(this->validate();)
574 int currVerb = 0;
575 const SkPoint* pts = fPathRef->points();
576 const SkPoint* first = pts;
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000577 Direction testDirs[2];
halcanary96fcdcc2015-08-27 07:41:13 -0700578 if (!isRectContour(true, &currVerb, &pts, nullptr, &testDirs[0])) {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000579 return false;
580 }
581 const SkPoint* last = pts;
582 SkRect testRects[2];
caryclark95bc5f32015-04-08 08:34:15 -0700583 bool isClosed;
584 if (isRectContour(false, &currVerb, &pts, &isClosed, &testDirs[1])) {
scroggo@google.com614f9e32013-05-09 18:05:32 +0000585 testRects[0].set(first, SkToS32(last - first));
caryclark95bc5f32015-04-08 08:34:15 -0700586 if (!isClosed) {
587 pts = fPathRef->points() + fPathRef->countPoints();
588 }
scroggo@google.com614f9e32013-05-09 18:05:32 +0000589 testRects[1].set(last, SkToS32(pts - last));
caryclark@google.com56f233a2012-11-19 13:06:06 +0000590 if (testRects[0].contains(testRects[1])) {
591 if (rects) {
592 rects[0] = testRects[0];
593 rects[1] = testRects[1];
594 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000595 if (dirs) {
596 dirs[0] = testDirs[0];
597 dirs[1] = testDirs[1];
598 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000599 return true;
600 }
601 if (testRects[1].contains(testRects[0])) {
602 if (rects) {
603 rects[0] = testRects[1];
604 rects[1] = testRects[0];
605 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000606 if (dirs) {
607 dirs[0] = testDirs[1];
608 dirs[1] = testDirs[0];
609 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000610 return true;
611 }
612 }
613 return false;
614}
615
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000616int SkPath::countPoints() const {
617 return fPathRef->countPoints();
618}
619
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000620int SkPath::getPoints(SkPoint dst[], int max) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000621 SkDEBUGCODE(this->validate();)
622
623 SkASSERT(max >= 0);
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000624 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000625 int count = SkMin32(max, fPathRef->countPoints());
mtklein8bf5d172015-12-09 13:15:07 -0800626 sk_careful_memcpy(dst, fPathRef->points(), count * sizeof(SkPoint));
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000627 return fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000628}
629
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000630SkPoint SkPath::getPoint(int index) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000631 if ((unsigned)index < (unsigned)fPathRef->countPoints()) {
632 return fPathRef->atPoint(index);
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000633 }
634 return SkPoint::Make(0, 0);
635}
636
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000637int SkPath::countVerbs() const {
638 return fPathRef->countVerbs();
639}
640
641static inline void copy_verbs_reverse(uint8_t* inorderDst,
642 const uint8_t* reversedSrc,
643 int count) {
644 for (int i = 0; i < count; ++i) {
645 inorderDst[i] = reversedSrc[~i];
646 }
647}
648
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000649int SkPath::getVerbs(uint8_t dst[], int max) const {
650 SkDEBUGCODE(this->validate();)
651
652 SkASSERT(max >= 0);
653 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000654 int count = SkMin32(max, fPathRef->countVerbs());
655 copy_verbs_reverse(dst, fPathRef->verbs(), count);
656 return fPathRef->countVerbs();
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000657}
658
reed@google.com294dd7b2011-10-11 11:58:32 +0000659bool SkPath::getLastPt(SkPoint* lastPt) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000660 SkDEBUGCODE(this->validate();)
661
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000662 int count = fPathRef->countPoints();
reed@google.com294dd7b2011-10-11 11:58:32 +0000663 if (count > 0) {
664 if (lastPt) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000665 *lastPt = fPathRef->atPoint(count - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000666 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000667 return true;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000668 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000669 if (lastPt) {
670 lastPt->set(0, 0);
671 }
672 return false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000673}
674
caryclarkaec25102015-04-29 08:28:30 -0700675void SkPath::setPt(int index, SkScalar x, SkScalar y) {
676 SkDEBUGCODE(this->validate();)
677
678 int count = fPathRef->countPoints();
679 if (count <= index) {
680 return;
681 } else {
682 SkPathRef::Editor ed(&fPathRef);
683 ed.atPoint(index)->set(x, y);
684 }
685}
686
reed@android.com8a1c16f2008-12-17 15:59:43 +0000687void SkPath::setLastPt(SkScalar x, SkScalar y) {
688 SkDEBUGCODE(this->validate();)
689
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000690 int count = fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000691 if (count == 0) {
692 this->moveTo(x, y);
693 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000694 SkPathRef::Editor ed(&fPathRef);
695 ed.atPoint(count-1)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000696 }
697}
698
reed@google.com04863fa2011-05-15 04:08:24 +0000699void SkPath::setConvexity(Convexity c) {
700 if (fConvexity != c) {
701 fConvexity = c;
reed@google.com04863fa2011-05-15 04:08:24 +0000702 }
703}
704
reed@android.com8a1c16f2008-12-17 15:59:43 +0000705//////////////////////////////////////////////////////////////////////////////
706// Construction methods
707
reed026beb52015-06-10 14:23:15 -0700708#define DIRTY_AFTER_EDIT \
709 do { \
710 fConvexity = kUnknown_Convexity; \
711 fFirstDirection = SkPathPriv::kUnknown_FirstDirection; \
reed@google.comb54455e2011-05-16 14:16:04 +0000712 } while (0)
713
reed@android.com8a1c16f2008-12-17 15:59:43 +0000714void SkPath::incReserve(U16CPU inc) {
715 SkDEBUGCODE(this->validate();)
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000716 SkPathRef::Editor(&fPathRef, inc, inc);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000717 SkDEBUGCODE(this->validate();)
718}
719
720void SkPath::moveTo(SkScalar x, SkScalar y) {
721 SkDEBUGCODE(this->validate();)
722
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000723 SkPathRef::Editor ed(&fPathRef);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000724
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000725 // remember our index
726 fLastMoveToIndex = fPathRef->countPoints();
727
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000728 ed.growForVerb(kMove_Verb)->set(x, y);
bsalomonb17c1292014-08-28 14:04:55 -0700729
730 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000731}
732
733void SkPath::rMoveTo(SkScalar x, SkScalar y) {
734 SkPoint pt;
735 this->getLastPt(&pt);
736 this->moveTo(pt.fX + x, pt.fY + y);
737}
738
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000739void SkPath::injectMoveToIfNeeded() {
740 if (fLastMoveToIndex < 0) {
741 SkScalar x, y;
742 if (fPathRef->countVerbs() == 0) {
743 x = y = 0;
744 } else {
745 const SkPoint& pt = fPathRef->atPoint(~fLastMoveToIndex);
746 x = pt.fX;
747 y = pt.fY;
748 }
749 this->moveTo(x, y);
750 }
751}
752
reed@android.com8a1c16f2008-12-17 15:59:43 +0000753void SkPath::lineTo(SkScalar x, SkScalar y) {
754 SkDEBUGCODE(this->validate();)
755
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000756 this->injectMoveToIfNeeded();
757
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000758 SkPathRef::Editor ed(&fPathRef);
759 ed.growForVerb(kLine_Verb)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000760
reed@google.comb54455e2011-05-16 14:16:04 +0000761 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000762}
763
764void SkPath::rLineTo(SkScalar x, SkScalar y) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000765 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000766 SkPoint pt;
767 this->getLastPt(&pt);
768 this->lineTo(pt.fX + x, pt.fY + y);
769}
770
771void SkPath::quadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
772 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000773
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000774 this->injectMoveToIfNeeded();
775
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000776 SkPathRef::Editor ed(&fPathRef);
777 SkPoint* pts = ed.growForVerb(kQuad_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000778 pts[0].set(x1, y1);
779 pts[1].set(x2, y2);
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000780
reed@google.comb54455e2011-05-16 14:16:04 +0000781 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000782}
783
784void SkPath::rQuadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000785 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000786 SkPoint pt;
787 this->getLastPt(&pt);
788 this->quadTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2);
789}
790
reed@google.com277c3f82013-05-31 15:17:50 +0000791void SkPath::conicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
792 SkScalar w) {
793 // check for <= 0 or NaN with this test
794 if (!(w > 0)) {
795 this->lineTo(x2, y2);
796 } else if (!SkScalarIsFinite(w)) {
797 this->lineTo(x1, y1);
798 this->lineTo(x2, y2);
799 } else if (SK_Scalar1 == w) {
800 this->quadTo(x1, y1, x2, y2);
801 } else {
802 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000803
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000804 this->injectMoveToIfNeeded();
805
reed@google.com277c3f82013-05-31 15:17:50 +0000806 SkPathRef::Editor ed(&fPathRef);
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000807 SkPoint* pts = ed.growForVerb(kConic_Verb, w);
reed@google.com277c3f82013-05-31 15:17:50 +0000808 pts[0].set(x1, y1);
809 pts[1].set(x2, y2);
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000810
reed@google.com277c3f82013-05-31 15:17:50 +0000811 DIRTY_AFTER_EDIT;
812 }
813}
814
815void SkPath::rConicTo(SkScalar dx1, SkScalar dy1, SkScalar dx2, SkScalar dy2,
816 SkScalar w) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000817 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@google.com277c3f82013-05-31 15:17:50 +0000818 SkPoint pt;
819 this->getLastPt(&pt);
820 this->conicTo(pt.fX + dx1, pt.fY + dy1, pt.fX + dx2, pt.fY + dy2, w);
821}
822
reed@android.com8a1c16f2008-12-17 15:59:43 +0000823void SkPath::cubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
824 SkScalar x3, SkScalar y3) {
825 SkDEBUGCODE(this->validate();)
826
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000827 this->injectMoveToIfNeeded();
828
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000829 SkPathRef::Editor ed(&fPathRef);
830 SkPoint* pts = ed.growForVerb(kCubic_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000831 pts[0].set(x1, y1);
832 pts[1].set(x2, y2);
833 pts[2].set(x3, y3);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000834
reed@google.comb54455e2011-05-16 14:16:04 +0000835 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000836}
837
838void SkPath::rCubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
839 SkScalar x3, SkScalar y3) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000840 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000841 SkPoint pt;
842 this->getLastPt(&pt);
843 this->cubicTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2,
844 pt.fX + x3, pt.fY + y3);
845}
846
847void SkPath::close() {
848 SkDEBUGCODE(this->validate();)
849
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000850 int count = fPathRef->countVerbs();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000851 if (count > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000852 switch (fPathRef->atVerb(count - 1)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000853 case kLine_Verb:
854 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000855 case kConic_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +0000856 case kCubic_Verb:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000857 case kMove_Verb: {
858 SkPathRef::Editor ed(&fPathRef);
859 ed.growForVerb(kClose_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000860 break;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000861 }
reed@google.com277c3f82013-05-31 15:17:50 +0000862 case kClose_Verb:
reed@google.comfa2f2a42013-05-30 15:29:48 +0000863 // don't add a close if it's the first verb or a repeat
reed@google.com7950a9e2013-05-30 14:57:55 +0000864 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000865 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000866 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000867 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000868 }
869 }
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000870
871 // signal that we need a moveTo to follow us (unless we're done)
872#if 0
873 if (fLastMoveToIndex >= 0) {
874 fLastMoveToIndex = ~fLastMoveToIndex;
875 }
876#else
877 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
878#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000879}
880
881///////////////////////////////////////////////////////////////////////////////
reed@google.comabf15c12011-01-18 20:35:51 +0000882
fmalitac08d53e2015-11-17 09:53:29 -0800883namespace {
884
885template <unsigned N>
886class PointIterator {
887public:
888 PointIterator(SkPath::Direction dir, unsigned startIndex)
889 : fCurrent(startIndex % N)
890 , fAdvance(dir == SkPath::kCW_Direction ? 1 : N - 1) { }
891
892 const SkPoint& current() const {
893 SkASSERT(fCurrent < N);
894 return fPts[fCurrent];
895 }
896
897 const SkPoint& next() {
898 fCurrent = (fCurrent + fAdvance) % N;
899 return this->current();
900 }
901
902protected:
903 SkPoint fPts[N];
904
905private:
906 unsigned fCurrent;
907 unsigned fAdvance;
908};
909
910class RectPointIterator : public PointIterator<4> {
911public:
912 RectPointIterator(const SkRect& rect, SkPath::Direction dir, unsigned startIndex)
913 : PointIterator(dir, startIndex) {
914
915 fPts[0] = SkPoint::Make(rect.fLeft, rect.fTop);
916 fPts[1] = SkPoint::Make(rect.fRight, rect.fTop);
917 fPts[2] = SkPoint::Make(rect.fRight, rect.fBottom);
918 fPts[3] = SkPoint::Make(rect.fLeft, rect.fBottom);
919 }
920};
921
922class OvalPointIterator : public PointIterator<4> {
923public:
924 OvalPointIterator(const SkRect& oval, SkPath::Direction dir, unsigned startIndex)
925 : PointIterator(dir, startIndex) {
926
927 const SkScalar cx = oval.centerX();
928 const SkScalar cy = oval.centerY();
929
930 fPts[0] = SkPoint::Make(cx, oval.fTop);
931 fPts[1] = SkPoint::Make(oval.fRight, cy);
932 fPts[2] = SkPoint::Make(cx, oval.fBottom);
933 fPts[3] = SkPoint::Make(oval.fLeft, cy);
934 }
935};
936
937class RRectPointIterator : public PointIterator<8> {
938public:
939 RRectPointIterator(const SkRRect& rrect, SkPath::Direction dir, unsigned startIndex)
940 : PointIterator(dir, startIndex) {
941
942 const SkRect& bounds = rrect.getBounds();
943 const SkScalar L = bounds.fLeft;
944 const SkScalar T = bounds.fTop;
945 const SkScalar R = bounds.fRight;
946 const SkScalar B = bounds.fBottom;
947
948 fPts[0] = SkPoint::Make(L + rrect.radii(SkRRect::kUpperLeft_Corner).fX, T);
949 fPts[1] = SkPoint::Make(R - rrect.radii(SkRRect::kUpperRight_Corner).fX, T);
950 fPts[2] = SkPoint::Make(R, T + rrect.radii(SkRRect::kUpperRight_Corner).fY);
951 fPts[3] = SkPoint::Make(R, B - rrect.radii(SkRRect::kLowerRight_Corner).fY);
952 fPts[4] = SkPoint::Make(R - rrect.radii(SkRRect::kLowerRight_Corner).fX, B);
953 fPts[5] = SkPoint::Make(L + rrect.radii(SkRRect::kLowerLeft_Corner).fX, B);
954 fPts[6] = SkPoint::Make(L, B - rrect.radii(SkRRect::kLowerLeft_Corner).fY);
955 fPts[7] = SkPoint::Make(L, T + rrect.radii(SkRRect::kUpperLeft_Corner).fY);
956 }
957};
958
959} // anonymous namespace
960
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000961static void assert_known_direction(int dir) {
962 SkASSERT(SkPath::kCW_Direction == dir || SkPath::kCCW_Direction == dir);
963}
964
reed@android.com8a1c16f2008-12-17 15:59:43 +0000965void SkPath::addRect(const SkRect& rect, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -0800966 this->addRect(rect, dir, 0);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000967}
968
969void SkPath::addRect(SkScalar left, SkScalar top, SkScalar right,
970 SkScalar bottom, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -0800971 this->addRect(SkRect::MakeLTRB(left, top, right, bottom), dir, 0);
972}
973
974void SkPath::addRect(const SkRect &rect, Direction dir, unsigned startIndex) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000975 assert_known_direction(dir);
reed026beb52015-06-10 14:23:15 -0700976 fFirstDirection = this->hasOnlyMoveTos() ?
fmalitac08d53e2015-11-17 09:53:29 -0800977 (SkPathPriv::FirstDirection)dir : SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000978 SkAutoDisableDirectionCheck addc(this);
fmalitac08d53e2015-11-17 09:53:29 -0800979 SkAutoPathBoundsUpdate apbu(this, rect);
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000980
fmalitac08d53e2015-11-17 09:53:29 -0800981 SkDEBUGCODE(int initialVerbCount = this->countVerbs());
reed@android.com8a1c16f2008-12-17 15:59:43 +0000982
fmalitac08d53e2015-11-17 09:53:29 -0800983 const int kVerbs = 5; // moveTo + 3x lineTo + close
984 this->incReserve(kVerbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000985
fmalitac08d53e2015-11-17 09:53:29 -0800986 RectPointIterator iter(rect, dir, startIndex);
987
988 this->moveTo(iter.current());
989 this->lineTo(iter.next());
990 this->lineTo(iter.next());
991 this->lineTo(iter.next());
reed@android.com8a1c16f2008-12-17 15:59:43 +0000992 this->close();
fmalitac08d53e2015-11-17 09:53:29 -0800993
994 SkASSERT(this->countVerbs() == initialVerbCount + kVerbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000995}
996
reed@google.com744faba2012-05-29 19:54:52 +0000997void SkPath::addPoly(const SkPoint pts[], int count, bool close) {
998 SkDEBUGCODE(this->validate();)
999 if (count <= 0) {
1000 return;
1001 }
1002
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +00001003 fLastMoveToIndex = fPathRef->countPoints();
1004
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001005 // +close makes room for the extra kClose_Verb
1006 SkPathRef::Editor ed(&fPathRef, count+close, count);
1007
1008 ed.growForVerb(kMove_Verb)->set(pts[0].fX, pts[0].fY);
reed@google.com744faba2012-05-29 19:54:52 +00001009 if (count > 1) {
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001010 SkPoint* p = ed.growForRepeatedVerb(kLine_Verb, count - 1);
1011 memcpy(p, &pts[1], (count-1) * sizeof(SkPoint));
reed@google.com744faba2012-05-29 19:54:52 +00001012 }
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001013
reed@google.com744faba2012-05-29 19:54:52 +00001014 if (close) {
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001015 ed.growForVerb(kClose_Verb);
caryclark63c684a2015-02-25 09:04:04 -08001016 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
reed@google.com744faba2012-05-29 19:54:52 +00001017 }
1018
reed@google.com744faba2012-05-29 19:54:52 +00001019 DIRTY_AFTER_EDIT;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001020 SkDEBUGCODE(this->validate();)
reed@google.com744faba2012-05-29 19:54:52 +00001021}
1022
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001023#include "SkGeometry.h"
1024
reedf90ea012015-01-29 12:03:58 -08001025static bool arc_is_lone_point(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
1026 SkPoint* pt) {
1027 if (0 == sweepAngle && (0 == startAngle || SkIntToScalar(360) == startAngle)) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001028 // Chrome uses this path to move into and out of ovals. If not
1029 // treated as a special case the moves can distort the oval's
1030 // bounding box (and break the circle special case).
reedf90ea012015-01-29 12:03:58 -08001031 pt->set(oval.fRight, oval.centerY());
1032 return true;
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001033 } else if (0 == oval.width() && 0 == oval.height()) {
1034 // Chrome will sometimes create 0 radius round rects. Having degenerate
1035 // quad segments in the path prevents the path from being recognized as
1036 // a rect.
1037 // TODO: optimizing the case where only one of width or height is zero
1038 // should also be considered. This case, however, doesn't seem to be
1039 // as common as the single point case.
reedf90ea012015-01-29 12:03:58 -08001040 pt->set(oval.fRight, oval.fTop);
1041 return true;
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001042 }
reedf90ea012015-01-29 12:03:58 -08001043 return false;
1044}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001045
reedd5d27d92015-02-09 13:54:43 -08001046// Return the unit vectors pointing at the start/stop points for the given start/sweep angles
1047//
1048static void angles_to_unit_vectors(SkScalar startAngle, SkScalar sweepAngle,
1049 SkVector* startV, SkVector* stopV, SkRotationDirection* dir) {
1050 startV->fY = SkScalarSinCos(SkDegreesToRadians(startAngle), &startV->fX);
1051 stopV->fY = SkScalarSinCos(SkDegreesToRadians(startAngle + sweepAngle), &stopV->fX);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001052
1053 /* If the sweep angle is nearly (but less than) 360, then due to precision
reedd5d27d92015-02-09 13:54:43 -08001054 loss in radians-conversion and/or sin/cos, we may end up with coincident
1055 vectors, which will fool SkBuildQuadArc into doing nothing (bad) instead
1056 of drawing a nearly complete circle (good).
1057 e.g. canvas.drawArc(0, 359.99, ...)
1058 -vs- canvas.drawArc(0, 359.9, ...)
1059 We try to detect this edge case, and tweak the stop vector
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001060 */
reedd5d27d92015-02-09 13:54:43 -08001061 if (*startV == *stopV) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001062 SkScalar sw = SkScalarAbs(sweepAngle);
1063 if (sw < SkIntToScalar(360) && sw > SkIntToScalar(359)) {
1064 SkScalar stopRad = SkDegreesToRadians(startAngle + sweepAngle);
1065 // make a guess at a tiny angle (in radians) to tweak by
1066 SkScalar deltaRad = SkScalarCopySign(SK_Scalar1/512, sweepAngle);
1067 // not sure how much will be enough, so we use a loop
1068 do {
1069 stopRad -= deltaRad;
reedd5d27d92015-02-09 13:54:43 -08001070 stopV->fY = SkScalarSinCos(stopRad, &stopV->fX);
1071 } while (*startV == *stopV);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001072 }
1073 }
reedd5d27d92015-02-09 13:54:43 -08001074 *dir = sweepAngle > 0 ? kCW_SkRotationDirection : kCCW_SkRotationDirection;
1075}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001076
reed9e779d42015-02-17 11:43:14 -08001077/**
1078 * If this returns 0, then the caller should just line-to the singlePt, else it should
1079 * ignore singlePt and append the specified number of conics.
1080 */
reedd5d27d92015-02-09 13:54:43 -08001081static int build_arc_conics(const SkRect& oval, const SkVector& start, const SkVector& stop,
reed9e779d42015-02-17 11:43:14 -08001082 SkRotationDirection dir, SkConic conics[SkConic::kMaxConicsForArc],
1083 SkPoint* singlePt) {
reedd5d27d92015-02-09 13:54:43 -08001084 SkMatrix matrix;
1085
1086 matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height()));
1087 matrix.postTranslate(oval.centerX(), oval.centerY());
1088
reed9e779d42015-02-17 11:43:14 -08001089 int count = SkConic::BuildUnitArc(start, stop, dir, &matrix, conics);
1090 if (0 == count) {
xidachen95e34a32016-10-19 10:24:28 -07001091 matrix.mapXY(stop.x(), stop.y(), singlePt);
reed9e779d42015-02-17 11:43:14 -08001092 }
1093 return count;
reedd5d27d92015-02-09 13:54:43 -08001094}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001095
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001096void SkPath::addRoundRect(const SkRect& rect, const SkScalar radii[],
reed@android.com8a1c16f2008-12-17 15:59:43 +00001097 Direction dir) {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001098 SkRRect rrect;
1099 rrect.setRectRadii(rect, (const SkVector*) radii);
1100 this->addRRect(rrect, dir);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001101}
1102
reed@google.com4ed0fb72012-12-12 20:48:18 +00001103void SkPath::addRRect(const SkRRect& rrect, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -08001104 // legacy start indices: 6 (CW) and 7(CCW)
1105 this->addRRect(rrect, dir, dir == kCW_Direction ? 6 : 7);
1106}
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001107
fmalitac08d53e2015-11-17 09:53:29 -08001108void SkPath::addRRect(const SkRRect &rrect, Direction dir, unsigned startIndex) {
1109 assert_known_direction(dir);
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001110
fmalitac08d53e2015-11-17 09:53:29 -08001111 if (rrect.isEmpty()) {
1112 return;
reed1b28a3a2014-12-17 14:42:09 -08001113 }
fmalitac08d53e2015-11-17 09:53:29 -08001114
caryclarkda707bf2015-11-19 14:47:43 -08001115 bool isRRect = hasOnlyMoveTos();
fmalitac08d53e2015-11-17 09:53:29 -08001116 const SkRect& bounds = rrect.getBounds();
1117
1118 if (rrect.isRect()) {
1119 // degenerate(rect) => radii points are collapsing
1120 this->addRect(bounds, dir, (startIndex + 1) / 2);
1121 } else if (rrect.isOval()) {
1122 // degenerate(oval) => line points are collapsing
1123 this->addOval(bounds, dir, startIndex / 2);
1124 } else {
1125 fFirstDirection = this->hasOnlyMoveTos() ?
1126 (SkPathPriv::FirstDirection)dir : SkPathPriv::kUnknown_FirstDirection;
1127
1128 SkAutoPathBoundsUpdate apbu(this, bounds);
1129 SkAutoDisableDirectionCheck addc(this);
1130
1131 // we start with a conic on odd indices when moving CW vs. even indices when moving CCW
1132 const bool startsWithConic = ((startIndex & 1) == (dir == kCW_Direction));
1133 const SkScalar weight = SK_ScalarRoot2Over2;
1134
1135 SkDEBUGCODE(int initialVerbCount = this->countVerbs());
1136 const int kVerbs = startsWithConic
1137 ? 9 // moveTo + 4x conicTo + 3x lineTo + close
1138 : 10; // moveTo + 4x lineTo + 4x conicTo + close
1139 this->incReserve(kVerbs);
1140
1141 RRectPointIterator rrectIter(rrect, dir, startIndex);
1142 // Corner iterator indices follow the collapsed radii model,
1143 // adjusted such that the start pt is "behind" the radii start pt.
1144 const unsigned rectStartIndex = startIndex / 2 + (dir == kCW_Direction ? 0 : 1);
1145 RectPointIterator rectIter(bounds, dir, rectStartIndex);
1146
1147 this->moveTo(rrectIter.current());
1148 if (startsWithConic) {
1149 for (unsigned i = 0; i < 3; ++i) {
1150 this->conicTo(rectIter.next(), rrectIter.next(), weight);
1151 this->lineTo(rrectIter.next());
1152 }
1153 this->conicTo(rectIter.next(), rrectIter.next(), weight);
1154 // final lineTo handled by close().
1155 } else {
1156 for (unsigned i = 0; i < 4; ++i) {
1157 this->lineTo(rrectIter.next());
1158 this->conicTo(rectIter.next(), rrectIter.next(), weight);
1159 }
1160 }
1161 this->close();
1162
caryclarkda707bf2015-11-19 14:47:43 -08001163 SkPathRef::Editor ed(&fPathRef);
bsalomon78d58d12016-05-27 09:17:04 -07001164 ed.setIsRRect(isRRect, dir, startIndex % 8);
caryclarkda707bf2015-11-19 14:47:43 -08001165
fmalitac08d53e2015-11-17 09:53:29 -08001166 SkASSERT(this->countVerbs() == initialVerbCount + kVerbs);
1167 }
1168
1169 SkDEBUGCODE(fPathRef->validate();)
reed@google.com4ed0fb72012-12-12 20:48:18 +00001170}
1171
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001172bool SkPath::hasOnlyMoveTos() const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001173 int count = fPathRef->countVerbs();
1174 const uint8_t* verbs = const_cast<const SkPathRef*>(fPathRef.get())->verbsMemBegin();
1175 for (int i = 0; i < count; ++i) {
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001176 if (*verbs == kLine_Verb ||
1177 *verbs == kQuad_Verb ||
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001178 *verbs == kConic_Verb ||
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001179 *verbs == kCubic_Verb) {
1180 return false;
1181 }
1182 ++verbs;
1183 }
1184 return true;
1185}
1186
caryclarkd49a86a2016-02-22 12:44:54 -08001187bool SkPath::isZeroLength() const {
1188 int count = fPathRef->countPoints();
1189 if (count < 2) {
1190 return true;
1191 }
1192 const SkPoint* pts = fPathRef.get()->points();
1193 const SkPoint& first = *pts;
1194 for (int index = 1; index < count; ++index) {
1195 if (first != pts[index]) {
1196 return false;
1197 }
1198 }
1199 return true;
1200}
1201
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001202void SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
1203 Direction dir) {
1204 assert_known_direction(dir);
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001205
humper@google.com75e3ca12013-04-08 21:44:11 +00001206 if (rx < 0 || ry < 0) {
humper@google.com75e3ca12013-04-08 21:44:11 +00001207 return;
1208 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001209
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001210 SkRRect rrect;
1211 rrect.setRectXY(rect, rx, ry);
1212 this->addRRect(rrect, dir);
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001213}
1214
reed@android.com8a1c16f2008-12-17 15:59:43 +00001215void SkPath::addOval(const SkRect& oval, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -08001216 // legacy start index: 1
1217 this->addOval(oval, dir, 1);
1218}
1219
1220void SkPath::addOval(const SkRect &oval, Direction dir, unsigned startPointIndex) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +00001221 assert_known_direction(dir);
1222
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001223 /* If addOval() is called after previous moveTo(),
1224 this path is still marked as an oval. This is used to
1225 fit into WebKit's calling sequences.
1226 We can't simply check isEmpty() in this case, as additional
1227 moveTo() would mark the path non empty.
1228 */
robertphillips@google.com466310d2013-12-03 16:43:54 +00001229 bool isOval = hasOnlyMoveTos();
1230 if (isOval) {
reed026beb52015-06-10 14:23:15 -07001231 fFirstDirection = (SkPathPriv::FirstDirection)dir;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001232 } else {
reed026beb52015-06-10 14:23:15 -07001233 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001234 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001235
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001236 SkAutoDisableDirectionCheck addc(this);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001237 SkAutoPathBoundsUpdate apbu(this, oval);
1238
fmalitac08d53e2015-11-17 09:53:29 -08001239 SkDEBUGCODE(int initialVerbCount = this->countVerbs());
1240 const int kVerbs = 6; // moveTo + 4x conicTo + close
1241 this->incReserve(kVerbs);
1242
1243 OvalPointIterator ovalIter(oval, dir, startPointIndex);
1244 // The corner iterator pts are tracking "behind" the oval/radii pts.
1245 RectPointIterator rectIter(oval, dir, startPointIndex + (dir == kCW_Direction ? 0 : 1));
reed220f9262014-12-17 08:21:04 -08001246 const SkScalar weight = SK_ScalarRoot2Over2;
1247
fmalitac08d53e2015-11-17 09:53:29 -08001248 this->moveTo(ovalIter.current());
1249 for (unsigned i = 0; i < 4; ++i) {
1250 this->conicTo(rectIter.next(), ovalIter.next(), weight);
reed220f9262014-12-17 08:21:04 -08001251 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001252 this->close();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001253
fmalitac08d53e2015-11-17 09:53:29 -08001254 SkASSERT(this->countVerbs() == initialVerbCount + kVerbs);
1255
robertphillips@google.com466310d2013-12-03 16:43:54 +00001256 SkPathRef::Editor ed(&fPathRef);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001257
bsalomon78d58d12016-05-27 09:17:04 -07001258 ed.setIsOval(isOval, kCCW_Direction == dir, startPointIndex % 4);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001259}
1260
reed@android.com8a1c16f2008-12-17 15:59:43 +00001261void SkPath::addCircle(SkScalar x, SkScalar y, SkScalar r, Direction dir) {
1262 if (r > 0) {
fmalitac08d53e2015-11-17 09:53:29 -08001263 this->addOval(SkRect::MakeLTRB(x - r, y - r, x + r, y + r), dir);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001264 }
1265}
1266
reed@android.com8a1c16f2008-12-17 15:59:43 +00001267void SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
1268 bool forceMoveTo) {
1269 if (oval.width() < 0 || oval.height() < 0) {
1270 return;
1271 }
1272
reedf90ea012015-01-29 12:03:58 -08001273 if (fPathRef->countVerbs() == 0) {
1274 forceMoveTo = true;
1275 }
1276
1277 SkPoint lonePt;
1278 if (arc_is_lone_point(oval, startAngle, sweepAngle, &lonePt)) {
1279 forceMoveTo ? this->moveTo(lonePt) : this->lineTo(lonePt);
1280 return;
1281 }
1282
reedd5d27d92015-02-09 13:54:43 -08001283 SkVector startV, stopV;
1284 SkRotationDirection dir;
1285 angles_to_unit_vectors(startAngle, sweepAngle, &startV, &stopV, &dir);
1286
reed9e779d42015-02-17 11:43:14 -08001287 SkPoint singlePt;
xidachen6069dda2016-10-06 05:42:23 -07001288
1289 // At this point, we know that the arc is not a lone point, but startV == stopV
1290 // indicates that the sweepAngle is too small such that angles_to_unit_vectors
1291 // cannot handle it.
1292 if (startV == stopV) {
1293 SkScalar endAngle = SkDegreesToRadians(startAngle + sweepAngle);
1294 SkScalar radiusX = oval.width() / 2;
1295 SkScalar radiusY = oval.height() / 2;
1296 // We cannot use SkScalarSinCos function in the next line because
1297 // SkScalarSinCos has a threshold *SkScalarNearlyZero*. When sin(startAngle)
1298 // is 0 and sweepAngle is very small and radius is huge, the expected
Mike Kleine54c75f2016-10-13 14:18:09 -04001299 // behavior here is to draw a line. But calling SkScalarSinCos will
xidachen6069dda2016-10-06 05:42:23 -07001300 // make sin(endAngle) to be 0 which will then draw a dot.
1301 singlePt.set(oval.centerX() + radiusX * sk_float_cos(endAngle),
1302 oval.centerY() + radiusY * sk_float_sin(endAngle));
1303 forceMoveTo ? this->moveTo(singlePt) : this->lineTo(singlePt);
1304 return;
1305 }
1306
reedd5d27d92015-02-09 13:54:43 -08001307 SkConic conics[SkConic::kMaxConicsForArc];
reed9e779d42015-02-17 11:43:14 -08001308 int count = build_arc_conics(oval, startV, stopV, dir, conics, &singlePt);
reedd5d27d92015-02-09 13:54:43 -08001309 if (count) {
1310 this->incReserve(count * 2 + 1);
1311 const SkPoint& pt = conics[0].fPts[0];
1312 forceMoveTo ? this->moveTo(pt) : this->lineTo(pt);
1313 for (int i = 0; i < count; ++i) {
1314 this->conicTo(conics[i].fPts[1], conics[i].fPts[2], conics[i].fW);
1315 }
reed9e779d42015-02-17 11:43:14 -08001316 } else {
1317 forceMoveTo ? this->moveTo(singlePt) : this->lineTo(singlePt);
reedd5d27d92015-02-09 13:54:43 -08001318 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001319}
1320
caryclark55d49052016-01-23 05:07:04 -08001321// This converts the SVG arc to conics.
1322// Partly adapted from Niko's code in kdelibs/kdecore/svgicons.
1323// Then transcribed from webkit/chrome's SVGPathNormalizer::decomposeArcToCubic()
1324// See also SVG implementation notes:
1325// http://www.w3.org/TR/SVG/implnote.html#ArcConversionEndpointToCenter
1326// Note that arcSweep bool value is flipped from the original implementation.
1327void SkPath::arcTo(SkScalar rx, SkScalar ry, SkScalar angle, SkPath::ArcSize arcLarge,
1328 SkPath::Direction arcSweep, SkScalar x, SkScalar y) {
caryclarkf1d41512016-02-09 10:30:22 -08001329 this->injectMoveToIfNeeded();
caryclark55d49052016-01-23 05:07:04 -08001330 SkPoint srcPts[2];
1331 this->getLastPt(&srcPts[0]);
1332 // If rx = 0 or ry = 0 then this arc is treated as a straight line segment (a "lineto")
1333 // joining the endpoints.
1334 // http://www.w3.org/TR/SVG/implnote.html#ArcOutOfRangeParameters
1335 if (!rx || !ry) {
caryclarkfc752532016-01-25 05:39:04 -08001336 this->lineTo(x, y);
caryclark55d49052016-01-23 05:07:04 -08001337 return;
1338 }
1339 // If the current point and target point for the arc are identical, it should be treated as a
1340 // zero length path. This ensures continuity in animations.
1341 srcPts[1].set(x, y);
1342 if (srcPts[0] == srcPts[1]) {
caryclarkfc752532016-01-25 05:39:04 -08001343 this->lineTo(x, y);
caryclark55d49052016-01-23 05:07:04 -08001344 return;
1345 }
1346 rx = SkScalarAbs(rx);
1347 ry = SkScalarAbs(ry);
1348 SkVector midPointDistance = srcPts[0] - srcPts[1];
1349 midPointDistance *= 0.5f;
1350
1351 SkMatrix pointTransform;
1352 pointTransform.setRotate(-angle);
1353
1354 SkPoint transformedMidPoint;
1355 pointTransform.mapPoints(&transformedMidPoint, &midPointDistance, 1);
1356 SkScalar squareRx = rx * rx;
1357 SkScalar squareRy = ry * ry;
1358 SkScalar squareX = transformedMidPoint.fX * transformedMidPoint.fX;
1359 SkScalar squareY = transformedMidPoint.fY * transformedMidPoint.fY;
1360
1361 // Check if the radii are big enough to draw the arc, scale radii if not.
1362 // http://www.w3.org/TR/SVG/implnote.html#ArcCorrectionOutOfRangeRadii
1363 SkScalar radiiScale = squareX / squareRx + squareY / squareRy;
1364 if (radiiScale > 1) {
1365 radiiScale = SkScalarSqrt(radiiScale);
1366 rx *= radiiScale;
1367 ry *= radiiScale;
1368 }
1369
1370 pointTransform.setScale(1 / rx, 1 / ry);
1371 pointTransform.preRotate(-angle);
1372
1373 SkPoint unitPts[2];
1374 pointTransform.mapPoints(unitPts, srcPts, (int) SK_ARRAY_COUNT(unitPts));
1375 SkVector delta = unitPts[1] - unitPts[0];
1376
1377 SkScalar d = delta.fX * delta.fX + delta.fY * delta.fY;
1378 SkScalar scaleFactorSquared = SkTMax(1 / d - 0.25f, 0.f);
1379
1380 SkScalar scaleFactor = SkScalarSqrt(scaleFactorSquared);
1381 if (SkToBool(arcSweep) != SkToBool(arcLarge)) { // flipped from the original implementation
1382 scaleFactor = -scaleFactor;
1383 }
1384 delta.scale(scaleFactor);
1385 SkPoint centerPoint = unitPts[0] + unitPts[1];
1386 centerPoint *= 0.5f;
1387 centerPoint.offset(-delta.fY, delta.fX);
1388 unitPts[0] -= centerPoint;
1389 unitPts[1] -= centerPoint;
1390 SkScalar theta1 = SkScalarATan2(unitPts[0].fY, unitPts[0].fX);
1391 SkScalar theta2 = SkScalarATan2(unitPts[1].fY, unitPts[1].fX);
1392 SkScalar thetaArc = theta2 - theta1;
1393 if (thetaArc < 0 && !arcSweep) { // arcSweep flipped from the original implementation
1394 thetaArc += SK_ScalarPI * 2;
1395 } else if (thetaArc > 0 && arcSweep) { // arcSweep flipped from the original implementation
1396 thetaArc -= SK_ScalarPI * 2;
1397 }
1398 pointTransform.setRotate(angle);
1399 pointTransform.preScale(rx, ry);
1400
1401 int segments = SkScalarCeilToInt(SkScalarAbs(thetaArc / (SK_ScalarPI / 2)));
1402 SkScalar thetaWidth = thetaArc / segments;
1403 SkScalar t = SkScalarTan(0.5f * thetaWidth);
1404 if (!SkScalarIsFinite(t)) {
1405 return;
1406 }
1407 SkScalar startTheta = theta1;
1408 SkScalar w = SkScalarSqrt(SK_ScalarHalf + SkScalarCos(thetaWidth) * SK_ScalarHalf);
1409 for (int i = 0; i < segments; ++i) {
1410 SkScalar endTheta = startTheta + thetaWidth;
1411 SkScalar cosEndTheta, sinEndTheta = SkScalarSinCos(endTheta, &cosEndTheta);
1412
1413 unitPts[1].set(cosEndTheta, sinEndTheta);
1414 unitPts[1] += centerPoint;
1415 unitPts[0] = unitPts[1];
1416 unitPts[0].offset(t * sinEndTheta, -t * cosEndTheta);
1417 SkPoint mapped[2];
1418 pointTransform.mapPoints(mapped, unitPts, (int) SK_ARRAY_COUNT(unitPts));
1419 this->conicTo(mapped[0], mapped[1], w);
1420 startTheta = endTheta;
1421 }
1422}
1423
1424void SkPath::rArcTo(SkScalar rx, SkScalar ry, SkScalar xAxisRotate, SkPath::ArcSize largeArc,
1425 SkPath::Direction sweep, SkScalar dx, SkScalar dy) {
1426 SkPoint currentPoint;
1427 this->getLastPt(&currentPoint);
1428 this->arcTo(rx, ry, xAxisRotate, largeArc, sweep, currentPoint.fX + dx, currentPoint.fY + dy);
1429}
1430
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001431void SkPath::addArc(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001432 if (oval.isEmpty() || 0 == sweepAngle) {
1433 return;
1434 }
1435
1436 const SkScalar kFullCircleAngle = SkIntToScalar(360);
1437
1438 if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
bsalomon1978ce22016-05-31 10:42:16 -07001439 // We can treat the arc as an oval if it begins at one of our legal starting positions.
1440 // See SkPath::addOval() docs.
1441 SkScalar startOver90 = startAngle / 90.f;
1442 SkScalar startOver90I = SkScalarRoundToScalar(startOver90);
1443 SkScalar error = startOver90 - startOver90I;
1444 if (SkScalarNearlyEqual(error, 0)) {
1445 // Index 1 is at startAngle == 0.
1446 SkScalar startIndex = std::fmod(startOver90I + 1.f, 4.f);
1447 startIndex = startIndex < 0 ? startIndex + 4.f : startIndex;
1448 this->addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction,
1449 (unsigned) startIndex);
1450 return;
1451 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001452 }
bsalomon1978ce22016-05-31 10:42:16 -07001453 this->arcTo(oval, startAngle, sweepAngle, true);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001454}
1455
1456/*
1457 Need to handle the case when the angle is sharp, and our computed end-points
1458 for the arc go behind pt1 and/or p2...
1459*/
reedc7789042015-01-29 12:59:11 -08001460void SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2, SkScalar radius) {
reeda8b326c2014-12-09 11:50:32 -08001461 if (radius == 0) {
1462 this->lineTo(x1, y1);
1463 return;
1464 }
1465
1466 SkVector before, after;
reed@google.comabf15c12011-01-18 20:35:51 +00001467
reed@android.com8a1c16f2008-12-17 15:59:43 +00001468 // need to know our prev pt so we can construct tangent vectors
1469 {
1470 SkPoint start;
1471 this->getLastPt(&start);
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001472 // Handle degenerate cases by adding a line to the first point and
1473 // bailing out.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001474 before.setNormalize(x1 - start.fX, y1 - start.fY);
1475 after.setNormalize(x2 - x1, y2 - y1);
1476 }
reed@google.comabf15c12011-01-18 20:35:51 +00001477
reed@android.com8a1c16f2008-12-17 15:59:43 +00001478 SkScalar cosh = SkPoint::DotProduct(before, after);
1479 SkScalar sinh = SkPoint::CrossProduct(before, after);
1480
1481 if (SkScalarNearlyZero(sinh)) { // angle is too tight
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001482 this->lineTo(x1, y1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001483 return;
1484 }
reed@google.comabf15c12011-01-18 20:35:51 +00001485
caryclark88651ae2016-01-20 11:55:11 -08001486 SkScalar dist = SkScalarAbs(SkScalarMulDiv(radius, SK_Scalar1 - cosh, sinh));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001487
1488 SkScalar xx = x1 - SkScalarMul(dist, before.fX);
1489 SkScalar yy = y1 - SkScalarMul(dist, before.fY);
caryclark88651ae2016-01-20 11:55:11 -08001490 after.setLength(dist);
1491 this->lineTo(xx, yy);
1492 SkScalar weight = SkScalarSqrt(SK_ScalarHalf + cosh * SK_ScalarHalf);
1493 this->conicTo(x1, y1, x1 + after.fX, y1 + after.fY, weight);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001494}
1495
1496///////////////////////////////////////////////////////////////////////////////
1497
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001498void SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy, AddPathMode mode) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001499 SkMatrix matrix;
1500
1501 matrix.setTranslate(dx, dy);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001502 this->addPath(path, matrix, mode);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001503}
1504
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001505void SkPath::addPath(const SkPath& path, const SkMatrix& matrix, AddPathMode mode) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001506 SkPathRef::Editor(&fPathRef, path.countVerbs(), path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001507
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001508 RawIter iter(path);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001509 SkPoint pts[4];
1510 Verb verb;
1511
1512 SkMatrix::MapPtsProc proc = matrix.getMapPtsProc();
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001513 bool firstVerb = true;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001514 while ((verb = iter.next(pts)) != kDone_Verb) {
1515 switch (verb) {
1516 case kMove_Verb:
1517 proc(matrix, &pts[0], &pts[0], 1);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001518 if (firstVerb && mode == kExtend_AddPathMode && !isEmpty()) {
1519 injectMoveToIfNeeded(); // In case last contour is closed
1520 this->lineTo(pts[0]);
1521 } else {
1522 this->moveTo(pts[0]);
1523 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001524 break;
1525 case kLine_Verb:
1526 proc(matrix, &pts[1], &pts[1], 1);
1527 this->lineTo(pts[1]);
1528 break;
1529 case kQuad_Verb:
1530 proc(matrix, &pts[1], &pts[1], 2);
1531 this->quadTo(pts[1], pts[2]);
1532 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001533 case kConic_Verb:
1534 proc(matrix, &pts[1], &pts[1], 2);
1535 this->conicTo(pts[1], pts[2], iter.conicWeight());
1536 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001537 case kCubic_Verb:
1538 proc(matrix, &pts[1], &pts[1], 3);
1539 this->cubicTo(pts[1], pts[2], pts[3]);
1540 break;
1541 case kClose_Verb:
1542 this->close();
1543 break;
1544 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001545 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001546 }
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001547 firstVerb = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001548 }
1549}
1550
1551///////////////////////////////////////////////////////////////////////////////
1552
reed@google.com277c3f82013-05-31 15:17:50 +00001553static int pts_in_verb(unsigned verb) {
1554 static const uint8_t gPtsInVerb[] = {
1555 1, // kMove
1556 1, // kLine
1557 2, // kQuad
1558 2, // kConic
1559 3, // kCubic
1560 0, // kClose
1561 0 // kDone
1562 };
1563
1564 SkASSERT(verb < SK_ARRAY_COUNT(gPtsInVerb));
1565 return gPtsInVerb[verb];
1566}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001567
reed@android.com8a1c16f2008-12-17 15:59:43 +00001568// ignore the last point of the 1st contour
1569void SkPath::reversePathTo(const SkPath& path) {
caryclark51c56782016-11-07 05:09:28 -08001570 const uint8_t* verbs = path.fPathRef->verbsMemBegin(); // points at the last verb
1571 if (!verbs) { // empty path returns nullptr
reed@android.com8a1c16f2008-12-17 15:59:43 +00001572 return;
1573 }
caryclark51c56782016-11-07 05:09:28 -08001574 const uint8_t* verbsEnd = path.fPathRef->verbs() - 1; // points just past the first verb
1575 SkASSERT(verbsEnd[0] == kMove_Verb);
1576 const SkPoint* pts = path.fPathRef->pointsEnd() - 1;
1577 const SkScalar* conicWeights = path.fPathRef->conicWeightsEnd();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001578
caryclark51c56782016-11-07 05:09:28 -08001579 while (verbs < verbsEnd) {
1580 uint8_t v = *verbs++;
1581 pts -= pts_in_verb(v);
1582 switch (v) {
1583 case kMove_Verb:
1584 // if the path has multiple contours, stop after reversing the last
1585 return;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001586 case kLine_Verb:
caryclark51c56782016-11-07 05:09:28 -08001587 this->lineTo(pts[0]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001588 break;
1589 case kQuad_Verb:
caryclark51c56782016-11-07 05:09:28 -08001590 this->quadTo(pts[1], pts[0]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001591 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001592 case kConic_Verb:
caryclark51c56782016-11-07 05:09:28 -08001593 this->conicTo(pts[1], pts[0], *--conicWeights);
reed@google.com277c3f82013-05-31 15:17:50 +00001594 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001595 case kCubic_Verb:
caryclark51c56782016-11-07 05:09:28 -08001596 this->cubicTo(pts[2], pts[1], pts[0]);
1597 break;
1598 case kClose_Verb:
1599 SkASSERT(verbs - path.fPathRef->verbsMemBegin() == 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001600 break;
1601 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001602 SkDEBUGFAIL("bad verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001603 break;
1604 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001605 }
1606}
1607
reed@google.com63d73742012-01-10 15:33:12 +00001608void SkPath::reverseAddPath(const SkPath& src) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001609 SkPathRef::Editor ed(&fPathRef, src.fPathRef->countPoints(), src.fPathRef->countVerbs());
reed@google.com63d73742012-01-10 15:33:12 +00001610
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001611 const SkPoint* pts = src.fPathRef->pointsEnd();
1612 // we will iterator through src's verbs backwards
1613 const uint8_t* verbs = src.fPathRef->verbsMemBegin(); // points at the last verb
1614 const uint8_t* verbsEnd = src.fPathRef->verbs(); // points just past the first verb
reed@google.com277c3f82013-05-31 15:17:50 +00001615 const SkScalar* conicWeights = src.fPathRef->conicWeightsEnd();
reed@google.com63d73742012-01-10 15:33:12 +00001616
1617 bool needMove = true;
1618 bool needClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001619 while (verbs < verbsEnd) {
1620 uint8_t v = *(verbs++);
reed@google.com277c3f82013-05-31 15:17:50 +00001621 int n = pts_in_verb(v);
reed@google.com63d73742012-01-10 15:33:12 +00001622
1623 if (needMove) {
1624 --pts;
1625 this->moveTo(pts->fX, pts->fY);
1626 needMove = false;
1627 }
1628 pts -= n;
1629 switch (v) {
1630 case kMove_Verb:
1631 if (needClose) {
1632 this->close();
1633 needClose = false;
1634 }
1635 needMove = true;
1636 pts += 1; // so we see the point in "if (needMove)" above
1637 break;
1638 case kLine_Verb:
1639 this->lineTo(pts[0]);
1640 break;
1641 case kQuad_Verb:
1642 this->quadTo(pts[1], pts[0]);
1643 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001644 case kConic_Verb:
1645 this->conicTo(pts[1], pts[0], *--conicWeights);
1646 break;
reed@google.com63d73742012-01-10 15:33:12 +00001647 case kCubic_Verb:
1648 this->cubicTo(pts[2], pts[1], pts[0]);
1649 break;
1650 case kClose_Verb:
1651 needClose = true;
1652 break;
1653 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00001654 SkDEBUGFAIL("unexpected verb");
reed@google.com63d73742012-01-10 15:33:12 +00001655 }
1656 }
1657}
1658
reed@android.com8a1c16f2008-12-17 15:59:43 +00001659///////////////////////////////////////////////////////////////////////////////
1660
1661void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
1662 SkMatrix matrix;
1663
1664 matrix.setTranslate(dx, dy);
1665 this->transform(matrix, dst);
1666}
1667
reed@android.com8a1c16f2008-12-17 15:59:43 +00001668static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
1669 int level = 2) {
1670 if (--level >= 0) {
1671 SkPoint tmp[7];
1672
1673 SkChopCubicAtHalf(pts, tmp);
1674 subdivide_cubic_to(path, &tmp[0], level);
1675 subdivide_cubic_to(path, &tmp[3], level);
1676 } else {
1677 path->cubicTo(pts[1], pts[2], pts[3]);
1678 }
1679}
1680
1681void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
1682 SkDEBUGCODE(this->validate();)
halcanary96fcdcc2015-08-27 07:41:13 -07001683 if (dst == nullptr) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001684 dst = (SkPath*)this;
1685 }
1686
tomhudson@google.com8d430182011-06-06 19:11:19 +00001687 if (matrix.hasPerspective()) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001688 SkPath tmp;
1689 tmp.fFillType = fFillType;
1690
1691 SkPath::Iter iter(*this, false);
1692 SkPoint pts[4];
1693 SkPath::Verb verb;
1694
reed@google.com4a3b7142012-05-16 17:16:46 +00001695 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001696 switch (verb) {
1697 case kMove_Verb:
1698 tmp.moveTo(pts[0]);
1699 break;
1700 case kLine_Verb:
1701 tmp.lineTo(pts[1]);
1702 break;
1703 case kQuad_Verb:
reed220f9262014-12-17 08:21:04 -08001704 // promote the quad to a conic
1705 tmp.conicTo(pts[1], pts[2],
1706 SkConic::TransformW(pts, SK_Scalar1, matrix));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001707 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001708 case kConic_Verb:
reed220f9262014-12-17 08:21:04 -08001709 tmp.conicTo(pts[1], pts[2],
1710 SkConic::TransformW(pts, iter.conicWeight(), matrix));
reed@google.com277c3f82013-05-31 15:17:50 +00001711 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001712 case kCubic_Verb:
1713 subdivide_cubic_to(&tmp, pts);
1714 break;
1715 case kClose_Verb:
1716 tmp.close();
1717 break;
1718 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001719 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001720 break;
1721 }
1722 }
1723
1724 dst->swap(tmp);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001725 SkPathRef::Editor ed(&dst->fPathRef);
1726 matrix.mapPoints(ed.points(), ed.pathRef()->countPoints());
reed026beb52015-06-10 14:23:15 -07001727 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001728 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001729 SkPathRef::CreateTransformedCopy(&dst->fPathRef, *fPathRef.get(), matrix);
1730
reed@android.com8a1c16f2008-12-17 15:59:43 +00001731 if (this != dst) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001732 dst->fFillType = fFillType;
reed@google.com2a6f8ab2011-10-25 18:41:23 +00001733 dst->fConvexity = fConvexity;
jvanverthb3eb6872014-10-24 07:12:51 -07001734 dst->fIsVolatile = fIsVolatile;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001735 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001736
reed026beb52015-06-10 14:23:15 -07001737 if (SkPathPriv::kUnknown_FirstDirection == fFirstDirection) {
1738 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001739 } else {
1740 SkScalar det2x2 =
1741 SkScalarMul(matrix.get(SkMatrix::kMScaleX), matrix.get(SkMatrix::kMScaleY)) -
1742 SkScalarMul(matrix.get(SkMatrix::kMSkewX), matrix.get(SkMatrix::kMSkewY));
1743 if (det2x2 < 0) {
herb9f4dbca2015-09-28 11:05:47 -07001744 dst->fFirstDirection = SkPathPriv::OppositeFirstDirection(
1745 (SkPathPriv::FirstDirection)fFirstDirection.load());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001746 } else if (det2x2 > 0) {
herb9f4dbca2015-09-28 11:05:47 -07001747 dst->fFirstDirection = fFirstDirection.load();
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001748 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001749 dst->fConvexity = kUnknown_Convexity;
reed026beb52015-06-10 14:23:15 -07001750 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001751 }
1752 }
1753
reed@android.com8a1c16f2008-12-17 15:59:43 +00001754 SkDEBUGCODE(dst->validate();)
1755 }
1756}
1757
reed@android.com8a1c16f2008-12-17 15:59:43 +00001758///////////////////////////////////////////////////////////////////////////////
1759///////////////////////////////////////////////////////////////////////////////
1760
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001761enum SegmentState {
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001762 kEmptyContour_SegmentState, // The current contour is empty. We may be
1763 // starting processing or we may have just
1764 // closed a contour.
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001765 kAfterMove_SegmentState, // We have seen a move, but nothing else.
1766 kAfterPrimitive_SegmentState // We have seen a primitive but not yet
1767 // closed the path. Also the initial state.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001768};
1769
1770SkPath::Iter::Iter() {
1771#ifdef SK_DEBUG
halcanary96fcdcc2015-08-27 07:41:13 -07001772 fPts = nullptr;
1773 fConicWeights = nullptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001774 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001775 fForceClose = fCloseLine = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001776 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001777#endif
1778 // need to init enough to make next() harmlessly return kDone_Verb
halcanary96fcdcc2015-08-27 07:41:13 -07001779 fVerbs = nullptr;
1780 fVerbStop = nullptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001781 fNeedClose = false;
1782}
1783
1784SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
1785 this->setPath(path, forceClose);
1786}
1787
1788void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001789 fPts = path.fPathRef->points();
1790 fVerbs = path.fPathRef->verbs();
1791 fVerbStop = path.fPathRef->verbsMemBegin();
caryclark69424422016-10-04 13:06:17 -07001792 fConicWeights = path.fPathRef->conicWeights();
1793 if (fConicWeights) {
1794 fConicWeights -= 1; // begin one behind
1795 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001796 fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org72785c42011-12-29 21:03:28 +00001797 fMoveTo.fX = fMoveTo.fY = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001798 fForceClose = SkToU8(forceClose);
1799 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001800 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001801}
1802
1803bool SkPath::Iter::isClosedContour() const {
halcanary96fcdcc2015-08-27 07:41:13 -07001804 if (fVerbs == nullptr || fVerbs == fVerbStop) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001805 return false;
1806 }
1807 if (fForceClose) {
1808 return true;
1809 }
1810
1811 const uint8_t* verbs = fVerbs;
1812 const uint8_t* stop = fVerbStop;
reed@google.comabf15c12011-01-18 20:35:51 +00001813
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001814 if (kMove_Verb == *(verbs - 1)) {
1815 verbs -= 1; // skip the initial moveto
reed@android.com8a1c16f2008-12-17 15:59:43 +00001816 }
1817
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001818 while (verbs > stop) {
1819 // verbs points one beyond the current verb, decrement first.
1820 unsigned v = *(--verbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001821 if (kMove_Verb == v) {
1822 break;
1823 }
1824 if (kClose_Verb == v) {
1825 return true;
1826 }
1827 }
1828 return false;
1829}
1830
1831SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001832 SkASSERT(pts);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001833 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001834 // A special case: if both points are NaN, SkPoint::operation== returns
1835 // false, but the iterator expects that they are treated as the same.
1836 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001837 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1838 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001839 return kClose_Verb;
1840 }
1841
reed@google.com9e25dbf2012-05-15 17:05:38 +00001842 pts[0] = fLastPt;
1843 pts[1] = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001844 fLastPt = fMoveTo;
1845 fCloseLine = true;
1846 return kLine_Verb;
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +00001847 } else {
1848 pts[0] = fMoveTo;
1849 return kClose_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001850 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001851}
1852
reed@google.com9e25dbf2012-05-15 17:05:38 +00001853const SkPoint& SkPath::Iter::cons_moveTo() {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001854 if (fSegmentState == kAfterMove_SegmentState) {
1855 // Set the first return pt to the move pt
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001856 fSegmentState = kAfterPrimitive_SegmentState;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001857 return fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001858 } else {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001859 SkASSERT(fSegmentState == kAfterPrimitive_SegmentState);
1860 // Set the first return pt to the last pt of the previous primitive.
reed@google.com9e25dbf2012-05-15 17:05:38 +00001861 return fPts[-1];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001862 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001863}
1864
caryclarke8c56662015-07-14 11:19:26 -07001865void SkPath::Iter::consumeDegenerateSegments(bool exact) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001866 // We need to step over anything that will not move the current draw point
1867 // forward before the next move is seen
1868 const uint8_t* lastMoveVerb = 0;
1869 const SkPoint* lastMovePt = 0;
halcanary96fcdcc2015-08-27 07:41:13 -07001870 const SkScalar* lastMoveWeight = nullptr;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001871 SkPoint lastPt = fLastPt;
1872 while (fVerbs != fVerbStop) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001873 unsigned verb = *(fVerbs - 1); // fVerbs is one beyond the current verb
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001874 switch (verb) {
1875 case kMove_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001876 // Keep a record of this most recent move
1877 lastMoveVerb = fVerbs;
1878 lastMovePt = fPts;
robertphillipsb8de1f42015-02-23 11:17:01 -08001879 lastMoveWeight = fConicWeights;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001880 lastPt = fPts[0];
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001881 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001882 fPts++;
1883 break;
1884
1885 case kClose_Verb:
schenney@chromium.org7e963602012-06-13 17:05:43 +00001886 // A close when we are in a segment is always valid except when it
1887 // follows a move which follows a segment.
1888 if (fSegmentState == kAfterPrimitive_SegmentState && !lastMoveVerb) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001889 return;
1890 }
1891 // A close at any other time must be ignored
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001892 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001893 break;
1894
1895 case kLine_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001896 if (!IsLineDegenerate(lastPt, fPts[0], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001897 if (lastMoveVerb) {
1898 fVerbs = lastMoveVerb;
1899 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001900 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001901 return;
1902 }
1903 return;
1904 }
1905 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001906 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001907 fPts++;
1908 break;
1909
reed@google.com277c3f82013-05-31 15:17:50 +00001910 case kConic_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001911 case kQuad_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001912 if (!IsQuadDegenerate(lastPt, fPts[0], fPts[1], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001913 if (lastMoveVerb) {
1914 fVerbs = lastMoveVerb;
1915 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001916 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001917 return;
1918 }
1919 return;
1920 }
1921 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001922 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001923 fPts += 2;
reed@google.com277c3f82013-05-31 15:17:50 +00001924 fConicWeights += (kConic_Verb == verb);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001925 break;
1926
1927 case kCubic_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001928 if (!IsCubicDegenerate(lastPt, fPts[0], fPts[1], fPts[2], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001929 if (lastMoveVerb) {
1930 fVerbs = lastMoveVerb;
1931 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001932 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001933 return;
1934 }
1935 return;
1936 }
1937 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001938 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001939 fPts += 3;
1940 break;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001941
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001942 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001943 SkDEBUGFAIL("Should never see kDone_Verb");
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001944 }
1945 }
1946}
1947
reed@google.com4a3b7142012-05-16 17:16:46 +00001948SkPath::Verb SkPath::Iter::doNext(SkPoint ptsParam[4]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001949 SkASSERT(ptsParam);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001950
reed@android.com8a1c16f2008-12-17 15:59:43 +00001951 if (fVerbs == fVerbStop) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001952 // Close the curve if requested and if there is some curve to close
1953 if (fNeedClose && fSegmentState == kAfterPrimitive_SegmentState) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001954 if (kLine_Verb == this->autoClose(ptsParam)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001955 return kLine_Verb;
1956 }
1957 fNeedClose = false;
1958 return kClose_Verb;
1959 }
1960 return kDone_Verb;
1961 }
1962
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001963 // fVerbs is one beyond the current verb, decrement first
1964 unsigned verb = *(--fVerbs);
reed@google.com9e25dbf2012-05-15 17:05:38 +00001965 const SkPoint* SK_RESTRICT srcPts = fPts;
1966 SkPoint* SK_RESTRICT pts = ptsParam;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001967
1968 switch (verb) {
1969 case kMove_Verb:
1970 if (fNeedClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001971 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001972 verb = this->autoClose(pts);
1973 if (verb == kClose_Verb) {
1974 fNeedClose = false;
1975 }
1976 return (Verb)verb;
1977 }
1978 if (fVerbs == fVerbStop) { // might be a trailing moveto
1979 return kDone_Verb;
1980 }
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001981 fMoveTo = *srcPts;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001982 pts[0] = *srcPts;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001983 srcPts += 1;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001984 fSegmentState = kAfterMove_SegmentState;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001985 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001986 fNeedClose = fForceClose;
1987 break;
1988 case kLine_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001989 pts[0] = this->cons_moveTo();
1990 pts[1] = srcPts[0];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001991 fLastPt = srcPts[0];
1992 fCloseLine = false;
1993 srcPts += 1;
1994 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001995 case kConic_Verb:
1996 fConicWeights += 1;
1997 // fall-through
reed@android.com8a1c16f2008-12-17 15:59:43 +00001998 case kQuad_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001999 pts[0] = this->cons_moveTo();
2000 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002001 fLastPt = srcPts[1];
2002 srcPts += 2;
2003 break;
2004 case kCubic_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00002005 pts[0] = this->cons_moveTo();
2006 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002007 fLastPt = srcPts[2];
2008 srcPts += 3;
2009 break;
2010 case kClose_Verb:
2011 verb = this->autoClose(pts);
2012 if (verb == kLine_Verb) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002013 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00002014 } else {
2015 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00002016 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002017 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002018 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002019 break;
2020 }
2021 fPts = srcPts;
2022 return (Verb)verb;
2023}
2024
2025///////////////////////////////////////////////////////////////////////////////
2026
reed@android.com8a1c16f2008-12-17 15:59:43 +00002027/*
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002028 Format in compressed buffer: [ptCount, verbCount, pts[], verbs[]]
reed@android.com8a1c16f2008-12-17 15:59:43 +00002029*/
2030
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002031size_t SkPath::writeToMemory(void* storage) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002032 SkDEBUGCODE(this->validate();)
2033
halcanary96fcdcc2015-08-27 07:41:13 -07002034 if (nullptr == storage) {
caryclark69006412016-02-17 12:16:27 -08002035 const int byteCount = sizeof(int32_t) * 2 + fPathRef->writeSize();
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002036 return SkAlign4(byteCount);
2037 }
2038
2039 SkWBuffer buffer(storage);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002040
robertphillips@google.com466310d2013-12-03 16:43:54 +00002041 int32_t packed = (fConvexity << kConvexity_SerializationShift) |
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002042 (fFillType << kFillType_SerializationShift) |
reed026beb52015-06-10 14:23:15 -07002043 (fFirstDirection << kDirection_SerializationShift) |
reed8f086022015-06-11 14:22:19 -07002044 (fIsVolatile << kIsVolatile_SerializationShift) |
2045 kCurrent_Version;
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002046
robertphillips@google.com2972bb52012-08-07 17:32:51 +00002047 buffer.write32(packed);
caryclark69006412016-02-17 12:16:27 -08002048 buffer.write32(fLastMoveToIndex);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002049
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002050 fPathRef->writeToBuffer(&buffer);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002051
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002052 buffer.padToAlign4();
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002053 return buffer.pos();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002054}
2055
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002056size_t SkPath::readFromMemory(const void* storage, size_t length) {
Mike Reed1026ccf2017-01-08 14:35:29 -05002057 SkRBuffer buffer(storage, length);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002058
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00002059 int32_t packed;
2060 if (!buffer.readS32(&packed)) {
2061 return 0;
2062 }
2063
reed8f086022015-06-11 14:22:19 -07002064 unsigned version = packed & 0xFF;
caryclark69006412016-02-17 12:16:27 -08002065 if (version >= kPathPrivLastMoveToIndex_Version && !buffer.readS32(&fLastMoveToIndex)) {
2066 return 0;
2067 }
mtklein1b249332015-07-07 12:21:21 -07002068
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002069 fConvexity = (packed >> kConvexity_SerializationShift) & 0xFF;
robertphillips7070a3c2016-06-28 04:54:54 -07002070 fFillType = (packed >> kFillType_SerializationShift) & 0x3;
reed8f086022015-06-11 14:22:19 -07002071 uint8_t dir = (packed >> kDirection_SerializationShift) & 0x3;
jvanverthb3eb6872014-10-24 07:12:51 -07002072 fIsVolatile = (packed >> kIsVolatile_SerializationShift) & 0x1;
commit-bot@chromium.orgfed2ab62014-01-23 15:16:05 +00002073 SkPathRef* pathRef = SkPathRef::CreateFromBuffer(&buffer);
ajumaf8aec582016-01-13 13:46:31 -08002074 if (!pathRef) {
2075 return 0;
2076 }
2077
2078 fPathRef.reset(pathRef);
2079 SkDEBUGCODE(this->validate();)
2080 buffer.skipToAlign4();
reed@google.comabf15c12011-01-18 20:35:51 +00002081
reed8f086022015-06-11 14:22:19 -07002082 // compatibility check
2083 if (version < kPathPrivFirstDirection_Version) {
2084 switch (dir) { // old values
2085 case 0:
2086 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
2087 break;
2088 case 1:
2089 fFirstDirection = SkPathPriv::kCW_FirstDirection;
2090 break;
2091 case 2:
2092 fFirstDirection = SkPathPriv::kCCW_FirstDirection;
2093 break;
2094 default:
2095 SkASSERT(false);
2096 }
2097 } else {
2098 fFirstDirection = dir;
2099 }
2100
ajumaf8aec582016-01-13 13:46:31 -08002101 return buffer.pos();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002102}
2103
2104///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00002105
reede05fed02014-12-15 07:59:53 -08002106#include "SkStringUtils.h"
caryclark66a5d8b2014-06-24 08:30:15 -07002107#include "SkStream.h"
reed@google.com51bbe752013-01-17 22:07:50 +00002108
reed@google.com51bbe752013-01-17 22:07:50 +00002109static void append_params(SkString* str, const char label[], const SkPoint pts[],
reede05fed02014-12-15 07:59:53 -08002110 int count, SkScalarAsStringType strType, SkScalar conicWeight = -1) {
reed@google.com51bbe752013-01-17 22:07:50 +00002111 str->append(label);
2112 str->append("(");
skia.committer@gmail.com15dd3002013-01-18 07:07:28 +00002113
reed@google.com51bbe752013-01-17 22:07:50 +00002114 const SkScalar* values = &pts[0].fX;
2115 count *= 2;
2116
2117 for (int i = 0; i < count; ++i) {
reede05fed02014-12-15 07:59:53 -08002118 SkAppendScalar(str, values[i], strType);
reed@google.com51bbe752013-01-17 22:07:50 +00002119 if (i < count - 1) {
2120 str->append(", ");
2121 }
2122 }
reed@google.com277c3f82013-05-31 15:17:50 +00002123 if (conicWeight >= 0) {
2124 str->append(", ");
reede05fed02014-12-15 07:59:53 -08002125 SkAppendScalar(str, conicWeight, strType);
reed@google.com277c3f82013-05-31 15:17:50 +00002126 }
caryclark08fa28c2014-10-23 13:08:56 -07002127 str->append(");");
reede05fed02014-12-15 07:59:53 -08002128 if (kHex_SkScalarAsStringType == strType) {
caryclark08fa28c2014-10-23 13:08:56 -07002129 str->append(" // ");
2130 for (int i = 0; i < count; ++i) {
reede05fed02014-12-15 07:59:53 -08002131 SkAppendScalarDec(str, values[i]);
caryclark08fa28c2014-10-23 13:08:56 -07002132 if (i < count - 1) {
2133 str->append(", ");
2134 }
2135 }
2136 if (conicWeight >= 0) {
2137 str->append(", ");
reede05fed02014-12-15 07:59:53 -08002138 SkAppendScalarDec(str, conicWeight);
caryclark08fa28c2014-10-23 13:08:56 -07002139 }
2140 }
2141 str->append("\n");
reed@google.com51bbe752013-01-17 22:07:50 +00002142}
2143
caryclarke9562592014-09-15 09:26:09 -07002144void SkPath::dump(SkWStream* wStream, bool forceClose, bool dumpAsHex) const {
reede05fed02014-12-15 07:59:53 -08002145 SkScalarAsStringType asType = dumpAsHex ? kHex_SkScalarAsStringType : kDec_SkScalarAsStringType;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002146 Iter iter(*this, forceClose);
2147 SkPoint pts[4];
2148 Verb verb;
2149
reed@google.com51bbe752013-01-17 22:07:50 +00002150 SkString builder;
Cary Clark9f67f042016-12-16 11:32:54 -05002151 char const * const gFillTypeStrs[] = {
2152 "Winding",
2153 "EvenOdd",
2154 "InverseWinding",
2155 "InverseEvenOdd",
2156 };
2157 builder.printf("path.setFillType(SkPath::k%s_FillType);\n",
2158 gFillTypeStrs[(int) this->getFillType()]);
reed@google.com4a3b7142012-05-16 17:16:46 +00002159 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002160 switch (verb) {
2161 case kMove_Verb:
reede05fed02014-12-15 07:59:53 -08002162 append_params(&builder, "path.moveTo", &pts[0], 1, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002163 break;
2164 case kLine_Verb:
reede05fed02014-12-15 07:59:53 -08002165 append_params(&builder, "path.lineTo", &pts[1], 1, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002166 break;
2167 case kQuad_Verb:
reede05fed02014-12-15 07:59:53 -08002168 append_params(&builder, "path.quadTo", &pts[1], 2, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002169 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002170 case kConic_Verb:
reede05fed02014-12-15 07:59:53 -08002171 append_params(&builder, "path.conicTo", &pts[1], 2, asType, iter.conicWeight());
reed@google.com277c3f82013-05-31 15:17:50 +00002172 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002173 case kCubic_Verb:
reede05fed02014-12-15 07:59:53 -08002174 append_params(&builder, "path.cubicTo", &pts[1], 3, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002175 break;
2176 case kClose_Verb:
caryclark66a5d8b2014-06-24 08:30:15 -07002177 builder.append("path.close();\n");
reed@android.com8a1c16f2008-12-17 15:59:43 +00002178 break;
2179 default:
2180 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
2181 verb = kDone_Verb; // stop the loop
2182 break;
2183 }
caryclark1049f122015-04-20 08:31:59 -07002184 if (!wStream && builder.size()) {
2185 SkDebugf("%s", builder.c_str());
2186 builder.reset();
2187 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002188 }
caryclark66a5d8b2014-06-24 08:30:15 -07002189 if (wStream) {
2190 wStream->writeText(builder.c_str());
caryclark66a5d8b2014-06-24 08:30:15 -07002191 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002192}
2193
reed@android.come522ca52009-11-23 20:10:41 +00002194void SkPath::dump() const {
halcanary96fcdcc2015-08-27 07:41:13 -07002195 this->dump(nullptr, false, false);
caryclarke9562592014-09-15 09:26:09 -07002196}
2197
2198void SkPath::dumpHex() const {
halcanary96fcdcc2015-08-27 07:41:13 -07002199 this->dump(nullptr, false, true);
reed@android.come522ca52009-11-23 20:10:41 +00002200}
2201
2202#ifdef SK_DEBUG
2203void SkPath::validate() const {
reed@android.come522ca52009-11-23 20:10:41 +00002204 SkASSERT((fFillType & ~3) == 0);
reed@google.comabf15c12011-01-18 20:35:51 +00002205
djsollen@google.com077348c2012-10-22 20:23:32 +00002206#ifdef SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002207 if (!fBoundsIsDirty) {
2208 SkRect bounds;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002209
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002210 bool isFinite = compute_pt_bounds(&bounds, *fPathRef.get());
robertphillips@google.com5d8d1862012-08-15 14:36:41 +00002211 SkASSERT(SkToBool(fIsFinite) == isFinite);
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002212
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002213 if (fPathRef->countPoints() <= 1) {
reed@android.come522ca52009-11-23 20:10:41 +00002214 // if we're empty, fBounds may be empty but translated, so we can't
2215 // necessarily compare to bounds directly
2216 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
2217 // be [2, 2, 2, 2]
2218 SkASSERT(bounds.isEmpty());
2219 SkASSERT(fBounds.isEmpty());
2220 } else {
reed@google.comeac52bd2011-11-14 18:13:59 +00002221 if (bounds.isEmpty()) {
2222 SkASSERT(fBounds.isEmpty());
2223 } else {
reed@google.com3563c9e2011-11-14 19:34:57 +00002224 if (!fBounds.isEmpty()) {
2225 SkASSERT(fBounds.contains(bounds));
2226 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002227 }
reed@android.come522ca52009-11-23 20:10:41 +00002228 }
2229 }
djsollen@google.com077348c2012-10-22 20:23:32 +00002230#endif // SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002231}
djsollen@google.com077348c2012-10-22 20:23:32 +00002232#endif // SK_DEBUG
reed@android.come522ca52009-11-23 20:10:41 +00002233
reed@google.com04863fa2011-05-15 04:08:24 +00002234///////////////////////////////////////////////////////////////////////////////
2235
reed@google.com0b7b9822011-05-16 12:29:27 +00002236static int sign(SkScalar x) { return x < 0; }
2237#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00002238
robertphillipsc506e302014-09-16 09:43:31 -07002239enum DirChange {
2240 kLeft_DirChange,
2241 kRight_DirChange,
2242 kStraight_DirChange,
2243 kBackwards_DirChange,
2244
2245 kInvalid_DirChange
2246};
2247
2248
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002249static bool almost_equal(SkScalar compA, SkScalar compB) {
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002250 // The error epsilon was empirically derived; worse case round rects
2251 // with a mid point outset by 2x float epsilon in tests had an error
2252 // of 12.
2253 const int epsilon = 16;
2254 if (!SkScalarIsFinite(compA) || !SkScalarIsFinite(compB)) {
2255 return false;
2256 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002257 // no need to check for small numbers because SkPath::Iter has removed degenerate values
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002258 int aBits = SkFloatAs2sCompliment(compA);
2259 int bBits = SkFloatAs2sCompliment(compB);
2260 return aBits < bBits + epsilon && bBits < aBits + epsilon;
reed@google.com04863fa2011-05-15 04:08:24 +00002261}
2262
caryclarkb4216502015-03-02 13:02:34 -08002263static bool approximately_zero_when_compared_to(double x, double y) {
2264 return x == 0 || fabs(x) < fabs(y * FLT_EPSILON);
robertphillipsc506e302014-09-16 09:43:31 -07002265}
2266
caryclarkb4216502015-03-02 13:02:34 -08002267
reed@google.com04863fa2011-05-15 04:08:24 +00002268// only valid for a single contour
2269struct Convexicator {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002270 Convexicator()
2271 : fPtCount(0)
2272 , fConvexity(SkPath::kConvex_Convexity)
reed026beb52015-06-10 14:23:15 -07002273 , fFirstDirection(SkPathPriv::kUnknown_FirstDirection)
caryclark5ccef572015-03-02 10:07:56 -08002274 , fIsFinite(true)
2275 , fIsCurve(false) {
robertphillipsc506e302014-09-16 09:43:31 -07002276 fExpectedDir = kInvalid_DirChange;
reed@google.com04863fa2011-05-15 04:08:24 +00002277 // warnings
djsollen2f124632016-04-29 13:53:05 -07002278 fPriorPt.set(0,0);
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002279 fLastPt.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002280 fCurrPt.set(0, 0);
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002281 fLastVec.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002282 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00002283
2284 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00002285 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00002286 }
2287
2288 SkPath::Convexity getConvexity() const { return fConvexity; }
2289
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002290 /** The direction returned is only valid if the path is determined convex */
reed026beb52015-06-10 14:23:15 -07002291 SkPathPriv::FirstDirection getFirstDirection() const { return fFirstDirection; }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002292
reed@google.com04863fa2011-05-15 04:08:24 +00002293 void addPt(const SkPoint& pt) {
caryclarkd3d1a982014-12-08 04:57:38 -08002294 if (SkPath::kConcave_Convexity == fConvexity || !fIsFinite) {
reed@google.com04863fa2011-05-15 04:08:24 +00002295 return;
2296 }
2297
2298 if (0 == fPtCount) {
2299 fCurrPt = pt;
2300 ++fPtCount;
2301 } else {
2302 SkVector vec = pt - fCurrPt;
caryclarkd3d1a982014-12-08 04:57:38 -08002303 SkScalar lengthSqd = vec.lengthSqd();
2304 if (!SkScalarIsFinite(lengthSqd)) {
2305 fIsFinite = false;
caryclarke8c56662015-07-14 11:19:26 -07002306 } else if (lengthSqd) {
caryclarkb4216502015-03-02 13:02:34 -08002307 fPriorPt = fLastPt;
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002308 fLastPt = fCurrPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002309 fCurrPt = pt;
2310 if (++fPtCount == 2) {
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002311 fFirstVec = fLastVec = vec;
reed@google.com04863fa2011-05-15 04:08:24 +00002312 } else {
2313 SkASSERT(fPtCount > 2);
2314 this->addVec(vec);
2315 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002316
reed@google.com85b6e392011-05-15 20:25:17 +00002317 int sx = sign(vec.fX);
2318 int sy = sign(vec.fY);
2319 fDx += (sx != fSx);
2320 fDy += (sy != fSy);
2321 fSx = sx;
2322 fSy = sy;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002323
reed@google.com85b6e392011-05-15 20:25:17 +00002324 if (fDx > 3 || fDy > 3) {
2325 fConvexity = SkPath::kConcave_Convexity;
2326 }
reed@google.com04863fa2011-05-15 04:08:24 +00002327 }
2328 }
2329 }
2330
2331 void close() {
2332 if (fPtCount > 2) {
2333 this->addVec(fFirstVec);
2334 }
2335 }
2336
caryclarkb4216502015-03-02 13:02:34 -08002337 DirChange directionChange(const SkVector& curVec) {
2338 SkScalar cross = SkPoint::CrossProduct(fLastVec, curVec);
2339
2340 SkScalar smallest = SkTMin(fCurrPt.fX, SkTMin(fCurrPt.fY, SkTMin(fLastPt.fX, fLastPt.fY)));
2341 SkScalar largest = SkTMax(fCurrPt.fX, SkTMax(fCurrPt.fY, SkTMax(fLastPt.fX, fLastPt.fY)));
2342 largest = SkTMax(largest, -smallest);
2343
2344 if (!almost_equal(largest, largest + cross)) {
2345 int sign = SkScalarSignAsInt(cross);
2346 if (sign) {
2347 return (1 == sign) ? kRight_DirChange : kLeft_DirChange;
2348 }
2349 }
2350
2351 if (cross) {
2352 double dLastVecX = SkScalarToDouble(fLastPt.fX) - SkScalarToDouble(fPriorPt.fX);
2353 double dLastVecY = SkScalarToDouble(fLastPt.fY) - SkScalarToDouble(fPriorPt.fY);
2354 double dCurrVecX = SkScalarToDouble(fCurrPt.fX) - SkScalarToDouble(fLastPt.fX);
2355 double dCurrVecY = SkScalarToDouble(fCurrPt.fY) - SkScalarToDouble(fLastPt.fY);
2356 double dCross = dLastVecX * dCurrVecY - dLastVecY * dCurrVecX;
2357 if (!approximately_zero_when_compared_to(dCross, SkScalarToDouble(largest))) {
2358 int sign = SkScalarSignAsInt(SkDoubleToScalar(dCross));
2359 if (sign) {
2360 return (1 == sign) ? kRight_DirChange : kLeft_DirChange;
2361 }
2362 }
2363 }
2364
2365 if (!SkScalarNearlyZero(fLastVec.lengthSqd(), SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
2366 !SkScalarNearlyZero(curVec.lengthSqd(), SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
2367 fLastVec.dot(curVec) < 0.0f) {
2368 return kBackwards_DirChange;
2369 }
2370
2371 return kStraight_DirChange;
2372 }
2373
2374
caryclarkd3d1a982014-12-08 04:57:38 -08002375 bool isFinite() const {
2376 return fIsFinite;
2377 }
2378
caryclark5ccef572015-03-02 10:07:56 -08002379 void setCurve(bool isCurve) {
2380 fIsCurve = isCurve;
2381 }
2382
reed@google.com04863fa2011-05-15 04:08:24 +00002383private:
2384 void addVec(const SkVector& vec) {
2385 SkASSERT(vec.fX || vec.fY);
caryclarkb4216502015-03-02 13:02:34 -08002386 DirChange dir = this->directionChange(vec);
robertphillipsc506e302014-09-16 09:43:31 -07002387 switch (dir) {
2388 case kLeft_DirChange: // fall through
2389 case kRight_DirChange:
2390 if (kInvalid_DirChange == fExpectedDir) {
2391 fExpectedDir = dir;
reed026beb52015-06-10 14:23:15 -07002392 fFirstDirection = (kRight_DirChange == dir) ? SkPathPriv::kCW_FirstDirection
2393 : SkPathPriv::kCCW_FirstDirection;
robertphillipsc506e302014-09-16 09:43:31 -07002394 } else if (dir != fExpectedDir) {
2395 fConvexity = SkPath::kConcave_Convexity;
reed026beb52015-06-10 14:23:15 -07002396 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
robertphillipsc506e302014-09-16 09:43:31 -07002397 }
2398 fLastVec = vec;
2399 break;
2400 case kStraight_DirChange:
2401 break;
2402 case kBackwards_DirChange:
caryclark5ccef572015-03-02 10:07:56 -08002403 if (fIsCurve) {
liyuqianbdabcc42016-11-03 11:40:19 -07002404 // If any of the subsequent dir is non-backward, it'll be concave.
2405 // Otherwise, it's still convex.
2406 fExpectedDir = dir;
caryclark5ccef572015-03-02 10:07:56 -08002407 }
robertphillipsc506e302014-09-16 09:43:31 -07002408 fLastVec = vec;
2409 break;
2410 case kInvalid_DirChange:
2411 SkFAIL("Use of invalid direction change flag");
2412 break;
reed@google.com04863fa2011-05-15 04:08:24 +00002413 }
2414 }
2415
caryclarkb4216502015-03-02 13:02:34 -08002416 SkPoint fPriorPt;
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002417 SkPoint fLastPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002418 SkPoint fCurrPt;
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002419 // fLastVec does not necessarily start at fLastPt. We only advance it when the cross product
2420 // value with the current vec is deemed to be of a significant value.
2421 SkVector fLastVec, fFirstVec;
reed@google.com04863fa2011-05-15 04:08:24 +00002422 int fPtCount; // non-degenerate points
robertphillipsc506e302014-09-16 09:43:31 -07002423 DirChange fExpectedDir;
reed@google.com04863fa2011-05-15 04:08:24 +00002424 SkPath::Convexity fConvexity;
reed026beb52015-06-10 14:23:15 -07002425 SkPathPriv::FirstDirection fFirstDirection;
reed@google.com0b7b9822011-05-16 12:29:27 +00002426 int fDx, fDy, fSx, fSy;
caryclarkd3d1a982014-12-08 04:57:38 -08002427 bool fIsFinite;
caryclark5ccef572015-03-02 10:07:56 -08002428 bool fIsCurve;
reed@google.com04863fa2011-05-15 04:08:24 +00002429};
2430
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002431SkPath::Convexity SkPath::internalGetConvexity() const {
2432 SkASSERT(kUnknown_Convexity == fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002433 SkPoint pts[4];
2434 SkPath::Verb verb;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002435 SkPath::Iter iter(*this, true);
reed@google.com04863fa2011-05-15 04:08:24 +00002436
2437 int contourCount = 0;
2438 int count;
2439 Convexicator state;
2440
caryclarkd3d1a982014-12-08 04:57:38 -08002441 if (!isFinite()) {
2442 return kUnknown_Convexity;
2443 }
caryclarke8c56662015-07-14 11:19:26 -07002444 while ((verb = iter.next(pts, true, true)) != SkPath::kDone_Verb) {
reed@google.com04863fa2011-05-15 04:08:24 +00002445 switch (verb) {
2446 case kMove_Verb:
2447 if (++contourCount > 1) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002448 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002449 return kConcave_Convexity;
2450 }
2451 pts[1] = pts[0];
caryclark5ccef572015-03-02 10:07:56 -08002452 // fall through
2453 case kLine_Verb:
reed@google.com04863fa2011-05-15 04:08:24 +00002454 count = 1;
caryclark5ccef572015-03-02 10:07:56 -08002455 state.setCurve(false);
reed@google.com04863fa2011-05-15 04:08:24 +00002456 break;
caryclark5ccef572015-03-02 10:07:56 -08002457 case kQuad_Verb:
2458 // fall through
2459 case kConic_Verb:
2460 // fall through
2461 case kCubic_Verb:
2462 count = 2 + (kCubic_Verb == verb);
2463 // As an additional enhancement, this could set curve true only
2464 // if the curve is nonlinear
2465 state.setCurve(true);
2466 break;
reed@google.com04863fa2011-05-15 04:08:24 +00002467 case kClose_Verb:
caryclark5ccef572015-03-02 10:07:56 -08002468 state.setCurve(false);
reed@google.com04863fa2011-05-15 04:08:24 +00002469 state.close();
2470 count = 0;
2471 break;
2472 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00002473 SkDEBUGFAIL("bad verb");
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002474 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002475 return kConcave_Convexity;
2476 }
2477
2478 for (int i = 1; i <= count; i++) {
2479 state.addPt(pts[i]);
2480 }
2481 // early exit
caryclarkd3d1a982014-12-08 04:57:38 -08002482 if (!state.isFinite()) {
2483 return kUnknown_Convexity;
2484 }
reed@google.com04863fa2011-05-15 04:08:24 +00002485 if (kConcave_Convexity == state.getConvexity()) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002486 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002487 return kConcave_Convexity;
2488 }
2489 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002490 fConvexity = state.getConvexity();
reed026beb52015-06-10 14:23:15 -07002491 if (kConvex_Convexity == fConvexity && SkPathPriv::kUnknown_FirstDirection == fFirstDirection) {
2492 fFirstDirection = state.getFirstDirection();
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002493 }
2494 return static_cast<Convexity>(fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002495}
reed@google.com69a99432012-01-10 18:00:10 +00002496
2497///////////////////////////////////////////////////////////////////////////////
2498
2499class ContourIter {
2500public:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002501 ContourIter(const SkPathRef& pathRef);
reed@google.com69a99432012-01-10 18:00:10 +00002502
2503 bool done() const { return fDone; }
2504 // if !done() then these may be called
2505 int count() const { return fCurrPtCount; }
2506 const SkPoint* pts() const { return fCurrPt; }
2507 void next();
2508
2509private:
2510 int fCurrPtCount;
2511 const SkPoint* fCurrPt;
2512 const uint8_t* fCurrVerb;
2513 const uint8_t* fStopVerbs;
reed@google.com277c3f82013-05-31 15:17:50 +00002514 const SkScalar* fCurrConicWeight;
reed@google.com69a99432012-01-10 18:00:10 +00002515 bool fDone;
reed@google.comd1ab9322012-01-10 18:40:03 +00002516 SkDEBUGCODE(int fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002517};
2518
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002519ContourIter::ContourIter(const SkPathRef& pathRef) {
2520 fStopVerbs = pathRef.verbsMemBegin();
reed@google.com69a99432012-01-10 18:00:10 +00002521 fDone = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002522 fCurrPt = pathRef.points();
2523 fCurrVerb = pathRef.verbs();
reed@google.com277c3f82013-05-31 15:17:50 +00002524 fCurrConicWeight = pathRef.conicWeights();
reed@google.com69a99432012-01-10 18:00:10 +00002525 fCurrPtCount = 0;
reed@google.comd1ab9322012-01-10 18:40:03 +00002526 SkDEBUGCODE(fContourCounter = 0;)
reed@google.com69a99432012-01-10 18:00:10 +00002527 this->next();
2528}
2529
2530void ContourIter::next() {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002531 if (fCurrVerb <= fStopVerbs) {
reed@google.com69a99432012-01-10 18:00:10 +00002532 fDone = true;
2533 }
2534 if (fDone) {
2535 return;
2536 }
2537
2538 // skip pts of prev contour
2539 fCurrPt += fCurrPtCount;
2540
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002541 SkASSERT(SkPath::kMove_Verb == fCurrVerb[~0]);
reed@google.com69a99432012-01-10 18:00:10 +00002542 int ptCount = 1; // moveTo
2543 const uint8_t* verbs = fCurrVerb;
2544
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002545 for (--verbs; verbs > fStopVerbs; --verbs) {
2546 switch (verbs[~0]) {
reed@google.com69a99432012-01-10 18:00:10 +00002547 case SkPath::kMove_Verb:
reed@google.com69a99432012-01-10 18:00:10 +00002548 goto CONTOUR_END;
2549 case SkPath::kLine_Verb:
2550 ptCount += 1;
2551 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002552 case SkPath::kConic_Verb:
2553 fCurrConicWeight += 1;
2554 // fall-through
reed@google.com69a99432012-01-10 18:00:10 +00002555 case SkPath::kQuad_Verb:
2556 ptCount += 2;
2557 break;
2558 case SkPath::kCubic_Verb:
2559 ptCount += 3;
2560 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002561 case SkPath::kClose_Verb:
2562 break;
2563 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00002564 SkDEBUGFAIL("unexpected verb");
reed@google.com69a99432012-01-10 18:00:10 +00002565 break;
2566 }
2567 }
2568CONTOUR_END:
2569 fCurrPtCount = ptCount;
2570 fCurrVerb = verbs;
reed@google.comd1ab9322012-01-10 18:40:03 +00002571 SkDEBUGCODE(++fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002572}
2573
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002574// returns cross product of (p1 - p0) and (p2 - p0)
reed@google.com69a99432012-01-10 18:00:10 +00002575static SkScalar cross_prod(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002576 SkScalar cross = SkPoint::CrossProduct(p1 - p0, p2 - p0);
2577 // We may get 0 when the above subtracts underflow. We expect this to be
2578 // very rare and lazily promote to double.
2579 if (0 == cross) {
2580 double p0x = SkScalarToDouble(p0.fX);
2581 double p0y = SkScalarToDouble(p0.fY);
2582
2583 double p1x = SkScalarToDouble(p1.fX);
2584 double p1y = SkScalarToDouble(p1.fY);
2585
2586 double p2x = SkScalarToDouble(p2.fX);
2587 double p2y = SkScalarToDouble(p2.fY);
2588
2589 cross = SkDoubleToScalar((p1x - p0x) * (p2y - p0y) -
2590 (p1y - p0y) * (p2x - p0x));
2591
2592 }
2593 return cross;
reed@google.com69a99432012-01-10 18:00:10 +00002594}
2595
reed@google.comc1ea60a2012-01-31 15:15:36 +00002596// Returns the first pt with the maximum Y coordinate
reed@google.com69a99432012-01-10 18:00:10 +00002597static int find_max_y(const SkPoint pts[], int count) {
2598 SkASSERT(count > 0);
2599 SkScalar max = pts[0].fY;
reed@google.comc1ea60a2012-01-31 15:15:36 +00002600 int firstIndex = 0;
reed@google.com69a99432012-01-10 18:00:10 +00002601 for (int i = 1; i < count; ++i) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002602 SkScalar y = pts[i].fY;
2603 if (y > max) {
2604 max = y;
2605 firstIndex = i;
reed@google.com69a99432012-01-10 18:00:10 +00002606 }
2607 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002608 return firstIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002609}
2610
reed@google.comcabaf1d2012-01-11 21:03:05 +00002611static int find_diff_pt(const SkPoint pts[], int index, int n, int inc) {
2612 int i = index;
2613 for (;;) {
2614 i = (i + inc) % n;
2615 if (i == index) { // we wrapped around, so abort
2616 break;
2617 }
2618 if (pts[index] != pts[i]) { // found a different point, success!
2619 break;
2620 }
2621 }
2622 return i;
2623}
2624
reed@google.comc1ea60a2012-01-31 15:15:36 +00002625/**
2626 * Starting at index, and moving forward (incrementing), find the xmin and
2627 * xmax of the contiguous points that have the same Y.
2628 */
2629static int find_min_max_x_at_y(const SkPoint pts[], int index, int n,
2630 int* maxIndexPtr) {
2631 const SkScalar y = pts[index].fY;
2632 SkScalar min = pts[index].fX;
2633 SkScalar max = min;
2634 int minIndex = index;
2635 int maxIndex = index;
2636 for (int i = index + 1; i < n; ++i) {
2637 if (pts[i].fY != y) {
2638 break;
2639 }
2640 SkScalar x = pts[i].fX;
2641 if (x < min) {
2642 min = x;
2643 minIndex = i;
2644 } else if (x > max) {
2645 max = x;
2646 maxIndex = i;
2647 }
2648 }
2649 *maxIndexPtr = maxIndex;
2650 return minIndex;
2651}
2652
reed026beb52015-06-10 14:23:15 -07002653static void crossToDir(SkScalar cross, SkPathPriv::FirstDirection* dir) {
2654 *dir = cross > 0 ? SkPathPriv::kCW_FirstDirection : SkPathPriv::kCCW_FirstDirection;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002655}
2656
reed@google.comac8543f2012-01-30 20:51:25 +00002657/*
2658 * We loop through all contours, and keep the computed cross-product of the
2659 * contour that contained the global y-max. If we just look at the first
2660 * contour, we may find one that is wound the opposite way (correctly) since
2661 * it is the interior of a hole (e.g. 'o'). Thus we must find the contour
2662 * that is outer most (or at least has the global y-max) before we can consider
2663 * its cross product.
2664 */
reed026beb52015-06-10 14:23:15 -07002665bool SkPathPriv::CheapComputeFirstDirection(const SkPath& path, FirstDirection* dir) {
herb9f4dbca2015-09-28 11:05:47 -07002666 if (kUnknown_FirstDirection != path.fFirstDirection.load()) {
2667 *dir = static_cast<FirstDirection>(path.fFirstDirection.load());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002668 return true;
2669 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002670
2671 // don't want to pay the cost for computing this if it
2672 // is unknown, so we don't call isConvex()
reed026beb52015-06-10 14:23:15 -07002673 if (SkPath::kConvex_Convexity == path.getConvexityOrUnknown()) {
2674 SkASSERT(kUnknown_FirstDirection == path.fFirstDirection);
herb9f4dbca2015-09-28 11:05:47 -07002675 *dir = static_cast<FirstDirection>(path.fFirstDirection.load());
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002676 return false;
2677 }
reed@google.com69a99432012-01-10 18:00:10 +00002678
reed026beb52015-06-10 14:23:15 -07002679 ContourIter iter(*path.fPathRef.get());
reed@google.com69a99432012-01-10 18:00:10 +00002680
reed@google.comac8543f2012-01-30 20:51:25 +00002681 // initialize with our logical y-min
reed026beb52015-06-10 14:23:15 -07002682 SkScalar ymax = path.getBounds().fTop;
reed@google.comac8543f2012-01-30 20:51:25 +00002683 SkScalar ymaxCross = 0;
2684
reed@google.com69a99432012-01-10 18:00:10 +00002685 for (; !iter.done(); iter.next()) {
2686 int n = iter.count();
reed@google.comcabaf1d2012-01-11 21:03:05 +00002687 if (n < 3) {
2688 continue;
2689 }
djsollen@google.come63793a2012-03-21 15:39:03 +00002690
reed@google.comcabaf1d2012-01-11 21:03:05 +00002691 const SkPoint* pts = iter.pts();
reed@google.com69a99432012-01-10 18:00:10 +00002692 SkScalar cross = 0;
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002693 int index = find_max_y(pts, n);
2694 if (pts[index].fY < ymax) {
2695 continue;
2696 }
2697
2698 // If there is more than 1 distinct point at the y-max, we take the
2699 // x-min and x-max of them and just subtract to compute the dir.
2700 if (pts[(index + 1) % n].fY == pts[index].fY) {
2701 int maxIndex;
2702 int minIndex = find_min_max_x_at_y(pts, index, n, &maxIndex);
2703 if (minIndex == maxIndex) {
2704 goto TRY_CROSSPROD;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002705 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002706 SkASSERT(pts[minIndex].fY == pts[index].fY);
2707 SkASSERT(pts[maxIndex].fY == pts[index].fY);
2708 SkASSERT(pts[minIndex].fX <= pts[maxIndex].fX);
2709 // we just subtract the indices, and let that auto-convert to
2710 // SkScalar, since we just want - or + to signal the direction.
2711 cross = minIndex - maxIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002712 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002713 TRY_CROSSPROD:
2714 // Find a next and prev index to use for the cross-product test,
2715 // but we try to find pts that form non-zero vectors from pts[index]
2716 //
2717 // Its possible that we can't find two non-degenerate vectors, so
2718 // we have to guard our search (e.g. all the pts could be in the
2719 // same place).
2720
2721 // we pass n - 1 instead of -1 so we don't foul up % operator by
2722 // passing it a negative LH argument.
2723 int prev = find_diff_pt(pts, index, n, n - 1);
2724 if (prev == index) {
2725 // completely degenerate, skip to next contour
reed@google.comac8543f2012-01-30 20:51:25 +00002726 continue;
2727 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002728 int next = find_diff_pt(pts, index, n, 1);
2729 SkASSERT(next != index);
2730 cross = cross_prod(pts[prev], pts[index], pts[next]);
2731 // if we get a zero and the points are horizontal, then we look at the spread in
2732 // x-direction. We really should continue to walk away from the degeneracy until
2733 // there is a divergence.
2734 if (0 == cross && pts[prev].fY == pts[index].fY && pts[next].fY == pts[index].fY) {
2735 // construct the subtract so we get the correct Direction below
2736 cross = pts[index].fX - pts[next].fX;
reed@google.com188bfcf2012-01-17 18:26:38 +00002737 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002738 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002739
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002740 if (cross) {
2741 // record our best guess so far
2742 ymax = pts[index].fY;
2743 ymaxCross = cross;
reed@google.com69a99432012-01-10 18:00:10 +00002744 }
2745 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002746 if (ymaxCross) {
2747 crossToDir(ymaxCross, dir);
reed026beb52015-06-10 14:23:15 -07002748 path.fFirstDirection = *dir;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002749 return true;
2750 } else {
2751 return false;
2752 }
reed@google.comac8543f2012-01-30 20:51:25 +00002753}
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002754
2755///////////////////////////////////////////////////////////////////////////////
2756
caryclark9aacd902015-12-14 08:38:09 -08002757static bool between(SkScalar a, SkScalar b, SkScalar c) {
2758 SkASSERT(((a <= b && b <= c) || (a >= b && b >= c)) == ((a - b) * (c - b) <= 0)
2759 || (SkScalarNearlyZero(a) && SkScalarNearlyZero(b) && SkScalarNearlyZero(c)));
2760 return (a - b) * (c - b) <= 0;
2761}
2762
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002763static SkScalar eval_cubic_coeff(SkScalar A, SkScalar B, SkScalar C,
2764 SkScalar D, SkScalar t) {
2765 return SkScalarMulAdd(SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C), t, D);
2766}
2767
2768static SkScalar eval_cubic_pts(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2769 SkScalar t) {
2770 SkScalar A = c3 + 3*(c1 - c2) - c0;
2771 SkScalar B = 3*(c2 - c1 - c1 + c0);
2772 SkScalar C = 3*(c1 - c0);
2773 SkScalar D = c0;
2774 return eval_cubic_coeff(A, B, C, D, t);
2775}
2776
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002777template <size_t N> static void find_minmax(const SkPoint pts[],
2778 SkScalar* minPtr, SkScalar* maxPtr) {
2779 SkScalar min, max;
2780 min = max = pts[0].fX;
2781 for (size_t i = 1; i < N; ++i) {
2782 min = SkMinScalar(min, pts[i].fX);
2783 max = SkMaxScalar(max, pts[i].fX);
2784 }
2785 *minPtr = min;
2786 *maxPtr = max;
2787}
2788
caryclark9cb5d752015-12-18 04:35:24 -08002789static bool checkOnCurve(SkScalar x, SkScalar y, const SkPoint& start, const SkPoint& end) {
2790 if (start.fY == end.fY) {
2791 return between(start.fX, x, end.fX) && x != end.fX;
2792 } else {
2793 return x == start.fX && y == start.fY;
2794 }
2795}
2796
caryclark9aacd902015-12-14 08:38:09 -08002797static int winding_mono_cubic(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
caryclark9cb5d752015-12-18 04:35:24 -08002798 SkScalar y0 = pts[0].fY;
2799 SkScalar y3 = pts[3].fY;
2800
2801 int dir = 1;
2802 if (y0 > y3) {
2803 SkTSwap(y0, y3);
2804 dir = -1;
2805 }
2806 if (y < y0 || y > y3) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002807 return 0;
2808 }
caryclark9cb5d752015-12-18 04:35:24 -08002809 if (checkOnCurve(x, y, pts[0], pts[3])) {
2810 *onCurveCount += 1;
caryclark9aacd902015-12-14 08:38:09 -08002811 return 0;
2812 }
caryclark9cb5d752015-12-18 04:35:24 -08002813 if (y == y3) {
2814 return 0;
2815 }
2816
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002817 // quickreject or quickaccept
2818 SkScalar min, max;
2819 find_minmax<4>(pts, &min, &max);
2820 if (x < min) {
2821 return 0;
2822 }
2823 if (x > max) {
2824 return dir;
2825 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002826
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002827 // compute the actual x(t) value
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002828 SkScalar t;
mbarbella276e6332016-05-31 14:44:01 -07002829 if (!SkCubicClipper::ChopMonoAtY(pts, y, &t)) {
mbarbella99600d02016-06-01 15:39:47 -07002830 return 0;
mbarbella276e6332016-05-31 14:44:01 -07002831 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002832 SkScalar xt = eval_cubic_pts(pts[0].fX, pts[1].fX, pts[2].fX, pts[3].fX, t);
caryclark9aacd902015-12-14 08:38:09 -08002833 if (SkScalarNearlyEqual(xt, x)) {
2834 if (x != pts[3].fX || y != pts[3].fY) { // don't test end points; they're start points
2835 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08002836 return 0;
caryclark9aacd902015-12-14 08:38:09 -08002837 }
2838 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002839 return xt < x ? dir : 0;
2840}
2841
caryclark9aacd902015-12-14 08:38:09 -08002842static int winding_cubic(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002843 SkPoint dst[10];
2844 int n = SkChopCubicAtYExtrema(pts, dst);
2845 int w = 0;
2846 for (int i = 0; i <= n; ++i) {
caryclark9aacd902015-12-14 08:38:09 -08002847 w += winding_mono_cubic(&dst[i * 3], x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002848 }
2849 return w;
2850}
2851
caryclark9aacd902015-12-14 08:38:09 -08002852static double conic_eval_numerator(const SkScalar src[], SkScalar w, SkScalar t) {
2853 SkASSERT(src);
2854 SkASSERT(t >= 0 && t <= 1);
2855 SkScalar src2w = src[2] * w;
2856 SkScalar C = src[0];
2857 SkScalar A = src[4] - 2 * src2w + C;
2858 SkScalar B = 2 * (src2w - C);
2859 return (A * t + B) * t + C;
2860}
2861
2862
2863static double conic_eval_denominator(SkScalar w, SkScalar t) {
2864 SkScalar B = 2 * (w - 1);
2865 SkScalar C = 1;
2866 SkScalar A = -B;
2867 return (A * t + B) * t + C;
2868}
2869
2870static int winding_mono_conic(const SkConic& conic, SkScalar x, SkScalar y, int* onCurveCount) {
2871 const SkPoint* pts = conic.fPts;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002872 SkScalar y0 = pts[0].fY;
2873 SkScalar y2 = pts[2].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002874
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002875 int dir = 1;
2876 if (y0 > y2) {
2877 SkTSwap(y0, y2);
2878 dir = -1;
2879 }
caryclark9aacd902015-12-14 08:38:09 -08002880 if (y < y0 || y > y2) {
2881 return 0;
2882 }
caryclark9cb5d752015-12-18 04:35:24 -08002883 if (checkOnCurve(x, y, pts[0], pts[2])) {
2884 *onCurveCount += 1;
2885 return 0;
2886 }
caryclark9aacd902015-12-14 08:38:09 -08002887 if (y == y2) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002888 return 0;
2889 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002890
caryclark9aacd902015-12-14 08:38:09 -08002891 SkScalar roots[2];
2892 SkScalar A = pts[2].fY;
2893 SkScalar B = pts[1].fY * conic.fW - y * conic.fW + y;
2894 SkScalar C = pts[0].fY;
2895 A += C - 2 * B; // A = a + c - 2*(b*w - yCept*w + yCept)
2896 B -= C; // B = b*w - w * yCept + yCept - a
2897 C -= y;
2898 int n = SkFindUnitQuadRoots(A, 2 * B, C, roots);
2899 SkASSERT(n <= 1);
2900 SkScalar xt;
2901 if (0 == n) {
caryclark9cb5d752015-12-18 04:35:24 -08002902 // zero roots are returned only when y0 == y
2903 // Need [0] if dir == 1
2904 // and [2] if dir == -1
2905 xt = pts[1 - dir].fX;
caryclark9aacd902015-12-14 08:38:09 -08002906 } else {
2907 SkScalar t = roots[0];
2908 xt = conic_eval_numerator(&pts[0].fX, conic.fW, t) / conic_eval_denominator(conic.fW, t);
2909 }
2910 if (SkScalarNearlyEqual(xt, x)) {
2911 if (x != pts[2].fX || y != pts[2].fY) { // don't test end points; they're start points
2912 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08002913 return 0;
caryclark9aacd902015-12-14 08:38:09 -08002914 }
2915 }
2916 return xt < x ? dir : 0;
2917}
2918
2919static bool is_mono_quad(SkScalar y0, SkScalar y1, SkScalar y2) {
2920 // return SkScalarSignAsInt(y0 - y1) + SkScalarSignAsInt(y1 - y2) != 0;
2921 if (y0 == y1) {
2922 return true;
2923 }
2924 if (y0 < y1) {
2925 return y1 <= y2;
2926 } else {
2927 return y1 >= y2;
2928 }
2929}
2930
2931static int winding_conic(const SkPoint pts[], SkScalar x, SkScalar y, SkScalar weight,
2932 int* onCurveCount) {
2933 SkConic conic(pts, weight);
caryclark9aacd902015-12-14 08:38:09 -08002934 SkConic chopped[2];
caryclark1e1e5092016-01-06 08:27:44 -08002935 // If the data points are very large, the conic may not be monotonic but may also
2936 // fail to chop. Then, the chopper does not split the original conic in two.
caryclarke114dd62016-01-06 08:15:44 -08002937 bool isMono = is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY) || !conic.chopAtYExtrema(chopped);
2938 int w = winding_mono_conic(isMono ? conic : chopped[0], x, y, onCurveCount);
2939 if (!isMono) {
caryclark9aacd902015-12-14 08:38:09 -08002940 w += winding_mono_conic(chopped[1], x, y, onCurveCount);
2941 }
2942 return w;
2943}
2944
2945static int winding_mono_quad(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
2946 SkScalar y0 = pts[0].fY;
2947 SkScalar y2 = pts[2].fY;
2948
2949 int dir = 1;
2950 if (y0 > y2) {
2951 SkTSwap(y0, y2);
2952 dir = -1;
2953 }
2954 if (y < y0 || y > y2) {
2955 return 0;
2956 }
caryclark9cb5d752015-12-18 04:35:24 -08002957 if (checkOnCurve(x, y, pts[0], pts[2])) {
2958 *onCurveCount += 1;
2959 return 0;
2960 }
caryclark9aacd902015-12-14 08:38:09 -08002961 if (y == y2) {
caryclark9aacd902015-12-14 08:38:09 -08002962 return 0;
2963 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002964 // bounds check on X (not required. is it faster?)
2965#if 0
2966 if (pts[0].fX > x && pts[1].fX > x && pts[2].fX > x) {
2967 return 0;
2968 }
2969#endif
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002970
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002971 SkScalar roots[2];
2972 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
2973 2 * (pts[1].fY - pts[0].fY),
2974 pts[0].fY - y,
2975 roots);
2976 SkASSERT(n <= 1);
2977 SkScalar xt;
2978 if (0 == n) {
caryclark9cb5d752015-12-18 04:35:24 -08002979 // zero roots are returned only when y0 == y
2980 // Need [0] if dir == 1
2981 // and [2] if dir == -1
2982 xt = pts[1 - dir].fX;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002983 } else {
2984 SkScalar t = roots[0];
2985 SkScalar C = pts[0].fX;
2986 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
2987 SkScalar B = 2 * (pts[1].fX - C);
2988 xt = SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C);
2989 }
caryclark9aacd902015-12-14 08:38:09 -08002990 if (SkScalarNearlyEqual(xt, x)) {
2991 if (x != pts[2].fX || y != pts[2].fY) { // don't test end points; they're start points
2992 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08002993 return 0;
caryclark9aacd902015-12-14 08:38:09 -08002994 }
2995 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002996 return xt < x ? dir : 0;
2997}
2998
caryclark9aacd902015-12-14 08:38:09 -08002999static int winding_quad(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003000 SkPoint dst[5];
3001 int n = 0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003002
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003003 if (!is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY)) {
3004 n = SkChopQuadAtYExtrema(pts, dst);
3005 pts = dst;
3006 }
caryclark9aacd902015-12-14 08:38:09 -08003007 int w = winding_mono_quad(pts, x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003008 if (n > 0) {
caryclark9aacd902015-12-14 08:38:09 -08003009 w += winding_mono_quad(&pts[2], x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003010 }
3011 return w;
3012}
3013
caryclark9aacd902015-12-14 08:38:09 -08003014static int winding_line(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003015 SkScalar x0 = pts[0].fX;
3016 SkScalar y0 = pts[0].fY;
3017 SkScalar x1 = pts[1].fX;
3018 SkScalar y1 = pts[1].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003019
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003020 SkScalar dy = y1 - y0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003021
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003022 int dir = 1;
3023 if (y0 > y1) {
3024 SkTSwap(y0, y1);
3025 dir = -1;
3026 }
caryclark9aacd902015-12-14 08:38:09 -08003027 if (y < y0 || y > y1) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003028 return 0;
3029 }
caryclark9cb5d752015-12-18 04:35:24 -08003030 if (checkOnCurve(x, y, pts[0], pts[1])) {
3031 *onCurveCount += 1;
3032 return 0;
3033 }
3034 if (y == y1) {
caryclark9aacd902015-12-14 08:38:09 -08003035 return 0;
3036 }
3037 SkScalar cross = SkScalarMul(x1 - x0, y - pts[0].fY) - SkScalarMul(dy, x - x0);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003038
caryclark9aacd902015-12-14 08:38:09 -08003039 if (!cross) {
fsc91065d2015-12-17 09:03:27 -08003040 // zero cross means the point is on the line, and since the case where
3041 // y of the query point is at the end point is handled above, we can be
3042 // sure that we're on the line (excluding the end point) here
caryclark9cb5d752015-12-18 04:35:24 -08003043 if (x != x1 || y != pts[1].fY) {
3044 *onCurveCount += 1;
3045 }
caryclark9aacd902015-12-14 08:38:09 -08003046 dir = 0;
3047 } else if (SkScalarSignAsInt(cross) == dir) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003048 dir = 0;
3049 }
3050 return dir;
3051}
3052
caryclark9aacd902015-12-14 08:38:09 -08003053static void tangent_cubic(const SkPoint pts[], SkScalar x, SkScalar y,
3054 SkTDArray<SkVector>* tangents) {
3055 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)
3056 && !between(pts[2].fY, y, pts[3].fY)) {
3057 return;
3058 }
3059 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)
3060 && !between(pts[2].fX, x, pts[3].fX)) {
3061 return;
3062 }
3063 SkPoint dst[10];
3064 int n = SkChopCubicAtYExtrema(pts, dst);
3065 for (int i = 0; i <= n; ++i) {
3066 SkPoint* c = &dst[i * 3];
3067 SkScalar t;
mbarbella276e6332016-05-31 14:44:01 -07003068 if (!SkCubicClipper::ChopMonoAtY(c, y, &t)) {
mbarbella99600d02016-06-01 15:39:47 -07003069 continue;
mbarbella276e6332016-05-31 14:44:01 -07003070 }
caryclark9aacd902015-12-14 08:38:09 -08003071 SkScalar xt = eval_cubic_pts(c[0].fX, c[1].fX, c[2].fX, c[3].fX, t);
3072 if (!SkScalarNearlyEqual(x, xt)) {
3073 continue;
3074 }
3075 SkVector tangent;
3076 SkEvalCubicAt(c, t, nullptr, &tangent, nullptr);
3077 tangents->push(tangent);
3078 }
3079}
3080
3081static void tangent_conic(const SkPoint pts[], SkScalar x, SkScalar y, SkScalar w,
3082 SkTDArray<SkVector>* tangents) {
3083 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)) {
3084 return;
3085 }
3086 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)) {
3087 return;
3088 }
3089 SkScalar roots[2];
3090 SkScalar A = pts[2].fY;
3091 SkScalar B = pts[1].fY * w - y * w + y;
3092 SkScalar C = pts[0].fY;
3093 A += C - 2 * B; // A = a + c - 2*(b*w - yCept*w + yCept)
3094 B -= C; // B = b*w - w * yCept + yCept - a
3095 C -= y;
3096 int n = SkFindUnitQuadRoots(A, 2 * B, C, roots);
3097 for (int index = 0; index < n; ++index) {
3098 SkScalar t = roots[index];
3099 SkScalar xt = conic_eval_numerator(&pts[0].fX, w, t) / conic_eval_denominator(w, t);
3100 if (!SkScalarNearlyEqual(x, xt)) {
3101 continue;
3102 }
3103 SkConic conic(pts, w);
3104 tangents->push(conic.evalTangentAt(t));
3105 }
3106}
3107
3108static void tangent_quad(const SkPoint pts[], SkScalar x, SkScalar y,
3109 SkTDArray<SkVector>* tangents) {
3110 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)) {
3111 return;
3112 }
3113 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)) {
3114 return;
3115 }
3116 SkScalar roots[2];
3117 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
3118 2 * (pts[1].fY - pts[0].fY),
3119 pts[0].fY - y,
3120 roots);
3121 for (int index = 0; index < n; ++index) {
3122 SkScalar t = roots[index];
3123 SkScalar C = pts[0].fX;
3124 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
3125 SkScalar B = 2 * (pts[1].fX - C);
3126 SkScalar xt = (A * t + B) * t + C;
3127 if (!SkScalarNearlyEqual(x, xt)) {
3128 continue;
3129 }
3130 tangents->push(SkEvalQuadTangentAt(pts, t));
3131 }
3132}
3133
3134static void tangent_line(const SkPoint pts[], SkScalar x, SkScalar y,
3135 SkTDArray<SkVector>* tangents) {
3136 SkScalar y0 = pts[0].fY;
3137 SkScalar y1 = pts[1].fY;
3138 if (!between(y0, y, y1)) {
3139 return;
3140 }
3141 SkScalar x0 = pts[0].fX;
3142 SkScalar x1 = pts[1].fX;
3143 if (!between(x0, x, x1)) {
3144 return;
3145 }
3146 SkScalar dx = x1 - x0;
3147 SkScalar dy = y1 - y0;
3148 if (!SkScalarNearlyEqual((x - x0) * dy, dx * (y - y0))) {
3149 return;
3150 }
3151 SkVector v;
3152 v.set(dx, dy);
3153 tangents->push(v);
3154}
3155
reed@google.com4db592c2013-10-30 17:39:43 +00003156static bool contains_inclusive(const SkRect& r, SkScalar x, SkScalar y) {
3157 return r.fLeft <= x && x <= r.fRight && r.fTop <= y && y <= r.fBottom;
3158}
3159
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003160bool SkPath::contains(SkScalar x, SkScalar y) const {
3161 bool isInverse = this->isInverseFillType();
3162 if (this->isEmpty()) {
3163 return isInverse;
3164 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003165
reed@google.com4db592c2013-10-30 17:39:43 +00003166 if (!contains_inclusive(this->getBounds(), x, y)) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003167 return isInverse;
3168 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003169
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003170 SkPath::Iter iter(*this, true);
3171 bool done = false;
3172 int w = 0;
caryclark9aacd902015-12-14 08:38:09 -08003173 int onCurveCount = 0;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003174 do {
3175 SkPoint pts[4];
3176 switch (iter.next(pts, false)) {
3177 case SkPath::kMove_Verb:
3178 case SkPath::kClose_Verb:
3179 break;
3180 case SkPath::kLine_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003181 w += winding_line(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003182 break;
3183 case SkPath::kQuad_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003184 w += winding_quad(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003185 break;
reed@google.com277c3f82013-05-31 15:17:50 +00003186 case SkPath::kConic_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003187 w += winding_conic(pts, x, y, iter.conicWeight(), &onCurveCount);
reed@google.com277c3f82013-05-31 15:17:50 +00003188 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003189 case SkPath::kCubic_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003190 w += winding_cubic(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003191 break;
3192 case SkPath::kDone_Verb:
3193 done = true;
3194 break;
reed@google.com277c3f82013-05-31 15:17:50 +00003195 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003196 } while (!done);
caryclark9aacd902015-12-14 08:38:09 -08003197 bool evenOddFill = SkPath::kEvenOdd_FillType == this->getFillType()
3198 || SkPath::kInverseEvenOdd_FillType == this->getFillType();
3199 if (evenOddFill) {
3200 w &= 1;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003201 }
caryclark9aacd902015-12-14 08:38:09 -08003202 if (w) {
3203 return !isInverse;
3204 }
3205 if (onCurveCount <= 1) {
3206 return SkToBool(onCurveCount) ^ isInverse;
3207 }
3208 if ((onCurveCount & 1) || evenOddFill) {
3209 return SkToBool(onCurveCount & 1) ^ isInverse;
3210 }
halcanary9d524f22016-03-29 09:03:52 -07003211 // If the point touches an even number of curves, and the fill is winding, check for
caryclark9aacd902015-12-14 08:38:09 -08003212 // coincidence. Count coincidence as places where the on curve points have identical tangents.
3213 iter.setPath(*this, true);
caryclark9cb5d752015-12-18 04:35:24 -08003214 done = false;
caryclark9aacd902015-12-14 08:38:09 -08003215 SkTDArray<SkVector> tangents;
3216 do {
3217 SkPoint pts[4];
3218 int oldCount = tangents.count();
3219 switch (iter.next(pts, false)) {
3220 case SkPath::kMove_Verb:
3221 case SkPath::kClose_Verb:
3222 break;
3223 case SkPath::kLine_Verb:
3224 tangent_line(pts, x, y, &tangents);
3225 break;
3226 case SkPath::kQuad_Verb:
3227 tangent_quad(pts, x, y, &tangents);
3228 break;
3229 case SkPath::kConic_Verb:
3230 tangent_conic(pts, x, y, iter.conicWeight(), &tangents);
3231 break;
3232 case SkPath::kCubic_Verb:
3233 tangent_cubic(pts, x, y, &tangents);
3234 break;
3235 case SkPath::kDone_Verb:
3236 done = true;
3237 break;
3238 }
3239 if (tangents.count() > oldCount) {
3240 int last = tangents.count() - 1;
3241 const SkVector& tangent = tangents[last];
3242 if (SkScalarNearlyZero(tangent.lengthSqd())) {
3243 tangents.remove(last);
3244 } else {
3245 for (int index = 0; index < last; ++index) {
3246 const SkVector& test = tangents[index];
3247 if (SkScalarNearlyZero(test.cross(tangent))
caryclark9cb5d752015-12-18 04:35:24 -08003248 && SkScalarSignAsInt(tangent.fX * test.fX) <= 0
3249 && SkScalarSignAsInt(tangent.fY * test.fY) <= 0) {
caryclark9aacd902015-12-14 08:38:09 -08003250 tangents.remove(last);
3251 tangents.removeShuffle(index);
3252 break;
3253 }
3254 }
3255 }
3256 }
3257 } while (!done);
3258 return SkToBool(tangents.count()) ^ isInverse;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003259}
fmalitaaa0df4e2015-12-01 09:13:23 -08003260
3261int SkPath::ConvertConicToQuads(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2,
3262 SkScalar w, SkPoint pts[], int pow2) {
3263 const SkConic conic(p0, p1, p2, w);
3264 return conic.chopIntoQuadsPOW2(pts, pow2);
3265}
bsalomonedc743a2016-06-01 09:42:31 -07003266
3267bool SkPathPriv::IsSimpleClosedRect(const SkPath& path, SkRect* rect, SkPath::Direction* direction,
3268 unsigned* start) {
3269 if (path.getSegmentMasks() != SkPath::kLine_SegmentMask) {
3270 return false;
3271 }
3272 SkPath::RawIter iter(path);
3273 SkPoint verbPts[4];
3274 SkPath::Verb v;
3275 SkPoint rectPts[5];
3276 int rectPtCnt = 0;
3277 while ((v = iter.next(verbPts)) != SkPath::kDone_Verb) {
3278 switch (v) {
3279 case SkPath::kMove_Verb:
3280 if (0 != rectPtCnt) {
3281 return false;
3282 }
3283 rectPts[0] = verbPts[0];
3284 ++rectPtCnt;
3285 break;
3286 case SkPath::kLine_Verb:
3287 if (5 == rectPtCnt) {
3288 return false;
3289 }
3290 rectPts[rectPtCnt] = verbPts[1];
3291 ++rectPtCnt;
3292 break;
3293 case SkPath::kClose_Verb:
3294 if (4 == rectPtCnt) {
3295 rectPts[4] = rectPts[0];
3296 rectPtCnt = 5;
3297 }
3298 break;
3299 default:
3300 return false;
3301 }
3302 }
3303 if (rectPtCnt < 5) {
3304 return false;
3305 }
3306 if (rectPts[0] != rectPts[4]) {
3307 return false;
3308 }
bsalomon057ae8a2016-07-24 05:37:26 -07003309 // Check for two cases of rectangles: pts 0 and 3 form a vertical edge or a horizontal edge (
3310 // and pts 1 and 2 the opposite vertical or horizontal edge).
3311 bool vec03IsVertical;
3312 if (rectPts[0].fX == rectPts[3].fX && rectPts[1].fX == rectPts[2].fX &&
3313 rectPts[0].fY == rectPts[1].fY && rectPts[3].fY == rectPts[2].fY) {
3314 // Make sure it has non-zero width and height
3315 if (rectPts[0].fX == rectPts[1].fX || rectPts[0].fY == rectPts[3].fY) {
bsalomonedc743a2016-06-01 09:42:31 -07003316 return false;
3317 }
bsalomon057ae8a2016-07-24 05:37:26 -07003318 vec03IsVertical = true;
3319 } else if (rectPts[0].fY == rectPts[3].fY && rectPts[1].fY == rectPts[2].fY &&
3320 rectPts[0].fX == rectPts[1].fX && rectPts[3].fX == rectPts[2].fX) {
3321 // Make sure it has non-zero width and height
3322 if (rectPts[0].fY == rectPts[1].fY || rectPts[0].fX == rectPts[3].fX) {
3323 return false;
3324 }
3325 vec03IsVertical = false;
3326 } else {
bsalomonedc743a2016-06-01 09:42:31 -07003327 return false;
3328 }
bsalomon057ae8a2016-07-24 05:37:26 -07003329 // Set sortFlags so that it has the low bit set if pt index 0 is on right edge and second bit
3330 // set if it is on the bottom edge.
3331 unsigned sortFlags =
3332 ((rectPts[0].fX < rectPts[2].fX) ? 0b00 : 0b01) |
3333 ((rectPts[0].fY < rectPts[2].fY) ? 0b00 : 0b10);
3334 switch (sortFlags) {
3335 case 0b00:
3336 rect->set(rectPts[0].fX, rectPts[0].fY, rectPts[2].fX, rectPts[2].fY);
3337 *direction = vec03IsVertical ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
3338 *start = 0;
3339 break;
3340 case 0b01:
3341 rect->set(rectPts[2].fX, rectPts[0].fY, rectPts[0].fX, rectPts[2].fY);
3342 *direction = vec03IsVertical ? SkPath::kCCW_Direction : SkPath::kCW_Direction;
3343 *start = 1;
3344 break;
3345 case 0b10:
3346 rect->set(rectPts[0].fX, rectPts[2].fY, rectPts[2].fX, rectPts[0].fY);
3347 *direction = vec03IsVertical ? SkPath::kCCW_Direction : SkPath::kCW_Direction;
3348 *start = 3;
3349 break;
3350 case 0b11:
3351 rect->set(rectPts[2].fX, rectPts[2].fY, rectPts[0].fX, rectPts[0].fY);
3352 *direction = vec03IsVertical ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
3353 *start = 2;
3354 break;
bsalomonedc743a2016-06-01 09:42:31 -07003355 }
bsalomonedc743a2016-06-01 09:42:31 -07003356 return true;
3357}
bsalomon21af9ca2016-08-25 12:29:23 -07003358
3359void SkPathPriv::CreateDrawArcPath(SkPath* path, const SkRect& oval, SkScalar startAngle,
3360 SkScalar sweepAngle, bool useCenter, bool isFillNoPathEffect) {
3361 SkASSERT(!oval.isEmpty());
3362 SkASSERT(sweepAngle);
3363
3364 path->reset();
3365 path->setIsVolatile(true);
3366 path->setFillType(SkPath::kWinding_FillType);
3367 if (isFillNoPathEffect && SkScalarAbs(sweepAngle) >= 360.f) {
3368 path->addOval(oval);
3369 return;
3370 }
3371 if (useCenter) {
3372 path->moveTo(oval.centerX(), oval.centerY());
3373 }
3374 // Arc to mods at 360 and drawArc is not supposed to.
3375 bool forceMoveTo = !useCenter;
3376 while (sweepAngle <= -360.f) {
3377 path->arcTo(oval, startAngle, -180.f, forceMoveTo);
3378 startAngle -= 180.f;
3379 path->arcTo(oval, startAngle, -180.f, false);
3380 startAngle -= 180.f;
3381 forceMoveTo = false;
3382 sweepAngle += 360.f;
3383 }
3384 while (sweepAngle >= 360.f) {
3385 path->arcTo(oval, startAngle, 180.f, forceMoveTo);
3386 startAngle += 180.f;
3387 path->arcTo(oval, startAngle, 180.f, false);
3388 startAngle += 180.f;
3389 forceMoveTo = false;
3390 sweepAngle -= 360.f;
3391 }
3392 path->arcTo(oval, startAngle, sweepAngle, forceMoveTo);
3393 if (useCenter) {
3394 path->close();
3395 }
3396}
Mike Reed0d7dac82017-02-02 17:45:56 -08003397
3398///////////////////////////////////////////////////////////////////////////////////////////////////
3399#include "SkNx.h"
3400
3401static int compute_quad_extremas(const SkPoint src[3], SkPoint extremas[3]) {
3402 SkScalar ts[2];
3403 int n = SkFindQuadExtrema(src[0].fX, src[1].fX, src[2].fX, ts);
3404 n += SkFindQuadExtrema(src[0].fY, src[1].fY, src[2].fY, &ts[n]);
3405 SkASSERT(n >= 0 && n <= 2);
3406 for (int i = 0; i < n; ++i) {
3407 extremas[i] = SkEvalQuadAt(src, ts[i]);
3408 }
3409 extremas[n] = src[2];
3410 return n + 1;
3411}
3412
3413static int compute_conic_extremas(const SkPoint src[3], SkScalar w, SkPoint extremas[3]) {
3414 SkConic conic(src[0], src[1], src[2], w);
3415 SkScalar ts[2];
3416 int n = conic.findXExtrema(ts);
3417 n += conic.findYExtrema(&ts[n]);
3418 SkASSERT(n >= 0 && n <= 2);
3419 for (int i = 0; i < n; ++i) {
3420 extremas[i] = conic.evalAt(ts[i]);
3421 }
3422 extremas[n] = src[2];
3423 return n + 1;
3424}
3425
3426static int compute_cubic_extremas(const SkPoint src[3], SkPoint extremas[5]) {
3427 SkScalar ts[4];
3428 int n = SkFindCubicExtrema(src[0].fX, src[1].fX, src[2].fX, src[3].fX, ts);
3429 n += SkFindCubicExtrema(src[0].fY, src[1].fY, src[2].fY, src[3].fY, &ts[n]);
3430 SkASSERT(n >= 0 && n <= 4);
3431 for (int i = 0; i < n; ++i) {
3432 SkEvalCubicAt(src, ts[i], &extremas[i], nullptr, nullptr);
3433 }
3434 extremas[n] = src[3];
3435 return n + 1;
3436}
3437
3438bool SkPathPriv::ComputeTightBounds(const SkPath& path, SkRect* bounds) {
3439 if (0 == path.countVerbs()) {
3440 return false;
3441 }
3442
3443 if (path.getSegmentMasks() == SkPath::kLine_SegmentMask) {
3444 *bounds = path.getBounds();
3445 return true;
3446 }
3447
3448 SkPoint extremas[5]; // big enough to hold worst-case curve type (cubic) extremas + 1
3449 SkPoint pts[4];
3450 SkPath::RawIter iter(path);
3451
3452 // initial with the first MoveTo, so we don't have to check inside the switch
3453 Sk2s min, max;
3454 min = max = from_point(path.getPoint(0));
3455 for (;;) {
3456 int count = 0;
3457 switch (iter.next(pts)) {
3458 case SkPath::kMove_Verb:
3459 extremas[0] = pts[0];
3460 count = 1;
3461 break;
3462 case SkPath::kLine_Verb:
3463 extremas[0] = pts[1];
3464 count = 1;
3465 break;
3466 case SkPath::kQuad_Verb:
3467 count = compute_quad_extremas(pts, extremas);
3468 break;
3469 case SkPath::kConic_Verb:
3470 count = compute_conic_extremas(pts, iter.conicWeight(), extremas);
3471 break;
3472 case SkPath::kCubic_Verb:
3473 count = compute_cubic_extremas(pts, extremas);
3474 break;
3475 case SkPath::kClose_Verb:
3476 break;
3477 case SkPath::kDone_Verb:
3478 goto DONE;
3479 }
3480 for (int i = 0; i < count; ++i) {
3481 Sk2s tmp = from_point(extremas[i]);
3482 min = Sk2s::Min(min, tmp);
3483 max = Sk2s::Max(max, tmp);
3484 }
3485 }
3486DONE:
3487 min.store((SkPoint*)&bounds->fLeft);
3488 max.store((SkPoint*)&bounds->fRight);
3489 return true;
3490}