blob: 5cd6316c7ff3556e8aad71efd11785f642f6372a [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"
humper@google.com75e3ca12013-04-08 21:44:11 +000011#include "SkErrorInternals.h"
reed220f9262014-12-17 08:21:04 -080012#include "SkGeometry.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000013#include "SkMath.h"
reed026beb52015-06-10 14:23:15 -070014#include "SkPathPriv.h"
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +000015#include "SkPathRef.h"
reed@google.com4ed0fb72012-12-12 20:48:18 +000016#include "SkRRect.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000017
18////////////////////////////////////////////////////////////////////////////
19
reed@google.com3563c9e2011-11-14 19:34:57 +000020/**
21 * Path.bounds is defined to be the bounds of all the control points.
22 * If we called bounds.join(r) we would skip r if r was empty, which breaks
23 * our promise. Hence we have a custom joiner that doesn't look at emptiness
24 */
25static void joinNoEmptyChecks(SkRect* dst, const SkRect& src) {
26 dst->fLeft = SkMinScalar(dst->fLeft, src.fLeft);
27 dst->fTop = SkMinScalar(dst->fTop, src.fTop);
28 dst->fRight = SkMaxScalar(dst->fRight, src.fRight);
29 dst->fBottom = SkMaxScalar(dst->fBottom, src.fBottom);
30}
31
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000032static bool is_degenerate(const SkPath& path) {
33 SkPath::Iter iter(path, false);
34 SkPoint pts[4];
35 return SkPath::kDone_Verb == iter.next(pts);
36}
37
bsalomon@google.com30c174b2012-11-13 14:36:42 +000038class SkAutoDisableDirectionCheck {
39public:
40 SkAutoDisableDirectionCheck(SkPath* path) : fPath(path) {
herb9f4dbca2015-09-28 11:05:47 -070041 fSaved = static_cast<SkPathPriv::FirstDirection>(fPath->fFirstDirection.load());
bsalomon@google.com30c174b2012-11-13 14:36:42 +000042 }
43
44 ~SkAutoDisableDirectionCheck() {
reed026beb52015-06-10 14:23:15 -070045 fPath->fFirstDirection = fSaved;
bsalomon@google.com30c174b2012-11-13 14:36:42 +000046 }
47
48private:
reed026beb52015-06-10 14:23:15 -070049 SkPath* fPath;
50 SkPathPriv::FirstDirection fSaved;
bsalomon@google.com30c174b2012-11-13 14:36:42 +000051};
commit-bot@chromium.orge61a86c2013-11-18 16:03:59 +000052#define SkAutoDisableDirectionCheck(...) SK_REQUIRE_LOCAL_VAR(SkAutoDisableDirectionCheck)
bsalomon@google.com30c174b2012-11-13 14:36:42 +000053
reed@android.com8a1c16f2008-12-17 15:59:43 +000054/* This guy's constructor/destructor bracket a path editing operation. It is
55 used when we know the bounds of the amount we are going to add to the path
56 (usually a new contour, but not required).
reed@google.comabf15c12011-01-18 20:35:51 +000057
reed@android.com8a1c16f2008-12-17 15:59:43 +000058 It captures some state about the path up front (i.e. if it already has a
robertphillips@google.comca0c8382013-09-26 12:18:23 +000059 cached bounds), and then if it can, it updates the cache bounds explicitly,
reed@android.comd252db02009-04-01 18:31:44 +000060 avoiding the need to revisit all of the points in getBounds().
reed@google.comabf15c12011-01-18 20:35:51 +000061
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000062 It also notes if the path was originally degenerate, and if so, sets
63 isConvex to true. Thus it can only be used if the contour being added is
robertphillips@google.com466310d2013-12-03 16:43:54 +000064 convex.
reed@android.com8a1c16f2008-12-17 15:59:43 +000065 */
66class SkAutoPathBoundsUpdate {
67public:
68 SkAutoPathBoundsUpdate(SkPath* path, const SkRect& r) : fRect(r) {
69 this->init(path);
70 }
71
72 SkAutoPathBoundsUpdate(SkPath* path, SkScalar left, SkScalar top,
73 SkScalar right, SkScalar bottom) {
74 fRect.set(left, top, right, bottom);
75 this->init(path);
76 }
reed@google.comabf15c12011-01-18 20:35:51 +000077
reed@android.com8a1c16f2008-12-17 15:59:43 +000078 ~SkAutoPathBoundsUpdate() {
reed@google.com44699382013-10-31 17:28:30 +000079 fPath->setConvexity(fDegenerate ? SkPath::kConvex_Convexity
80 : SkPath::kUnknown_Convexity);
robertphillips@google.comca0c8382013-09-26 12:18:23 +000081 if (fEmpty || fHasValidBounds) {
82 fPath->setBounds(fRect);
reed@android.com8a1c16f2008-12-17 15:59:43 +000083 }
84 }
reed@google.comabf15c12011-01-18 20:35:51 +000085
reed@android.com8a1c16f2008-12-17 15:59:43 +000086private:
reed@android.com6b82d1a2009-06-03 02:35:01 +000087 SkPath* fPath;
88 SkRect fRect;
robertphillips@google.comca0c8382013-09-26 12:18:23 +000089 bool fHasValidBounds;
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000090 bool fDegenerate;
reed@android.com6b82d1a2009-06-03 02:35:01 +000091 bool fEmpty;
reed@google.comabf15c12011-01-18 20:35:51 +000092
reed@android.com6b82d1a2009-06-03 02:35:01 +000093 void init(SkPath* path) {
robertphillips@google.comca0c8382013-09-26 12:18:23 +000094 // Cannot use fRect for our bounds unless we know it is sorted
95 fRect.sort();
reed@android.com8a1c16f2008-12-17 15:59:43 +000096 fPath = path;
reed@google.coma8790de2012-10-24 21:04:04 +000097 // Mark the path's bounds as dirty if (1) they are, or (2) the path
98 // is non-finite, and therefore its bounds are not meaningful
robertphillips@google.comca0c8382013-09-26 12:18:23 +000099 fHasValidBounds = path->hasComputedBounds() && path->isFinite();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000100 fEmpty = path->isEmpty();
robertphillips@google.comca0c8382013-09-26 12:18:23 +0000101 if (fHasValidBounds && !fEmpty) {
102 joinNoEmptyChecks(&fRect, fPath->getBounds());
103 }
104 fDegenerate = is_degenerate(*path);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000105 }
106};
commit-bot@chromium.orge61a86c2013-11-18 16:03:59 +0000107#define SkAutoPathBoundsUpdate(...) SK_REQUIRE_LOCAL_VAR(SkAutoPathBoundsUpdate)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000108
reed@android.com8a1c16f2008-12-17 15:59:43 +0000109////////////////////////////////////////////////////////////////////////////
110
111/*
112 Stores the verbs and points as they are given to us, with exceptions:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000113 - we only record "Close" if it was immediately preceeded by Move | Line | Quad | Cubic
reed@android.com8a1c16f2008-12-17 15:59:43 +0000114 - we insert a Move(0,0) if Line | Quad | Cubic is our first command
115
116 The iterator does more cleanup, especially if forceClose == true
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000117 1. If we encounter degenerate segments, remove them
118 2. if we encounter Close, return a cons'd up Line() first (if the curr-pt != start-pt)
119 3. if we encounter Move without a preceeding Close, and forceClose is true, goto #2
120 4. if we encounter Line | Quad | Cubic after Close, cons up a Move
reed@android.com8a1c16f2008-12-17 15:59:43 +0000121*/
122
123////////////////////////////////////////////////////////////////////////////
124
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000125// flag to require a moveTo if we begin with something else, like lineTo etc.
126#define INITIAL_LASTMOVETOINDEX_VALUE ~0
127
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000128SkPath::SkPath()
djsollen523cda32015-02-17 08:06:32 -0800129 : fPathRef(SkPathRef::CreateEmpty()) {
bungeman@google.coma5809a32013-06-21 15:13:34 +0000130 this->resetFields();
jvanverthb3eb6872014-10-24 07:12:51 -0700131 fIsVolatile = false;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000132}
133
134void SkPath::resetFields() {
135 //fPathRef is assumed to have been emptied by the caller.
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000136 fLastMoveToIndex = INITIAL_LASTMOVETOINDEX_VALUE;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000137 fFillType = kWinding_FillType;
reed@google.com04863fa2011-05-15 04:08:24 +0000138 fConvexity = kUnknown_Convexity;
reed026beb52015-06-10 14:23:15 -0700139 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000140
141 // We don't touch Android's fSourcePath. It's used to track texture garbage collection, so we
halcanary96fcdcc2015-08-27 07:41:13 -0700142 // don't want to muck with it if it's been set to something non-nullptr.
reed@android.com6b82d1a2009-06-03 02:35:01 +0000143}
reed@android.com8a1c16f2008-12-17 15:59:43 +0000144
bungeman@google.coma5809a32013-06-21 15:13:34 +0000145SkPath::SkPath(const SkPath& that)
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000146 : fPathRef(SkRef(that.fPathRef.get())) {
bungeman@google.coma5809a32013-06-21 15:13:34 +0000147 this->copyFields(that);
148 SkDEBUGCODE(that.validate();)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000149}
150
151SkPath::~SkPath() {
152 SkDEBUGCODE(this->validate();)
153}
154
bungeman@google.coma5809a32013-06-21 15:13:34 +0000155SkPath& SkPath::operator=(const SkPath& that) {
156 SkDEBUGCODE(that.validate();)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000157
bungeman@google.coma5809a32013-06-21 15:13:34 +0000158 if (this != &that) {
159 fPathRef.reset(SkRef(that.fPathRef.get()));
160 this->copyFields(that);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000161 }
162 SkDEBUGCODE(this->validate();)
163 return *this;
164}
165
bungeman@google.coma5809a32013-06-21 15:13:34 +0000166void SkPath::copyFields(const SkPath& that) {
167 //fPathRef is assumed to have been set by the caller.
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000168 fLastMoveToIndex = that.fLastMoveToIndex;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000169 fFillType = that.fFillType;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000170 fConvexity = that.fConvexity;
herb9f4dbca2015-09-28 11:05:47 -0700171 // Simulate fFirstDirection = that.fFirstDirection;
172 fFirstDirection.store(that.fFirstDirection.load());
jvanverthb3eb6872014-10-24 07:12:51 -0700173 fIsVolatile = that.fIsVolatile;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000174}
175
djsollen@google.com9c1a9672013-08-09 13:49:13 +0000176bool operator==(const SkPath& a, const SkPath& b) {
reed@android.com6b82d1a2009-06-03 02:35:01 +0000177 // note: don't need to look at isConvex or bounds, since just comparing the
178 // raw data is sufficient.
reed@android.com3abec1d2009-03-02 05:36:20 +0000179 return &a == &b ||
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000180 (a.fFillType == b.fFillType && *a.fPathRef.get() == *b.fPathRef.get());
reed@android.com3abec1d2009-03-02 05:36:20 +0000181}
182
bungeman@google.coma5809a32013-06-21 15:13:34 +0000183void SkPath::swap(SkPath& that) {
bungeman@google.coma5809a32013-06-21 15:13:34 +0000184 if (this != &that) {
bungeman77a53de2015-10-01 12:28:49 -0700185 fPathRef.swap(that.fPathRef);
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000186 SkTSwap<int>(fLastMoveToIndex, that.fLastMoveToIndex);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000187 SkTSwap<uint8_t>(fFillType, that.fFillType);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000188 SkTSwap<uint8_t>(fConvexity, that.fConvexity);
herb9f4dbca2015-09-28 11:05:47 -0700189 // Simulate SkTSwap<uint8_t>(fFirstDirection, that.fFirstDirection);
190 uint8_t temp = fFirstDirection;
191 fFirstDirection.store(that.fFirstDirection.load());
192 that.fFirstDirection.store(temp);
jvanverthb3eb6872014-10-24 07:12:51 -0700193 SkTSwap<SkBool8>(fIsVolatile, that.fIsVolatile);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000194 }
195}
196
caryclark8e7b19d2016-02-18 04:11:48 -0800197bool SkPath::isInterpolatable(const SkPath& compare) const {
198 int count = fPathRef->countVerbs();
199 if (count != compare.fPathRef->countVerbs()) {
200 return false;
201 }
202 if (!count) {
203 return true;
204 }
205 if (memcmp(fPathRef->verbsMemBegin(), compare.fPathRef->verbsMemBegin(),
206 count)) {
207 return false;
208 }
caryclarkdcd1fcc2016-02-18 09:00:01 -0800209 return !fPathRef->countWeights() ||
210 !SkToBool(memcmp(fPathRef->conicWeights(), compare.fPathRef->conicWeights(),
caryclark8e7b19d2016-02-18 04:11:48 -0800211 fPathRef->countWeights() * sizeof(*fPathRef->conicWeights())));
212}
213
214bool SkPath::interpolate(const SkPath& ending, SkScalar weight, SkPath* out) const {
215 int verbCount = fPathRef->countVerbs();
216 if (verbCount != ending.fPathRef->countVerbs()) {
217 return false;
218 }
caryclarka1105382016-02-18 06:13:25 -0800219 if (!verbCount) {
220 return true;
221 }
caryclark8e7b19d2016-02-18 04:11:48 -0800222 out->reset();
223 out->addPath(*this);
224 fPathRef->interpolate(*ending.fPathRef, weight, out->fPathRef);
225 return true;
226}
227
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000228static inline bool check_edge_against_rect(const SkPoint& p0,
229 const SkPoint& p1,
230 const SkRect& rect,
reed026beb52015-06-10 14:23:15 -0700231 SkPathPriv::FirstDirection dir) {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000232 const SkPoint* edgeBegin;
233 SkVector v;
reed026beb52015-06-10 14:23:15 -0700234 if (SkPathPriv::kCW_FirstDirection == dir) {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000235 v = p1 - p0;
236 edgeBegin = &p0;
237 } else {
238 v = p0 - p1;
239 edgeBegin = &p1;
240 }
241 if (v.fX || v.fY) {
242 // check the cross product of v with the vec from edgeBegin to each rect corner
243 SkScalar yL = SkScalarMul(v.fY, rect.fLeft - edgeBegin->fX);
244 SkScalar xT = SkScalarMul(v.fX, rect.fTop - edgeBegin->fY);
245 SkScalar yR = SkScalarMul(v.fY, rect.fRight - edgeBegin->fX);
246 SkScalar xB = SkScalarMul(v.fX, rect.fBottom - edgeBegin->fY);
247 if ((xT < yL) || (xT < yR) || (xB < yL) || (xB < yR)) {
248 return false;
249 }
250 }
251 return true;
252}
253
254bool SkPath::conservativelyContainsRect(const SkRect& rect) const {
255 // This only handles non-degenerate convex paths currently.
256 if (kConvex_Convexity != this->getConvexity()) {
257 return false;
258 }
skia.committer@gmail.comcec8de62012-11-14 02:01:22 +0000259
reed026beb52015-06-10 14:23:15 -0700260 SkPathPriv::FirstDirection direction;
261 if (!SkPathPriv::CheapComputeFirstDirection(*this, &direction)) {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000262 return false;
263 }
264
265 SkPoint firstPt;
266 SkPoint prevPt;
267 RawIter iter(*this);
268 SkPath::Verb verb;
269 SkPoint pts[4];
270 SkDEBUGCODE(int moveCnt = 0;)
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000271 SkDEBUGCODE(int segmentCount = 0;)
272 SkDEBUGCODE(int closeCount = 0;)
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000273
274 while ((verb = iter.next(pts)) != kDone_Verb) {
275 int nextPt = -1;
276 switch (verb) {
277 case kMove_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000278 SkASSERT(!segmentCount && !closeCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000279 SkDEBUGCODE(++moveCnt);
280 firstPt = prevPt = pts[0];
281 break;
282 case kLine_Verb:
283 nextPt = 1;
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000284 SkASSERT(moveCnt && !closeCount);
285 SkDEBUGCODE(++segmentCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000286 break;
287 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000288 case kConic_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000289 SkASSERT(moveCnt && !closeCount);
290 SkDEBUGCODE(++segmentCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000291 nextPt = 2;
292 break;
293 case kCubic_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000294 SkASSERT(moveCnt && !closeCount);
295 SkDEBUGCODE(++segmentCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000296 nextPt = 3;
297 break;
298 case kClose_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000299 SkDEBUGCODE(++closeCount;)
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000300 break;
301 default:
302 SkDEBUGFAIL("unknown verb");
303 }
304 if (-1 != nextPt) {
reed220f9262014-12-17 08:21:04 -0800305 if (SkPath::kConic_Verb == verb) {
306 SkConic orig;
307 orig.set(pts, iter.conicWeight());
308 SkPoint quadPts[5];
309 int count = orig.chopIntoQuadsPOW2(quadPts, 1);
djsollenf2b340f2016-01-29 08:51:04 -0800310 SkASSERT_RELEASE(2 == count);
reed220f9262014-12-17 08:21:04 -0800311
312 if (!check_edge_against_rect(quadPts[0], quadPts[2], rect, direction)) {
313 return false;
314 }
315 if (!check_edge_against_rect(quadPts[2], quadPts[4], rect, direction)) {
316 return false;
317 }
318 } else {
319 if (!check_edge_against_rect(prevPt, pts[nextPt], rect, direction)) {
320 return false;
321 }
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000322 }
323 prevPt = pts[nextPt];
324 }
325 }
326
327 return check_edge_against_rect(prevPt, firstPt, rect, direction);
328}
329
robertphillips@google.com7101abe2013-10-29 22:45:37 +0000330uint32_t SkPath::getGenerationID() const {
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000331 uint32_t genID = fPathRef->genID();
djsollen523cda32015-02-17 08:06:32 -0800332#ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000333 SkASSERT((unsigned)fFillType < (1 << (32 - kPathRefGenIDBitCnt)));
334 genID |= static_cast<uint32_t>(fFillType) << kPathRefGenIDBitCnt;
335#endif
336 return genID;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000337}
djsollen@google.come63793a2012-03-21 15:39:03 +0000338
reed@android.com8a1c16f2008-12-17 15:59:43 +0000339void SkPath::reset() {
340 SkDEBUGCODE(this->validate();)
341
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000342 fPathRef.reset(SkPathRef::CreateEmpty());
bungeman@google.coma5809a32013-06-21 15:13:34 +0000343 this->resetFields();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000344}
345
346void SkPath::rewind() {
347 SkDEBUGCODE(this->validate();)
348
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000349 SkPathRef::Rewind(&fPathRef);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000350 this->resetFields();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000351}
352
fsb1475b02016-01-20 09:51:07 -0800353bool SkPath::isLastContourClosed() const {
354 int verbCount = fPathRef->countVerbs();
355 if (0 == verbCount) {
356 return false;
357 }
358 return kClose_Verb == fPathRef->atVerb(verbCount - 1);
359}
360
reed@google.com7e6c4d12012-05-10 14:05:43 +0000361bool SkPath::isLine(SkPoint line[2]) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000362 int verbCount = fPathRef->countVerbs();
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000363
commit-bot@chromium.orga62efcc2013-08-05 13:23:13 +0000364 if (2 == verbCount) {
365 SkASSERT(kMove_Verb == fPathRef->atVerb(0));
366 if (kLine_Verb == fPathRef->atVerb(1)) {
367 SkASSERT(2 == fPathRef->countPoints());
reed@google.com7e6c4d12012-05-10 14:05:43 +0000368 if (line) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000369 const SkPoint* pts = fPathRef->points();
reed@google.com7e6c4d12012-05-10 14:05:43 +0000370 line[0] = pts[0];
371 line[1] = pts[1];
372 }
373 return true;
374 }
375 }
376 return false;
377}
378
caryclark@google.comf1316942011-07-26 19:54:45 +0000379/*
380 Determines if path is a rect by keeping track of changes in direction
381 and looking for a loop either clockwise or counterclockwise.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000382
caryclark@google.comf1316942011-07-26 19:54:45 +0000383 The direction is computed such that:
384 0: vertical up
caryclark@google.comf68154a2012-11-21 15:18:06 +0000385 1: horizontal left
caryclark@google.comf1316942011-07-26 19:54:45 +0000386 2: vertical down
caryclark@google.comf68154a2012-11-21 15:18:06 +0000387 3: horizontal right
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000388
caryclark@google.comf1316942011-07-26 19:54:45 +0000389A rectangle cycles up/right/down/left or up/left/down/right.
390
391The test fails if:
392 The path is closed, and followed by a line.
393 A second move creates a new endpoint.
394 A diagonal line is parsed.
395 There's more than four changes of direction.
396 There's a discontinuity on the line (e.g., a move in the middle)
397 The line reverses direction.
caryclark@google.comf1316942011-07-26 19:54:45 +0000398 The path contains a quadratic or cubic.
399 The path contains fewer than four points.
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000400 *The rectangle doesn't complete a cycle.
401 *The final point isn't equal to the first point.
402
403 *These last two conditions we relax if we have a 3-edge path that would
404 form a rectangle if it were closed (as we do when we fill a path)
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000405
caryclark@google.comf1316942011-07-26 19:54:45 +0000406It's OK if the path has:
407 Several colinear line segments composing a rectangle side.
408 Single points on the rectangle side.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000409
caryclark@google.comf1316942011-07-26 19:54:45 +0000410The direction takes advantage of the corners found since opposite sides
411must travel in opposite directions.
412
413FIXME: Allow colinear quads and cubics to be treated like lines.
414FIXME: If the API passes fill-only, return true if the filled stroke
415 is a rectangle, though the caller failed to close the path.
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000416
417 first,last,next direction state-machine:
418 0x1 is set if the segment is horizontal
419 0x2 is set if the segment is moving to the right or down
420 thus:
421 two directions are opposites iff (dirA ^ dirB) == 0x2
422 two directions are perpendicular iff (dirA ^ dirB) == 0x1
skia.committer@gmail.comf5e67c12014-01-16 07:01:48 +0000423
caryclark@google.comf1316942011-07-26 19:54:45 +0000424 */
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000425static int rect_make_dir(SkScalar dx, SkScalar dy) {
426 return ((0 != dx) << 0) | ((dx > 0 || dy > 0) << 1);
427}
caryclark@google.comf68154a2012-11-21 15:18:06 +0000428bool SkPath::isRectContour(bool allowPartial, int* currVerb, const SkPoint** ptsPtr,
429 bool* isClosed, Direction* direction) const {
caryclark@google.comf1316942011-07-26 19:54:45 +0000430 int corners = 0;
431 SkPoint first, last;
caryclark@google.com56f233a2012-11-19 13:06:06 +0000432 const SkPoint* pts = *ptsPtr;
halcanary96fcdcc2015-08-27 07:41:13 -0700433 const SkPoint* savePts = nullptr;
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000434 first.set(0, 0);
435 last.set(0, 0);
436 int firstDirection = 0;
437 int lastDirection = 0;
438 int nextDirection = 0;
439 bool closedOrMoved = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000440 bool autoClose = false;
caryclark95bc5f32015-04-08 08:34:15 -0700441 bool insertClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000442 int verbCnt = fPathRef->countVerbs();
caryclark@google.com56f233a2012-11-19 13:06:06 +0000443 while (*currVerb < verbCnt && (!allowPartial || !autoClose)) {
caryclark95bc5f32015-04-08 08:34:15 -0700444 uint8_t verb = insertClose ? (uint8_t) kClose_Verb : fPathRef->atVerb(*currVerb);
445 switch (verb) {
caryclark@google.comf1316942011-07-26 19:54:45 +0000446 case kClose_Verb:
caryclark@google.com56f233a2012-11-19 13:06:06 +0000447 savePts = pts;
448 pts = *ptsPtr;
caryclark@google.comf1316942011-07-26 19:54:45 +0000449 autoClose = true;
caryclark95bc5f32015-04-08 08:34:15 -0700450 insertClose = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000451 case kLine_Verb: {
452 SkScalar left = last.fX;
453 SkScalar top = last.fY;
454 SkScalar right = pts->fX;
455 SkScalar bottom = pts->fY;
456 ++pts;
457 if (left != right && top != bottom) {
458 return false; // diagonal
459 }
460 if (left == right && top == bottom) {
461 break; // single point on side OK
462 }
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000463 nextDirection = rect_make_dir(right - left, bottom - top);
caryclark@google.comf1316942011-07-26 19:54:45 +0000464 if (0 == corners) {
465 firstDirection = nextDirection;
466 first = last;
467 last = pts[-1];
468 corners = 1;
469 closedOrMoved = false;
470 break;
471 }
472 if (closedOrMoved) {
473 return false; // closed followed by a line
474 }
caryclark@google.combfe90372012-11-21 13:56:20 +0000475 if (autoClose && nextDirection == firstDirection) {
476 break; // colinear with first
477 }
caryclark@google.comf1316942011-07-26 19:54:45 +0000478 closedOrMoved = autoClose;
479 if (lastDirection != nextDirection) {
480 if (++corners > 4) {
481 return false; // too many direction changes
482 }
483 }
484 last = pts[-1];
485 if (lastDirection == nextDirection) {
486 break; // colinear segment
487 }
488 // Possible values for corners are 2, 3, and 4.
489 // When corners == 3, nextDirection opposes firstDirection.
490 // Otherwise, nextDirection at corner 2 opposes corner 4.
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000491 int turn = firstDirection ^ (corners - 1);
caryclark@google.comf1316942011-07-26 19:54:45 +0000492 int directionCycle = 3 == corners ? 0 : nextDirection ^ turn;
493 if ((directionCycle ^ turn) != nextDirection) {
494 return false; // direction didn't follow cycle
495 }
496 break;
497 }
498 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000499 case kConic_Verb:
caryclark@google.comf1316942011-07-26 19:54:45 +0000500 case kCubic_Verb:
501 return false; // quadratic, cubic not allowed
502 case kMove_Verb:
caryclark95bc5f32015-04-08 08:34:15 -0700503 if (allowPartial && !autoClose && firstDirection) {
504 insertClose = true;
505 *currVerb -= 1; // try move again afterwards
506 goto addMissingClose;
507 }
caryclark@google.comf1316942011-07-26 19:54:45 +0000508 last = *pts++;
509 closedOrMoved = true;
510 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000511 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000512 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000513 break;
caryclark@google.comf1316942011-07-26 19:54:45 +0000514 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000515 *currVerb += 1;
caryclark@google.comf1316942011-07-26 19:54:45 +0000516 lastDirection = nextDirection;
caryclark95bc5f32015-04-08 08:34:15 -0700517addMissingClose:
518 ;
caryclark@google.comf1316942011-07-26 19:54:45 +0000519 }
520 // Success if 4 corners and first point equals last
caryclark@google.combfe90372012-11-21 13:56:20 +0000521 bool result = 4 == corners && (first == last || autoClose);
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000522 if (!result) {
523 // check if we are just an incomplete rectangle, in which case we can
524 // return true, but not claim to be closed.
525 // e.g.
526 // 3 sided rectangle
527 // 4 sided but the last edge is not long enough to reach the start
528 //
529 SkScalar closeX = first.x() - last.x();
530 SkScalar closeY = first.y() - last.y();
531 if (closeX && closeY) {
532 return false; // we're diagonal, abort (can we ever reach this?)
533 }
534 int closeDirection = rect_make_dir(closeX, closeY);
535 // make sure the close-segment doesn't double-back on itself
536 if (3 == corners || (4 == corners && closeDirection == lastDirection)) {
537 result = true;
538 autoClose = false; // we are not closed
539 }
540 }
caryclark@google.combfe90372012-11-21 13:56:20 +0000541 if (savePts) {
542 *ptsPtr = savePts;
543 }
caryclark@google.comf68154a2012-11-21 15:18:06 +0000544 if (result && isClosed) {
545 *isClosed = autoClose;
546 }
547 if (result && direction) {
sugoi@google.com12b4e272012-12-06 20:13:11 +0000548 *direction = firstDirection == ((lastDirection + 1) & 3) ? kCCW_Direction : kCW_Direction;
caryclark@google.comf68154a2012-11-21 15:18:06 +0000549 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000550 return result;
551}
552
robertphillips4f662e62014-12-29 14:06:51 -0800553bool SkPath::isRect(SkRect* rect, bool* isClosed, Direction* direction) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000554 SkDEBUGCODE(this->validate();)
555 int currVerb = 0;
556 const SkPoint* pts = fPathRef->points();
robertphillipsfe7c4272014-12-29 11:36:39 -0800557 const SkPoint* first = pts;
robertphillips4f662e62014-12-29 14:06:51 -0800558 if (!this->isRectContour(false, &currVerb, &pts, isClosed, direction)) {
robertphillipsfe7c4272014-12-29 11:36:39 -0800559 return false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000560 }
robertphillipsfe7c4272014-12-29 11:36:39 -0800561 if (rect) {
robertphillips4f662e62014-12-29 14:06:51 -0800562 int32_t num = SkToS32(pts - first);
563 if (num) {
564 rect->set(first, num);
robertphillipsfe7c4272014-12-29 11:36:39 -0800565 } else {
566 // 'pts' isn't updated for open rects
567 *rect = this->getBounds();
568 }
569 }
570 return true;
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000571}
skia.committer@gmail.com020b25b2013-06-22 07:00:58 +0000572
caryclark95bc5f32015-04-08 08:34:15 -0700573bool SkPath::isNestedFillRects(SkRect rects[2], Direction dirs[2]) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000574 SkDEBUGCODE(this->validate();)
575 int currVerb = 0;
576 const SkPoint* pts = fPathRef->points();
577 const SkPoint* first = pts;
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000578 Direction testDirs[2];
halcanary96fcdcc2015-08-27 07:41:13 -0700579 if (!isRectContour(true, &currVerb, &pts, nullptr, &testDirs[0])) {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000580 return false;
581 }
582 const SkPoint* last = pts;
583 SkRect testRects[2];
caryclark95bc5f32015-04-08 08:34:15 -0700584 bool isClosed;
585 if (isRectContour(false, &currVerb, &pts, &isClosed, &testDirs[1])) {
scroggo@google.com614f9e32013-05-09 18:05:32 +0000586 testRects[0].set(first, SkToS32(last - first));
caryclark95bc5f32015-04-08 08:34:15 -0700587 if (!isClosed) {
588 pts = fPathRef->points() + fPathRef->countPoints();
589 }
scroggo@google.com614f9e32013-05-09 18:05:32 +0000590 testRects[1].set(last, SkToS32(pts - last));
caryclark@google.com56f233a2012-11-19 13:06:06 +0000591 if (testRects[0].contains(testRects[1])) {
592 if (rects) {
593 rects[0] = testRects[0];
594 rects[1] = testRects[1];
595 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000596 if (dirs) {
597 dirs[0] = testDirs[0];
598 dirs[1] = testDirs[1];
599 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000600 return true;
601 }
602 if (testRects[1].contains(testRects[0])) {
603 if (rects) {
604 rects[0] = testRects[1];
605 rects[1] = testRects[0];
606 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000607 if (dirs) {
608 dirs[0] = testDirs[1];
609 dirs[1] = testDirs[0];
610 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000611 return true;
612 }
613 }
614 return false;
615}
616
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000617int SkPath::countPoints() const {
618 return fPathRef->countPoints();
619}
620
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000621int SkPath::getPoints(SkPoint dst[], int max) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000622 SkDEBUGCODE(this->validate();)
623
624 SkASSERT(max >= 0);
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000625 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000626 int count = SkMin32(max, fPathRef->countPoints());
mtklein8bf5d172015-12-09 13:15:07 -0800627 sk_careful_memcpy(dst, fPathRef->points(), count * sizeof(SkPoint));
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000628 return fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000629}
630
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000631SkPoint SkPath::getPoint(int index) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000632 if ((unsigned)index < (unsigned)fPathRef->countPoints()) {
633 return fPathRef->atPoint(index);
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000634 }
635 return SkPoint::Make(0, 0);
636}
637
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000638int SkPath::countVerbs() const {
639 return fPathRef->countVerbs();
640}
641
642static inline void copy_verbs_reverse(uint8_t* inorderDst,
643 const uint8_t* reversedSrc,
644 int count) {
645 for (int i = 0; i < count; ++i) {
646 inorderDst[i] = reversedSrc[~i];
647 }
648}
649
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000650int SkPath::getVerbs(uint8_t dst[], int max) const {
651 SkDEBUGCODE(this->validate();)
652
653 SkASSERT(max >= 0);
654 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000655 int count = SkMin32(max, fPathRef->countVerbs());
656 copy_verbs_reverse(dst, fPathRef->verbs(), count);
657 return fPathRef->countVerbs();
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000658}
659
reed@google.com294dd7b2011-10-11 11:58:32 +0000660bool SkPath::getLastPt(SkPoint* lastPt) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000661 SkDEBUGCODE(this->validate();)
662
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000663 int count = fPathRef->countPoints();
reed@google.com294dd7b2011-10-11 11:58:32 +0000664 if (count > 0) {
665 if (lastPt) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000666 *lastPt = fPathRef->atPoint(count - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000667 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000668 return true;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000669 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000670 if (lastPt) {
671 lastPt->set(0, 0);
672 }
673 return false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000674}
675
caryclarkaec25102015-04-29 08:28:30 -0700676void SkPath::setPt(int index, SkScalar x, SkScalar y) {
677 SkDEBUGCODE(this->validate();)
678
679 int count = fPathRef->countPoints();
680 if (count <= index) {
681 return;
682 } else {
683 SkPathRef::Editor ed(&fPathRef);
684 ed.atPoint(index)->set(x, y);
685 }
686}
687
reed@android.com8a1c16f2008-12-17 15:59:43 +0000688void SkPath::setLastPt(SkScalar x, SkScalar y) {
689 SkDEBUGCODE(this->validate();)
690
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000691 int count = fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000692 if (count == 0) {
693 this->moveTo(x, y);
694 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000695 SkPathRef::Editor ed(&fPathRef);
696 ed.atPoint(count-1)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000697 }
698}
699
reed@google.com04863fa2011-05-15 04:08:24 +0000700void SkPath::setConvexity(Convexity c) {
701 if (fConvexity != c) {
702 fConvexity = c;
reed@google.com04863fa2011-05-15 04:08:24 +0000703 }
704}
705
reed@android.com8a1c16f2008-12-17 15:59:43 +0000706//////////////////////////////////////////////////////////////////////////////
707// Construction methods
708
reed026beb52015-06-10 14:23:15 -0700709#define DIRTY_AFTER_EDIT \
710 do { \
711 fConvexity = kUnknown_Convexity; \
712 fFirstDirection = SkPathPriv::kUnknown_FirstDirection; \
reed@google.comb54455e2011-05-16 14:16:04 +0000713 } while (0)
714
reed@android.com8a1c16f2008-12-17 15:59:43 +0000715void SkPath::incReserve(U16CPU inc) {
716 SkDEBUGCODE(this->validate();)
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000717 SkPathRef::Editor(&fPathRef, inc, inc);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000718 SkDEBUGCODE(this->validate();)
719}
720
721void SkPath::moveTo(SkScalar x, SkScalar y) {
722 SkDEBUGCODE(this->validate();)
723
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000724 SkPathRef::Editor ed(&fPathRef);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000725
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000726 // remember our index
727 fLastMoveToIndex = fPathRef->countPoints();
728
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000729 ed.growForVerb(kMove_Verb)->set(x, y);
bsalomonb17c1292014-08-28 14:04:55 -0700730
731 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000732}
733
734void SkPath::rMoveTo(SkScalar x, SkScalar y) {
735 SkPoint pt;
736 this->getLastPt(&pt);
737 this->moveTo(pt.fX + x, pt.fY + y);
738}
739
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000740void SkPath::injectMoveToIfNeeded() {
741 if (fLastMoveToIndex < 0) {
742 SkScalar x, y;
743 if (fPathRef->countVerbs() == 0) {
744 x = y = 0;
745 } else {
746 const SkPoint& pt = fPathRef->atPoint(~fLastMoveToIndex);
747 x = pt.fX;
748 y = pt.fY;
749 }
750 this->moveTo(x, y);
751 }
752}
753
reed@android.com8a1c16f2008-12-17 15:59:43 +0000754void SkPath::lineTo(SkScalar x, SkScalar y) {
755 SkDEBUGCODE(this->validate();)
756
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000757 this->injectMoveToIfNeeded();
758
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000759 SkPathRef::Editor ed(&fPathRef);
760 ed.growForVerb(kLine_Verb)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000761
reed@google.comb54455e2011-05-16 14:16:04 +0000762 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000763}
764
765void SkPath::rLineTo(SkScalar x, SkScalar y) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000766 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000767 SkPoint pt;
768 this->getLastPt(&pt);
769 this->lineTo(pt.fX + x, pt.fY + y);
770}
771
772void SkPath::quadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
773 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000774
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000775 this->injectMoveToIfNeeded();
776
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000777 SkPathRef::Editor ed(&fPathRef);
778 SkPoint* pts = ed.growForVerb(kQuad_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000779 pts[0].set(x1, y1);
780 pts[1].set(x2, y2);
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000781
reed@google.comb54455e2011-05-16 14:16:04 +0000782 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000783}
784
785void SkPath::rQuadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000786 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000787 SkPoint pt;
788 this->getLastPt(&pt);
789 this->quadTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2);
790}
791
reed@google.com277c3f82013-05-31 15:17:50 +0000792void SkPath::conicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
793 SkScalar w) {
794 // check for <= 0 or NaN with this test
795 if (!(w > 0)) {
796 this->lineTo(x2, y2);
797 } else if (!SkScalarIsFinite(w)) {
798 this->lineTo(x1, y1);
799 this->lineTo(x2, y2);
800 } else if (SK_Scalar1 == w) {
801 this->quadTo(x1, y1, x2, y2);
802 } else {
803 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000804
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000805 this->injectMoveToIfNeeded();
806
reed@google.com277c3f82013-05-31 15:17:50 +0000807 SkPathRef::Editor ed(&fPathRef);
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000808 SkPoint* pts = ed.growForVerb(kConic_Verb, w);
reed@google.com277c3f82013-05-31 15:17:50 +0000809 pts[0].set(x1, y1);
810 pts[1].set(x2, y2);
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000811
reed@google.com277c3f82013-05-31 15:17:50 +0000812 DIRTY_AFTER_EDIT;
813 }
814}
815
816void SkPath::rConicTo(SkScalar dx1, SkScalar dy1, SkScalar dx2, SkScalar dy2,
817 SkScalar w) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000818 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@google.com277c3f82013-05-31 15:17:50 +0000819 SkPoint pt;
820 this->getLastPt(&pt);
821 this->conicTo(pt.fX + dx1, pt.fY + dy1, pt.fX + dx2, pt.fY + dy2, w);
822}
823
reed@android.com8a1c16f2008-12-17 15:59:43 +0000824void SkPath::cubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
825 SkScalar x3, SkScalar y3) {
826 SkDEBUGCODE(this->validate();)
827
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000828 this->injectMoveToIfNeeded();
829
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000830 SkPathRef::Editor ed(&fPathRef);
831 SkPoint* pts = ed.growForVerb(kCubic_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000832 pts[0].set(x1, y1);
833 pts[1].set(x2, y2);
834 pts[2].set(x3, y3);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000835
reed@google.comb54455e2011-05-16 14:16:04 +0000836 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000837}
838
839void SkPath::rCubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
840 SkScalar x3, SkScalar y3) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000841 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000842 SkPoint pt;
843 this->getLastPt(&pt);
844 this->cubicTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2,
845 pt.fX + x3, pt.fY + y3);
846}
847
848void SkPath::close() {
849 SkDEBUGCODE(this->validate();)
850
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000851 int count = fPathRef->countVerbs();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000852 if (count > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000853 switch (fPathRef->atVerb(count - 1)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000854 case kLine_Verb:
855 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000856 case kConic_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +0000857 case kCubic_Verb:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000858 case kMove_Verb: {
859 SkPathRef::Editor ed(&fPathRef);
860 ed.growForVerb(kClose_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000861 break;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000862 }
reed@google.com277c3f82013-05-31 15:17:50 +0000863 case kClose_Verb:
reed@google.comfa2f2a42013-05-30 15:29:48 +0000864 // don't add a close if it's the first verb or a repeat
reed@google.com7950a9e2013-05-30 14:57:55 +0000865 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000866 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000867 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000868 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000869 }
870 }
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000871
872 // signal that we need a moveTo to follow us (unless we're done)
873#if 0
874 if (fLastMoveToIndex >= 0) {
875 fLastMoveToIndex = ~fLastMoveToIndex;
876 }
877#else
878 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
879#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000880}
881
882///////////////////////////////////////////////////////////////////////////////
reed@google.comabf15c12011-01-18 20:35:51 +0000883
fmalitac08d53e2015-11-17 09:53:29 -0800884namespace {
885
886template <unsigned N>
887class PointIterator {
888public:
889 PointIterator(SkPath::Direction dir, unsigned startIndex)
890 : fCurrent(startIndex % N)
891 , fAdvance(dir == SkPath::kCW_Direction ? 1 : N - 1) { }
892
893 const SkPoint& current() const {
894 SkASSERT(fCurrent < N);
895 return fPts[fCurrent];
896 }
897
898 const SkPoint& next() {
899 fCurrent = (fCurrent + fAdvance) % N;
900 return this->current();
901 }
902
903protected:
904 SkPoint fPts[N];
905
906private:
907 unsigned fCurrent;
908 unsigned fAdvance;
909};
910
911class RectPointIterator : public PointIterator<4> {
912public:
913 RectPointIterator(const SkRect& rect, SkPath::Direction dir, unsigned startIndex)
914 : PointIterator(dir, startIndex) {
915
916 fPts[0] = SkPoint::Make(rect.fLeft, rect.fTop);
917 fPts[1] = SkPoint::Make(rect.fRight, rect.fTop);
918 fPts[2] = SkPoint::Make(rect.fRight, rect.fBottom);
919 fPts[3] = SkPoint::Make(rect.fLeft, rect.fBottom);
920 }
921};
922
923class OvalPointIterator : public PointIterator<4> {
924public:
925 OvalPointIterator(const SkRect& oval, SkPath::Direction dir, unsigned startIndex)
926 : PointIterator(dir, startIndex) {
927
928 const SkScalar cx = oval.centerX();
929 const SkScalar cy = oval.centerY();
930
931 fPts[0] = SkPoint::Make(cx, oval.fTop);
932 fPts[1] = SkPoint::Make(oval.fRight, cy);
933 fPts[2] = SkPoint::Make(cx, oval.fBottom);
934 fPts[3] = SkPoint::Make(oval.fLeft, cy);
935 }
936};
937
938class RRectPointIterator : public PointIterator<8> {
939public:
940 RRectPointIterator(const SkRRect& rrect, SkPath::Direction dir, unsigned startIndex)
941 : PointIterator(dir, startIndex) {
942
943 const SkRect& bounds = rrect.getBounds();
944 const SkScalar L = bounds.fLeft;
945 const SkScalar T = bounds.fTop;
946 const SkScalar R = bounds.fRight;
947 const SkScalar B = bounds.fBottom;
948
949 fPts[0] = SkPoint::Make(L + rrect.radii(SkRRect::kUpperLeft_Corner).fX, T);
950 fPts[1] = SkPoint::Make(R - rrect.radii(SkRRect::kUpperRight_Corner).fX, T);
951 fPts[2] = SkPoint::Make(R, T + rrect.radii(SkRRect::kUpperRight_Corner).fY);
952 fPts[3] = SkPoint::Make(R, B - rrect.radii(SkRRect::kLowerRight_Corner).fY);
953 fPts[4] = SkPoint::Make(R - rrect.radii(SkRRect::kLowerRight_Corner).fX, B);
954 fPts[5] = SkPoint::Make(L + rrect.radii(SkRRect::kLowerLeft_Corner).fX, B);
955 fPts[6] = SkPoint::Make(L, B - rrect.radii(SkRRect::kLowerLeft_Corner).fY);
956 fPts[7] = SkPoint::Make(L, T + rrect.radii(SkRRect::kUpperLeft_Corner).fY);
957 }
958};
959
960} // anonymous namespace
961
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000962static void assert_known_direction(int dir) {
963 SkASSERT(SkPath::kCW_Direction == dir || SkPath::kCCW_Direction == dir);
964}
965
reed@android.com8a1c16f2008-12-17 15:59:43 +0000966void SkPath::addRect(const SkRect& rect, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -0800967 this->addRect(rect, dir, 0);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000968}
969
970void SkPath::addRect(SkScalar left, SkScalar top, SkScalar right,
971 SkScalar bottom, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -0800972 this->addRect(SkRect::MakeLTRB(left, top, right, bottom), dir, 0);
973}
974
975void SkPath::addRect(const SkRect &rect, Direction dir, unsigned startIndex) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000976 assert_known_direction(dir);
reed026beb52015-06-10 14:23:15 -0700977 fFirstDirection = this->hasOnlyMoveTos() ?
fmalitac08d53e2015-11-17 09:53:29 -0800978 (SkPathPriv::FirstDirection)dir : SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000979 SkAutoDisableDirectionCheck addc(this);
fmalitac08d53e2015-11-17 09:53:29 -0800980 SkAutoPathBoundsUpdate apbu(this, rect);
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000981
fmalitac08d53e2015-11-17 09:53:29 -0800982 SkDEBUGCODE(int initialVerbCount = this->countVerbs());
reed@android.com8a1c16f2008-12-17 15:59:43 +0000983
fmalitac08d53e2015-11-17 09:53:29 -0800984 const int kVerbs = 5; // moveTo + 3x lineTo + close
985 this->incReserve(kVerbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000986
fmalitac08d53e2015-11-17 09:53:29 -0800987 RectPointIterator iter(rect, dir, startIndex);
988
989 this->moveTo(iter.current());
990 this->lineTo(iter.next());
991 this->lineTo(iter.next());
992 this->lineTo(iter.next());
reed@android.com8a1c16f2008-12-17 15:59:43 +0000993 this->close();
fmalitac08d53e2015-11-17 09:53:29 -0800994
995 SkASSERT(this->countVerbs() == initialVerbCount + kVerbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000996}
997
reed@google.com744faba2012-05-29 19:54:52 +0000998void SkPath::addPoly(const SkPoint pts[], int count, bool close) {
999 SkDEBUGCODE(this->validate();)
1000 if (count <= 0) {
1001 return;
1002 }
1003
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +00001004 fLastMoveToIndex = fPathRef->countPoints();
1005
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001006 // +close makes room for the extra kClose_Verb
1007 SkPathRef::Editor ed(&fPathRef, count+close, count);
1008
1009 ed.growForVerb(kMove_Verb)->set(pts[0].fX, pts[0].fY);
reed@google.com744faba2012-05-29 19:54:52 +00001010 if (count > 1) {
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001011 SkPoint* p = ed.growForRepeatedVerb(kLine_Verb, count - 1);
1012 memcpy(p, &pts[1], (count-1) * sizeof(SkPoint));
reed@google.com744faba2012-05-29 19:54:52 +00001013 }
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001014
reed@google.com744faba2012-05-29 19:54:52 +00001015 if (close) {
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001016 ed.growForVerb(kClose_Verb);
caryclark63c684a2015-02-25 09:04:04 -08001017 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
reed@google.com744faba2012-05-29 19:54:52 +00001018 }
1019
reed@google.com744faba2012-05-29 19:54:52 +00001020 DIRTY_AFTER_EDIT;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001021 SkDEBUGCODE(this->validate();)
reed@google.com744faba2012-05-29 19:54:52 +00001022}
1023
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001024#include "SkGeometry.h"
1025
reedf90ea012015-01-29 12:03:58 -08001026static bool arc_is_lone_point(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
1027 SkPoint* pt) {
1028 if (0 == sweepAngle && (0 == startAngle || SkIntToScalar(360) == startAngle)) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001029 // Chrome uses this path to move into and out of ovals. If not
1030 // treated as a special case the moves can distort the oval's
1031 // bounding box (and break the circle special case).
reedf90ea012015-01-29 12:03:58 -08001032 pt->set(oval.fRight, oval.centerY());
1033 return true;
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001034 } else if (0 == oval.width() && 0 == oval.height()) {
1035 // Chrome will sometimes create 0 radius round rects. Having degenerate
1036 // quad segments in the path prevents the path from being recognized as
1037 // a rect.
1038 // TODO: optimizing the case where only one of width or height is zero
1039 // should also be considered. This case, however, doesn't seem to be
1040 // as common as the single point case.
reedf90ea012015-01-29 12:03:58 -08001041 pt->set(oval.fRight, oval.fTop);
1042 return true;
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001043 }
reedf90ea012015-01-29 12:03:58 -08001044 return false;
1045}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001046
reedd5d27d92015-02-09 13:54:43 -08001047// Return the unit vectors pointing at the start/stop points for the given start/sweep angles
1048//
1049static void angles_to_unit_vectors(SkScalar startAngle, SkScalar sweepAngle,
1050 SkVector* startV, SkVector* stopV, SkRotationDirection* dir) {
1051 startV->fY = SkScalarSinCos(SkDegreesToRadians(startAngle), &startV->fX);
1052 stopV->fY = SkScalarSinCos(SkDegreesToRadians(startAngle + sweepAngle), &stopV->fX);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001053
1054 /* If the sweep angle is nearly (but less than) 360, then due to precision
reedd5d27d92015-02-09 13:54:43 -08001055 loss in radians-conversion and/or sin/cos, we may end up with coincident
1056 vectors, which will fool SkBuildQuadArc into doing nothing (bad) instead
1057 of drawing a nearly complete circle (good).
1058 e.g. canvas.drawArc(0, 359.99, ...)
1059 -vs- canvas.drawArc(0, 359.9, ...)
1060 We try to detect this edge case, and tweak the stop vector
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001061 */
reedd5d27d92015-02-09 13:54:43 -08001062 if (*startV == *stopV) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001063 SkScalar sw = SkScalarAbs(sweepAngle);
1064 if (sw < SkIntToScalar(360) && sw > SkIntToScalar(359)) {
1065 SkScalar stopRad = SkDegreesToRadians(startAngle + sweepAngle);
1066 // make a guess at a tiny angle (in radians) to tweak by
1067 SkScalar deltaRad = SkScalarCopySign(SK_Scalar1/512, sweepAngle);
1068 // not sure how much will be enough, so we use a loop
1069 do {
1070 stopRad -= deltaRad;
reedd5d27d92015-02-09 13:54:43 -08001071 stopV->fY = SkScalarSinCos(stopRad, &stopV->fX);
1072 } while (*startV == *stopV);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001073 }
1074 }
reedd5d27d92015-02-09 13:54:43 -08001075 *dir = sweepAngle > 0 ? kCW_SkRotationDirection : kCCW_SkRotationDirection;
1076}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001077
reed9e779d42015-02-17 11:43:14 -08001078/**
1079 * If this returns 0, then the caller should just line-to the singlePt, else it should
1080 * ignore singlePt and append the specified number of conics.
1081 */
reedd5d27d92015-02-09 13:54:43 -08001082static int build_arc_conics(const SkRect& oval, const SkVector& start, const SkVector& stop,
reed9e779d42015-02-17 11:43:14 -08001083 SkRotationDirection dir, SkConic conics[SkConic::kMaxConicsForArc],
1084 SkPoint* singlePt) {
reedd5d27d92015-02-09 13:54:43 -08001085 SkMatrix matrix;
1086
1087 matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height()));
1088 matrix.postTranslate(oval.centerX(), oval.centerY());
1089
reed9e779d42015-02-17 11:43:14 -08001090 int count = SkConic::BuildUnitArc(start, stop, dir, &matrix, conics);
1091 if (0 == count) {
1092 matrix.mapXY(start.x(), start.y(), singlePt);
1093 }
1094 return count;
reedd5d27d92015-02-09 13:54:43 -08001095}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001096
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001097void SkPath::addRoundRect(const SkRect& rect, const SkScalar radii[],
reed@android.com8a1c16f2008-12-17 15:59:43 +00001098 Direction dir) {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001099 SkRRect rrect;
1100 rrect.setRectRadii(rect, (const SkVector*) radii);
1101 this->addRRect(rrect, dir);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001102}
1103
reed@google.com4ed0fb72012-12-12 20:48:18 +00001104void SkPath::addRRect(const SkRRect& rrect, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -08001105 // legacy start indices: 6 (CW) and 7(CCW)
1106 this->addRRect(rrect, dir, dir == kCW_Direction ? 6 : 7);
1107}
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001108
fmalitac08d53e2015-11-17 09:53:29 -08001109void SkPath::addRRect(const SkRRect &rrect, Direction dir, unsigned startIndex) {
1110 assert_known_direction(dir);
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001111
fmalitac08d53e2015-11-17 09:53:29 -08001112 if (rrect.isEmpty()) {
1113 return;
reed1b28a3a2014-12-17 14:42:09 -08001114 }
fmalitac08d53e2015-11-17 09:53:29 -08001115
caryclarkda707bf2015-11-19 14:47:43 -08001116 bool isRRect = hasOnlyMoveTos();
fmalitac08d53e2015-11-17 09:53:29 -08001117 const SkRect& bounds = rrect.getBounds();
1118
1119 if (rrect.isRect()) {
1120 // degenerate(rect) => radii points are collapsing
1121 this->addRect(bounds, dir, (startIndex + 1) / 2);
1122 } else if (rrect.isOval()) {
1123 // degenerate(oval) => line points are collapsing
1124 this->addOval(bounds, dir, startIndex / 2);
1125 } else {
1126 fFirstDirection = this->hasOnlyMoveTos() ?
1127 (SkPathPriv::FirstDirection)dir : SkPathPriv::kUnknown_FirstDirection;
1128
1129 SkAutoPathBoundsUpdate apbu(this, bounds);
1130 SkAutoDisableDirectionCheck addc(this);
1131
1132 // we start with a conic on odd indices when moving CW vs. even indices when moving CCW
1133 const bool startsWithConic = ((startIndex & 1) == (dir == kCW_Direction));
1134 const SkScalar weight = SK_ScalarRoot2Over2;
1135
1136 SkDEBUGCODE(int initialVerbCount = this->countVerbs());
1137 const int kVerbs = startsWithConic
1138 ? 9 // moveTo + 4x conicTo + 3x lineTo + close
1139 : 10; // moveTo + 4x lineTo + 4x conicTo + close
1140 this->incReserve(kVerbs);
1141
1142 RRectPointIterator rrectIter(rrect, dir, startIndex);
1143 // Corner iterator indices follow the collapsed radii model,
1144 // adjusted such that the start pt is "behind" the radii start pt.
1145 const unsigned rectStartIndex = startIndex / 2 + (dir == kCW_Direction ? 0 : 1);
1146 RectPointIterator rectIter(bounds, dir, rectStartIndex);
1147
1148 this->moveTo(rrectIter.current());
1149 if (startsWithConic) {
1150 for (unsigned i = 0; i < 3; ++i) {
1151 this->conicTo(rectIter.next(), rrectIter.next(), weight);
1152 this->lineTo(rrectIter.next());
1153 }
1154 this->conicTo(rectIter.next(), rrectIter.next(), weight);
1155 // final lineTo handled by close().
1156 } else {
1157 for (unsigned i = 0; i < 4; ++i) {
1158 this->lineTo(rrectIter.next());
1159 this->conicTo(rectIter.next(), rrectIter.next(), weight);
1160 }
1161 }
1162 this->close();
1163
caryclarkda707bf2015-11-19 14:47:43 -08001164 SkPathRef::Editor ed(&fPathRef);
bsalomon78d58d12016-05-27 09:17:04 -07001165 ed.setIsRRect(isRRect, dir, startIndex % 8);
caryclarkda707bf2015-11-19 14:47:43 -08001166
fmalitac08d53e2015-11-17 09:53:29 -08001167 SkASSERT(this->countVerbs() == initialVerbCount + kVerbs);
1168 }
1169
1170 SkDEBUGCODE(fPathRef->validate();)
reed@google.com4ed0fb72012-12-12 20:48:18 +00001171}
1172
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001173bool SkPath::hasOnlyMoveTos() const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001174 int count = fPathRef->countVerbs();
1175 const uint8_t* verbs = const_cast<const SkPathRef*>(fPathRef.get())->verbsMemBegin();
1176 for (int i = 0; i < count; ++i) {
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001177 if (*verbs == kLine_Verb ||
1178 *verbs == kQuad_Verb ||
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001179 *verbs == kConic_Verb ||
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001180 *verbs == kCubic_Verb) {
1181 return false;
1182 }
1183 ++verbs;
1184 }
1185 return true;
1186}
1187
caryclarkd49a86a2016-02-22 12:44:54 -08001188bool SkPath::isZeroLength() const {
1189 int count = fPathRef->countPoints();
1190 if (count < 2) {
1191 return true;
1192 }
1193 const SkPoint* pts = fPathRef.get()->points();
1194 const SkPoint& first = *pts;
1195 for (int index = 1; index < count; ++index) {
1196 if (first != pts[index]) {
1197 return false;
1198 }
1199 }
1200 return true;
1201}
1202
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001203void SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
1204 Direction dir) {
1205 assert_known_direction(dir);
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001206
humper@google.com75e3ca12013-04-08 21:44:11 +00001207 if (rx < 0 || ry < 0) {
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001208 SkErrorInternals::SetError( kInvalidArgument_SkError,
humper@google.com75e3ca12013-04-08 21:44:11 +00001209 "I got %f and %f as radii to SkPath::AddRoundRect, "
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001210 "but negative radii are not allowed.",
humper@google.com75e3ca12013-04-08 21:44:11 +00001211 SkScalarToDouble(rx), SkScalarToDouble(ry) );
1212 return;
1213 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001214
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001215 SkRRect rrect;
1216 rrect.setRectXY(rect, rx, ry);
1217 this->addRRect(rrect, dir);
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001218}
1219
reed@android.com8a1c16f2008-12-17 15:59:43 +00001220void SkPath::addOval(const SkRect& oval, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -08001221 // legacy start index: 1
1222 this->addOval(oval, dir, 1);
1223}
1224
1225void SkPath::addOval(const SkRect &oval, Direction dir, unsigned startPointIndex) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +00001226 assert_known_direction(dir);
1227
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001228 /* If addOval() is called after previous moveTo(),
1229 this path is still marked as an oval. This is used to
1230 fit into WebKit's calling sequences.
1231 We can't simply check isEmpty() in this case, as additional
1232 moveTo() would mark the path non empty.
1233 */
robertphillips@google.com466310d2013-12-03 16:43:54 +00001234 bool isOval = hasOnlyMoveTos();
1235 if (isOval) {
reed026beb52015-06-10 14:23:15 -07001236 fFirstDirection = (SkPathPriv::FirstDirection)dir;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001237 } else {
reed026beb52015-06-10 14:23:15 -07001238 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001239 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001240
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001241 SkAutoDisableDirectionCheck addc(this);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001242 SkAutoPathBoundsUpdate apbu(this, oval);
1243
fmalitac08d53e2015-11-17 09:53:29 -08001244 SkDEBUGCODE(int initialVerbCount = this->countVerbs());
1245 const int kVerbs = 6; // moveTo + 4x conicTo + close
1246 this->incReserve(kVerbs);
1247
1248 OvalPointIterator ovalIter(oval, dir, startPointIndex);
1249 // The corner iterator pts are tracking "behind" the oval/radii pts.
1250 RectPointIterator rectIter(oval, dir, startPointIndex + (dir == kCW_Direction ? 0 : 1));
reed220f9262014-12-17 08:21:04 -08001251 const SkScalar weight = SK_ScalarRoot2Over2;
1252
fmalitac08d53e2015-11-17 09:53:29 -08001253 this->moveTo(ovalIter.current());
1254 for (unsigned i = 0; i < 4; ++i) {
1255 this->conicTo(rectIter.next(), ovalIter.next(), weight);
reed220f9262014-12-17 08:21:04 -08001256 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001257 this->close();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001258
fmalitac08d53e2015-11-17 09:53:29 -08001259 SkASSERT(this->countVerbs() == initialVerbCount + kVerbs);
1260
robertphillips@google.com466310d2013-12-03 16:43:54 +00001261 SkPathRef::Editor ed(&fPathRef);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001262
bsalomon78d58d12016-05-27 09:17:04 -07001263 ed.setIsOval(isOval, kCCW_Direction == dir, startPointIndex % 4);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001264}
1265
reed@android.com8a1c16f2008-12-17 15:59:43 +00001266void SkPath::addCircle(SkScalar x, SkScalar y, SkScalar r, Direction dir) {
1267 if (r > 0) {
fmalitac08d53e2015-11-17 09:53:29 -08001268 this->addOval(SkRect::MakeLTRB(x - r, y - r, x + r, y + r), dir);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001269 }
1270}
1271
reed@android.com8a1c16f2008-12-17 15:59:43 +00001272void SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
1273 bool forceMoveTo) {
1274 if (oval.width() < 0 || oval.height() < 0) {
1275 return;
1276 }
1277
reedf90ea012015-01-29 12:03:58 -08001278 if (fPathRef->countVerbs() == 0) {
1279 forceMoveTo = true;
1280 }
1281
1282 SkPoint lonePt;
1283 if (arc_is_lone_point(oval, startAngle, sweepAngle, &lonePt)) {
1284 forceMoveTo ? this->moveTo(lonePt) : this->lineTo(lonePt);
1285 return;
1286 }
1287
reedd5d27d92015-02-09 13:54:43 -08001288 SkVector startV, stopV;
1289 SkRotationDirection dir;
1290 angles_to_unit_vectors(startAngle, sweepAngle, &startV, &stopV, &dir);
1291
reed9e779d42015-02-17 11:43:14 -08001292 SkPoint singlePt;
reedd5d27d92015-02-09 13:54:43 -08001293 SkConic conics[SkConic::kMaxConicsForArc];
reed9e779d42015-02-17 11:43:14 -08001294 int count = build_arc_conics(oval, startV, stopV, dir, conics, &singlePt);
reedd5d27d92015-02-09 13:54:43 -08001295 if (count) {
1296 this->incReserve(count * 2 + 1);
1297 const SkPoint& pt = conics[0].fPts[0];
1298 forceMoveTo ? this->moveTo(pt) : this->lineTo(pt);
1299 for (int i = 0; i < count; ++i) {
1300 this->conicTo(conics[i].fPts[1], conics[i].fPts[2], conics[i].fW);
1301 }
reed9e779d42015-02-17 11:43:14 -08001302 } else {
1303 forceMoveTo ? this->moveTo(singlePt) : this->lineTo(singlePt);
reedd5d27d92015-02-09 13:54:43 -08001304 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001305}
1306
caryclark55d49052016-01-23 05:07:04 -08001307// This converts the SVG arc to conics.
1308// Partly adapted from Niko's code in kdelibs/kdecore/svgicons.
1309// Then transcribed from webkit/chrome's SVGPathNormalizer::decomposeArcToCubic()
1310// See also SVG implementation notes:
1311// http://www.w3.org/TR/SVG/implnote.html#ArcConversionEndpointToCenter
1312// Note that arcSweep bool value is flipped from the original implementation.
1313void SkPath::arcTo(SkScalar rx, SkScalar ry, SkScalar angle, SkPath::ArcSize arcLarge,
1314 SkPath::Direction arcSweep, SkScalar x, SkScalar y) {
caryclarkf1d41512016-02-09 10:30:22 -08001315 this->injectMoveToIfNeeded();
caryclark55d49052016-01-23 05:07:04 -08001316 SkPoint srcPts[2];
1317 this->getLastPt(&srcPts[0]);
1318 // If rx = 0 or ry = 0 then this arc is treated as a straight line segment (a "lineto")
1319 // joining the endpoints.
1320 // http://www.w3.org/TR/SVG/implnote.html#ArcOutOfRangeParameters
1321 if (!rx || !ry) {
caryclarkfc752532016-01-25 05:39:04 -08001322 this->lineTo(x, y);
caryclark55d49052016-01-23 05:07:04 -08001323 return;
1324 }
1325 // If the current point and target point for the arc are identical, it should be treated as a
1326 // zero length path. This ensures continuity in animations.
1327 srcPts[1].set(x, y);
1328 if (srcPts[0] == srcPts[1]) {
caryclarkfc752532016-01-25 05:39:04 -08001329 this->lineTo(x, y);
caryclark55d49052016-01-23 05:07:04 -08001330 return;
1331 }
1332 rx = SkScalarAbs(rx);
1333 ry = SkScalarAbs(ry);
1334 SkVector midPointDistance = srcPts[0] - srcPts[1];
1335 midPointDistance *= 0.5f;
1336
1337 SkMatrix pointTransform;
1338 pointTransform.setRotate(-angle);
1339
1340 SkPoint transformedMidPoint;
1341 pointTransform.mapPoints(&transformedMidPoint, &midPointDistance, 1);
1342 SkScalar squareRx = rx * rx;
1343 SkScalar squareRy = ry * ry;
1344 SkScalar squareX = transformedMidPoint.fX * transformedMidPoint.fX;
1345 SkScalar squareY = transformedMidPoint.fY * transformedMidPoint.fY;
1346
1347 // Check if the radii are big enough to draw the arc, scale radii if not.
1348 // http://www.w3.org/TR/SVG/implnote.html#ArcCorrectionOutOfRangeRadii
1349 SkScalar radiiScale = squareX / squareRx + squareY / squareRy;
1350 if (radiiScale > 1) {
1351 radiiScale = SkScalarSqrt(radiiScale);
1352 rx *= radiiScale;
1353 ry *= radiiScale;
1354 }
1355
1356 pointTransform.setScale(1 / rx, 1 / ry);
1357 pointTransform.preRotate(-angle);
1358
1359 SkPoint unitPts[2];
1360 pointTransform.mapPoints(unitPts, srcPts, (int) SK_ARRAY_COUNT(unitPts));
1361 SkVector delta = unitPts[1] - unitPts[0];
1362
1363 SkScalar d = delta.fX * delta.fX + delta.fY * delta.fY;
1364 SkScalar scaleFactorSquared = SkTMax(1 / d - 0.25f, 0.f);
1365
1366 SkScalar scaleFactor = SkScalarSqrt(scaleFactorSquared);
1367 if (SkToBool(arcSweep) != SkToBool(arcLarge)) { // flipped from the original implementation
1368 scaleFactor = -scaleFactor;
1369 }
1370 delta.scale(scaleFactor);
1371 SkPoint centerPoint = unitPts[0] + unitPts[1];
1372 centerPoint *= 0.5f;
1373 centerPoint.offset(-delta.fY, delta.fX);
1374 unitPts[0] -= centerPoint;
1375 unitPts[1] -= centerPoint;
1376 SkScalar theta1 = SkScalarATan2(unitPts[0].fY, unitPts[0].fX);
1377 SkScalar theta2 = SkScalarATan2(unitPts[1].fY, unitPts[1].fX);
1378 SkScalar thetaArc = theta2 - theta1;
1379 if (thetaArc < 0 && !arcSweep) { // arcSweep flipped from the original implementation
1380 thetaArc += SK_ScalarPI * 2;
1381 } else if (thetaArc > 0 && arcSweep) { // arcSweep flipped from the original implementation
1382 thetaArc -= SK_ScalarPI * 2;
1383 }
1384 pointTransform.setRotate(angle);
1385 pointTransform.preScale(rx, ry);
1386
1387 int segments = SkScalarCeilToInt(SkScalarAbs(thetaArc / (SK_ScalarPI / 2)));
1388 SkScalar thetaWidth = thetaArc / segments;
1389 SkScalar t = SkScalarTan(0.5f * thetaWidth);
1390 if (!SkScalarIsFinite(t)) {
1391 return;
1392 }
1393 SkScalar startTheta = theta1;
1394 SkScalar w = SkScalarSqrt(SK_ScalarHalf + SkScalarCos(thetaWidth) * SK_ScalarHalf);
1395 for (int i = 0; i < segments; ++i) {
1396 SkScalar endTheta = startTheta + thetaWidth;
1397 SkScalar cosEndTheta, sinEndTheta = SkScalarSinCos(endTheta, &cosEndTheta);
1398
1399 unitPts[1].set(cosEndTheta, sinEndTheta);
1400 unitPts[1] += centerPoint;
1401 unitPts[0] = unitPts[1];
1402 unitPts[0].offset(t * sinEndTheta, -t * cosEndTheta);
1403 SkPoint mapped[2];
1404 pointTransform.mapPoints(mapped, unitPts, (int) SK_ARRAY_COUNT(unitPts));
1405 this->conicTo(mapped[0], mapped[1], w);
1406 startTheta = endTheta;
1407 }
1408}
1409
1410void SkPath::rArcTo(SkScalar rx, SkScalar ry, SkScalar xAxisRotate, SkPath::ArcSize largeArc,
1411 SkPath::Direction sweep, SkScalar dx, SkScalar dy) {
1412 SkPoint currentPoint;
1413 this->getLastPt(&currentPoint);
1414 this->arcTo(rx, ry, xAxisRotate, largeArc, sweep, currentPoint.fX + dx, currentPoint.fY + dy);
1415}
1416
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001417void SkPath::addArc(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001418 if (oval.isEmpty() || 0 == sweepAngle) {
1419 return;
1420 }
1421
1422 const SkScalar kFullCircleAngle = SkIntToScalar(360);
1423
1424 if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
bsalomon1978ce22016-05-31 10:42:16 -07001425 // We can treat the arc as an oval if it begins at one of our legal starting positions.
1426 // See SkPath::addOval() docs.
1427 SkScalar startOver90 = startAngle / 90.f;
1428 SkScalar startOver90I = SkScalarRoundToScalar(startOver90);
1429 SkScalar error = startOver90 - startOver90I;
1430 if (SkScalarNearlyEqual(error, 0)) {
1431 // Index 1 is at startAngle == 0.
1432 SkScalar startIndex = std::fmod(startOver90I + 1.f, 4.f);
1433 startIndex = startIndex < 0 ? startIndex + 4.f : startIndex;
1434 this->addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction,
1435 (unsigned) startIndex);
1436 return;
1437 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001438 }
bsalomon1978ce22016-05-31 10:42:16 -07001439 this->arcTo(oval, startAngle, sweepAngle, true);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001440}
1441
1442/*
1443 Need to handle the case when the angle is sharp, and our computed end-points
1444 for the arc go behind pt1 and/or p2...
1445*/
reedc7789042015-01-29 12:59:11 -08001446void SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2, SkScalar radius) {
reeda8b326c2014-12-09 11:50:32 -08001447 if (radius == 0) {
1448 this->lineTo(x1, y1);
1449 return;
1450 }
1451
1452 SkVector before, after;
reed@google.comabf15c12011-01-18 20:35:51 +00001453
reed@android.com8a1c16f2008-12-17 15:59:43 +00001454 // need to know our prev pt so we can construct tangent vectors
1455 {
1456 SkPoint start;
1457 this->getLastPt(&start);
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001458 // Handle degenerate cases by adding a line to the first point and
1459 // bailing out.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001460 before.setNormalize(x1 - start.fX, y1 - start.fY);
1461 after.setNormalize(x2 - x1, y2 - y1);
1462 }
reed@google.comabf15c12011-01-18 20:35:51 +00001463
reed@android.com8a1c16f2008-12-17 15:59:43 +00001464 SkScalar cosh = SkPoint::DotProduct(before, after);
1465 SkScalar sinh = SkPoint::CrossProduct(before, after);
1466
1467 if (SkScalarNearlyZero(sinh)) { // angle is too tight
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001468 this->lineTo(x1, y1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001469 return;
1470 }
reed@google.comabf15c12011-01-18 20:35:51 +00001471
caryclark88651ae2016-01-20 11:55:11 -08001472 SkScalar dist = SkScalarAbs(SkScalarMulDiv(radius, SK_Scalar1 - cosh, sinh));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001473
1474 SkScalar xx = x1 - SkScalarMul(dist, before.fX);
1475 SkScalar yy = y1 - SkScalarMul(dist, before.fY);
caryclark88651ae2016-01-20 11:55:11 -08001476 after.setLength(dist);
1477 this->lineTo(xx, yy);
1478 SkScalar weight = SkScalarSqrt(SK_ScalarHalf + cosh * SK_ScalarHalf);
1479 this->conicTo(x1, y1, x1 + after.fX, y1 + after.fY, weight);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001480}
1481
1482///////////////////////////////////////////////////////////////////////////////
1483
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001484void SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy, AddPathMode mode) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001485 SkMatrix matrix;
1486
1487 matrix.setTranslate(dx, dy);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001488 this->addPath(path, matrix, mode);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001489}
1490
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001491void SkPath::addPath(const SkPath& path, const SkMatrix& matrix, AddPathMode mode) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001492 SkPathRef::Editor(&fPathRef, path.countVerbs(), path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001493
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001494 RawIter iter(path);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001495 SkPoint pts[4];
1496 Verb verb;
1497
1498 SkMatrix::MapPtsProc proc = matrix.getMapPtsProc();
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001499 bool firstVerb = true;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001500 while ((verb = iter.next(pts)) != kDone_Verb) {
1501 switch (verb) {
1502 case kMove_Verb:
1503 proc(matrix, &pts[0], &pts[0], 1);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001504 if (firstVerb && mode == kExtend_AddPathMode && !isEmpty()) {
1505 injectMoveToIfNeeded(); // In case last contour is closed
1506 this->lineTo(pts[0]);
1507 } else {
1508 this->moveTo(pts[0]);
1509 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001510 break;
1511 case kLine_Verb:
1512 proc(matrix, &pts[1], &pts[1], 1);
1513 this->lineTo(pts[1]);
1514 break;
1515 case kQuad_Verb:
1516 proc(matrix, &pts[1], &pts[1], 2);
1517 this->quadTo(pts[1], pts[2]);
1518 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001519 case kConic_Verb:
1520 proc(matrix, &pts[1], &pts[1], 2);
1521 this->conicTo(pts[1], pts[2], iter.conicWeight());
1522 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001523 case kCubic_Verb:
1524 proc(matrix, &pts[1], &pts[1], 3);
1525 this->cubicTo(pts[1], pts[2], pts[3]);
1526 break;
1527 case kClose_Verb:
1528 this->close();
1529 break;
1530 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001531 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001532 }
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001533 firstVerb = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001534 }
1535}
1536
1537///////////////////////////////////////////////////////////////////////////////
1538
reed@google.com277c3f82013-05-31 15:17:50 +00001539static int pts_in_verb(unsigned verb) {
1540 static const uint8_t gPtsInVerb[] = {
1541 1, // kMove
1542 1, // kLine
1543 2, // kQuad
1544 2, // kConic
1545 3, // kCubic
1546 0, // kClose
1547 0 // kDone
1548 };
1549
1550 SkASSERT(verb < SK_ARRAY_COUNT(gPtsInVerb));
1551 return gPtsInVerb[verb];
1552}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001553
reed@android.com8a1c16f2008-12-17 15:59:43 +00001554// ignore the last point of the 1st contour
1555void SkPath::reversePathTo(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001556 int i, vcount = path.fPathRef->countVerbs();
1557 // exit early if the path is empty, or just has a moveTo.
1558 if (vcount < 2) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001559 return;
1560 }
1561
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001562 SkPathRef::Editor(&fPathRef, vcount, path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001563
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001564 const uint8_t* verbs = path.fPathRef->verbs();
1565 const SkPoint* pts = path.fPathRef->points();
reed@google.com277c3f82013-05-31 15:17:50 +00001566 const SkScalar* conicWeights = path.fPathRef->conicWeights();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001567
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001568 SkASSERT(verbs[~0] == kMove_Verb);
1569 for (i = 1; i < vcount; ++i) {
reed@google.com277c3f82013-05-31 15:17:50 +00001570 unsigned v = verbs[~i];
1571 int n = pts_in_verb(v);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001572 if (n == 0) {
1573 break;
1574 }
1575 pts += n;
reed@google.com277c3f82013-05-31 15:17:50 +00001576 conicWeights += (SkPath::kConic_Verb == v);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001577 }
1578
1579 while (--i > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001580 switch (verbs[~i]) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001581 case kLine_Verb:
1582 this->lineTo(pts[-1].fX, pts[-1].fY);
1583 break;
1584 case kQuad_Verb:
1585 this->quadTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY);
1586 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001587 case kConic_Verb:
1588 this->conicTo(pts[-1], pts[-2], *--conicWeights);
1589 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001590 case kCubic_Verb:
1591 this->cubicTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY,
1592 pts[-3].fX, pts[-3].fY);
1593 break;
1594 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001595 SkDEBUGFAIL("bad verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001596 break;
1597 }
reed@google.com277c3f82013-05-31 15:17:50 +00001598 pts -= pts_in_verb(verbs[~i]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001599 }
1600}
1601
reed@google.com63d73742012-01-10 15:33:12 +00001602void SkPath::reverseAddPath(const SkPath& src) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001603 SkPathRef::Editor ed(&fPathRef, src.fPathRef->countPoints(), src.fPathRef->countVerbs());
reed@google.com63d73742012-01-10 15:33:12 +00001604
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001605 const SkPoint* pts = src.fPathRef->pointsEnd();
1606 // we will iterator through src's verbs backwards
1607 const uint8_t* verbs = src.fPathRef->verbsMemBegin(); // points at the last verb
1608 const uint8_t* verbsEnd = src.fPathRef->verbs(); // points just past the first verb
reed@google.com277c3f82013-05-31 15:17:50 +00001609 const SkScalar* conicWeights = src.fPathRef->conicWeightsEnd();
reed@google.com63d73742012-01-10 15:33:12 +00001610
1611 bool needMove = true;
1612 bool needClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001613 while (verbs < verbsEnd) {
1614 uint8_t v = *(verbs++);
reed@google.com277c3f82013-05-31 15:17:50 +00001615 int n = pts_in_verb(v);
reed@google.com63d73742012-01-10 15:33:12 +00001616
1617 if (needMove) {
1618 --pts;
1619 this->moveTo(pts->fX, pts->fY);
1620 needMove = false;
1621 }
1622 pts -= n;
1623 switch (v) {
1624 case kMove_Verb:
1625 if (needClose) {
1626 this->close();
1627 needClose = false;
1628 }
1629 needMove = true;
1630 pts += 1; // so we see the point in "if (needMove)" above
1631 break;
1632 case kLine_Verb:
1633 this->lineTo(pts[0]);
1634 break;
1635 case kQuad_Verb:
1636 this->quadTo(pts[1], pts[0]);
1637 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001638 case kConic_Verb:
1639 this->conicTo(pts[1], pts[0], *--conicWeights);
1640 break;
reed@google.com63d73742012-01-10 15:33:12 +00001641 case kCubic_Verb:
1642 this->cubicTo(pts[2], pts[1], pts[0]);
1643 break;
1644 case kClose_Verb:
1645 needClose = true;
1646 break;
1647 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00001648 SkDEBUGFAIL("unexpected verb");
reed@google.com63d73742012-01-10 15:33:12 +00001649 }
1650 }
1651}
1652
reed@android.com8a1c16f2008-12-17 15:59:43 +00001653///////////////////////////////////////////////////////////////////////////////
1654
1655void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
1656 SkMatrix matrix;
1657
1658 matrix.setTranslate(dx, dy);
1659 this->transform(matrix, dst);
1660}
1661
reed@android.com8a1c16f2008-12-17 15:59:43 +00001662static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
1663 int level = 2) {
1664 if (--level >= 0) {
1665 SkPoint tmp[7];
1666
1667 SkChopCubicAtHalf(pts, tmp);
1668 subdivide_cubic_to(path, &tmp[0], level);
1669 subdivide_cubic_to(path, &tmp[3], level);
1670 } else {
1671 path->cubicTo(pts[1], pts[2], pts[3]);
1672 }
1673}
1674
1675void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
1676 SkDEBUGCODE(this->validate();)
halcanary96fcdcc2015-08-27 07:41:13 -07001677 if (dst == nullptr) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001678 dst = (SkPath*)this;
1679 }
1680
tomhudson@google.com8d430182011-06-06 19:11:19 +00001681 if (matrix.hasPerspective()) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001682 SkPath tmp;
1683 tmp.fFillType = fFillType;
1684
1685 SkPath::Iter iter(*this, false);
1686 SkPoint pts[4];
1687 SkPath::Verb verb;
1688
reed@google.com4a3b7142012-05-16 17:16:46 +00001689 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001690 switch (verb) {
1691 case kMove_Verb:
1692 tmp.moveTo(pts[0]);
1693 break;
1694 case kLine_Verb:
1695 tmp.lineTo(pts[1]);
1696 break;
1697 case kQuad_Verb:
reed220f9262014-12-17 08:21:04 -08001698 // promote the quad to a conic
1699 tmp.conicTo(pts[1], pts[2],
1700 SkConic::TransformW(pts, SK_Scalar1, matrix));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001701 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001702 case kConic_Verb:
reed220f9262014-12-17 08:21:04 -08001703 tmp.conicTo(pts[1], pts[2],
1704 SkConic::TransformW(pts, iter.conicWeight(), matrix));
reed@google.com277c3f82013-05-31 15:17:50 +00001705 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001706 case kCubic_Verb:
1707 subdivide_cubic_to(&tmp, pts);
1708 break;
1709 case kClose_Verb:
1710 tmp.close();
1711 break;
1712 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001713 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001714 break;
1715 }
1716 }
1717
1718 dst->swap(tmp);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001719 SkPathRef::Editor ed(&dst->fPathRef);
1720 matrix.mapPoints(ed.points(), ed.pathRef()->countPoints());
reed026beb52015-06-10 14:23:15 -07001721 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001722 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001723 SkPathRef::CreateTransformedCopy(&dst->fPathRef, *fPathRef.get(), matrix);
1724
reed@android.com8a1c16f2008-12-17 15:59:43 +00001725 if (this != dst) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001726 dst->fFillType = fFillType;
reed@google.com2a6f8ab2011-10-25 18:41:23 +00001727 dst->fConvexity = fConvexity;
jvanverthb3eb6872014-10-24 07:12:51 -07001728 dst->fIsVolatile = fIsVolatile;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001729 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001730
reed026beb52015-06-10 14:23:15 -07001731 if (SkPathPriv::kUnknown_FirstDirection == fFirstDirection) {
1732 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001733 } else {
1734 SkScalar det2x2 =
1735 SkScalarMul(matrix.get(SkMatrix::kMScaleX), matrix.get(SkMatrix::kMScaleY)) -
1736 SkScalarMul(matrix.get(SkMatrix::kMSkewX), matrix.get(SkMatrix::kMSkewY));
1737 if (det2x2 < 0) {
herb9f4dbca2015-09-28 11:05:47 -07001738 dst->fFirstDirection = SkPathPriv::OppositeFirstDirection(
1739 (SkPathPriv::FirstDirection)fFirstDirection.load());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001740 } else if (det2x2 > 0) {
herb9f4dbca2015-09-28 11:05:47 -07001741 dst->fFirstDirection = fFirstDirection.load();
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001742 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001743 dst->fConvexity = kUnknown_Convexity;
reed026beb52015-06-10 14:23:15 -07001744 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001745 }
1746 }
1747
reed@android.com8a1c16f2008-12-17 15:59:43 +00001748 SkDEBUGCODE(dst->validate();)
1749 }
1750}
1751
reed@android.com8a1c16f2008-12-17 15:59:43 +00001752///////////////////////////////////////////////////////////////////////////////
1753///////////////////////////////////////////////////////////////////////////////
1754
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001755enum SegmentState {
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001756 kEmptyContour_SegmentState, // The current contour is empty. We may be
1757 // starting processing or we may have just
1758 // closed a contour.
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001759 kAfterMove_SegmentState, // We have seen a move, but nothing else.
1760 kAfterPrimitive_SegmentState // We have seen a primitive but not yet
1761 // closed the path. Also the initial state.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001762};
1763
1764SkPath::Iter::Iter() {
1765#ifdef SK_DEBUG
halcanary96fcdcc2015-08-27 07:41:13 -07001766 fPts = nullptr;
1767 fConicWeights = nullptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001768 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001769 fForceClose = fCloseLine = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001770 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001771#endif
1772 // need to init enough to make next() harmlessly return kDone_Verb
halcanary96fcdcc2015-08-27 07:41:13 -07001773 fVerbs = nullptr;
1774 fVerbStop = nullptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001775 fNeedClose = false;
1776}
1777
1778SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
1779 this->setPath(path, forceClose);
1780}
1781
1782void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001783 fPts = path.fPathRef->points();
1784 fVerbs = path.fPathRef->verbs();
1785 fVerbStop = path.fPathRef->verbsMemBegin();
reed@google.com277c3f82013-05-31 15:17:50 +00001786 fConicWeights = path.fPathRef->conicWeights() - 1; // begin one behind
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001787 fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org72785c42011-12-29 21:03:28 +00001788 fMoveTo.fX = fMoveTo.fY = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001789 fForceClose = SkToU8(forceClose);
1790 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001791 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001792}
1793
1794bool SkPath::Iter::isClosedContour() const {
halcanary96fcdcc2015-08-27 07:41:13 -07001795 if (fVerbs == nullptr || fVerbs == fVerbStop) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001796 return false;
1797 }
1798 if (fForceClose) {
1799 return true;
1800 }
1801
1802 const uint8_t* verbs = fVerbs;
1803 const uint8_t* stop = fVerbStop;
reed@google.comabf15c12011-01-18 20:35:51 +00001804
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001805 if (kMove_Verb == *(verbs - 1)) {
1806 verbs -= 1; // skip the initial moveto
reed@android.com8a1c16f2008-12-17 15:59:43 +00001807 }
1808
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001809 while (verbs > stop) {
1810 // verbs points one beyond the current verb, decrement first.
1811 unsigned v = *(--verbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001812 if (kMove_Verb == v) {
1813 break;
1814 }
1815 if (kClose_Verb == v) {
1816 return true;
1817 }
1818 }
1819 return false;
1820}
1821
1822SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001823 SkASSERT(pts);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001824 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001825 // A special case: if both points are NaN, SkPoint::operation== returns
1826 // false, but the iterator expects that they are treated as the same.
1827 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001828 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1829 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001830 return kClose_Verb;
1831 }
1832
reed@google.com9e25dbf2012-05-15 17:05:38 +00001833 pts[0] = fLastPt;
1834 pts[1] = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001835 fLastPt = fMoveTo;
1836 fCloseLine = true;
1837 return kLine_Verb;
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +00001838 } else {
1839 pts[0] = fMoveTo;
1840 return kClose_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001841 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001842}
1843
reed@google.com9e25dbf2012-05-15 17:05:38 +00001844const SkPoint& SkPath::Iter::cons_moveTo() {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001845 if (fSegmentState == kAfterMove_SegmentState) {
1846 // Set the first return pt to the move pt
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001847 fSegmentState = kAfterPrimitive_SegmentState;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001848 return fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001849 } else {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001850 SkASSERT(fSegmentState == kAfterPrimitive_SegmentState);
1851 // Set the first return pt to the last pt of the previous primitive.
reed@google.com9e25dbf2012-05-15 17:05:38 +00001852 return fPts[-1];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001853 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001854}
1855
caryclarke8c56662015-07-14 11:19:26 -07001856void SkPath::Iter::consumeDegenerateSegments(bool exact) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001857 // We need to step over anything that will not move the current draw point
1858 // forward before the next move is seen
1859 const uint8_t* lastMoveVerb = 0;
1860 const SkPoint* lastMovePt = 0;
halcanary96fcdcc2015-08-27 07:41:13 -07001861 const SkScalar* lastMoveWeight = nullptr;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001862 SkPoint lastPt = fLastPt;
1863 while (fVerbs != fVerbStop) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001864 unsigned verb = *(fVerbs - 1); // fVerbs is one beyond the current verb
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001865 switch (verb) {
1866 case kMove_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001867 // Keep a record of this most recent move
1868 lastMoveVerb = fVerbs;
1869 lastMovePt = fPts;
robertphillipsb8de1f42015-02-23 11:17:01 -08001870 lastMoveWeight = fConicWeights;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001871 lastPt = fPts[0];
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001872 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001873 fPts++;
1874 break;
1875
1876 case kClose_Verb:
schenney@chromium.org7e963602012-06-13 17:05:43 +00001877 // A close when we are in a segment is always valid except when it
1878 // follows a move which follows a segment.
1879 if (fSegmentState == kAfterPrimitive_SegmentState && !lastMoveVerb) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001880 return;
1881 }
1882 // A close at any other time must be ignored
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001883 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001884 break;
1885
1886 case kLine_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001887 if (!IsLineDegenerate(lastPt, fPts[0], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001888 if (lastMoveVerb) {
1889 fVerbs = lastMoveVerb;
1890 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001891 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001892 return;
1893 }
1894 return;
1895 }
1896 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001897 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001898 fPts++;
1899 break;
1900
reed@google.com277c3f82013-05-31 15:17:50 +00001901 case kConic_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001902 case kQuad_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001903 if (!IsQuadDegenerate(lastPt, fPts[0], fPts[1], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001904 if (lastMoveVerb) {
1905 fVerbs = lastMoveVerb;
1906 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001907 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001908 return;
1909 }
1910 return;
1911 }
1912 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001913 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001914 fPts += 2;
reed@google.com277c3f82013-05-31 15:17:50 +00001915 fConicWeights += (kConic_Verb == verb);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001916 break;
1917
1918 case kCubic_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001919 if (!IsCubicDegenerate(lastPt, fPts[0], fPts[1], fPts[2], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001920 if (lastMoveVerb) {
1921 fVerbs = lastMoveVerb;
1922 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001923 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001924 return;
1925 }
1926 return;
1927 }
1928 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001929 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001930 fPts += 3;
1931 break;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001932
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001933 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001934 SkDEBUGFAIL("Should never see kDone_Verb");
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001935 }
1936 }
1937}
1938
reed@google.com4a3b7142012-05-16 17:16:46 +00001939SkPath::Verb SkPath::Iter::doNext(SkPoint ptsParam[4]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001940 SkASSERT(ptsParam);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001941
reed@android.com8a1c16f2008-12-17 15:59:43 +00001942 if (fVerbs == fVerbStop) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001943 // Close the curve if requested and if there is some curve to close
1944 if (fNeedClose && fSegmentState == kAfterPrimitive_SegmentState) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001945 if (kLine_Verb == this->autoClose(ptsParam)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001946 return kLine_Verb;
1947 }
1948 fNeedClose = false;
1949 return kClose_Verb;
1950 }
1951 return kDone_Verb;
1952 }
1953
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001954 // fVerbs is one beyond the current verb, decrement first
1955 unsigned verb = *(--fVerbs);
reed@google.com9e25dbf2012-05-15 17:05:38 +00001956 const SkPoint* SK_RESTRICT srcPts = fPts;
1957 SkPoint* SK_RESTRICT pts = ptsParam;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001958
1959 switch (verb) {
1960 case kMove_Verb:
1961 if (fNeedClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001962 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001963 verb = this->autoClose(pts);
1964 if (verb == kClose_Verb) {
1965 fNeedClose = false;
1966 }
1967 return (Verb)verb;
1968 }
1969 if (fVerbs == fVerbStop) { // might be a trailing moveto
1970 return kDone_Verb;
1971 }
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001972 fMoveTo = *srcPts;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001973 pts[0] = *srcPts;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001974 srcPts += 1;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001975 fSegmentState = kAfterMove_SegmentState;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001976 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001977 fNeedClose = fForceClose;
1978 break;
1979 case kLine_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001980 pts[0] = this->cons_moveTo();
1981 pts[1] = srcPts[0];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001982 fLastPt = srcPts[0];
1983 fCloseLine = false;
1984 srcPts += 1;
1985 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001986 case kConic_Verb:
1987 fConicWeights += 1;
1988 // fall-through
reed@android.com8a1c16f2008-12-17 15:59:43 +00001989 case kQuad_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001990 pts[0] = this->cons_moveTo();
1991 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001992 fLastPt = srcPts[1];
1993 srcPts += 2;
1994 break;
1995 case kCubic_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001996 pts[0] = this->cons_moveTo();
1997 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001998 fLastPt = srcPts[2];
1999 srcPts += 3;
2000 break;
2001 case kClose_Verb:
2002 verb = this->autoClose(pts);
2003 if (verb == kLine_Verb) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002004 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00002005 } else {
2006 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00002007 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002008 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002009 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002010 break;
2011 }
2012 fPts = srcPts;
2013 return (Verb)verb;
2014}
2015
2016///////////////////////////////////////////////////////////////////////////////
2017
reed@android.com8a1c16f2008-12-17 15:59:43 +00002018/*
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002019 Format in compressed buffer: [ptCount, verbCount, pts[], verbs[]]
reed@android.com8a1c16f2008-12-17 15:59:43 +00002020*/
2021
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002022size_t SkPath::writeToMemory(void* storage) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002023 SkDEBUGCODE(this->validate();)
2024
halcanary96fcdcc2015-08-27 07:41:13 -07002025 if (nullptr == storage) {
caryclark69006412016-02-17 12:16:27 -08002026 const int byteCount = sizeof(int32_t) * 2 + fPathRef->writeSize();
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002027 return SkAlign4(byteCount);
2028 }
2029
2030 SkWBuffer buffer(storage);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002031
robertphillips@google.com466310d2013-12-03 16:43:54 +00002032 int32_t packed = (fConvexity << kConvexity_SerializationShift) |
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002033 (fFillType << kFillType_SerializationShift) |
reed026beb52015-06-10 14:23:15 -07002034 (fFirstDirection << kDirection_SerializationShift) |
reed8f086022015-06-11 14:22:19 -07002035 (fIsVolatile << kIsVolatile_SerializationShift) |
2036 kCurrent_Version;
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002037
robertphillips@google.com2972bb52012-08-07 17:32:51 +00002038 buffer.write32(packed);
caryclark69006412016-02-17 12:16:27 -08002039 buffer.write32(fLastMoveToIndex);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002040
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002041 fPathRef->writeToBuffer(&buffer);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002042
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002043 buffer.padToAlign4();
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002044 return buffer.pos();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002045}
2046
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002047size_t SkPath::readFromMemory(const void* storage, size_t length) {
2048 SkRBufferWithSizeCheck buffer(storage, length);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002049
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00002050 int32_t packed;
2051 if (!buffer.readS32(&packed)) {
2052 return 0;
2053 }
2054
reed8f086022015-06-11 14:22:19 -07002055 unsigned version = packed & 0xFF;
caryclark69006412016-02-17 12:16:27 -08002056 if (version >= kPathPrivLastMoveToIndex_Version && !buffer.readS32(&fLastMoveToIndex)) {
2057 return 0;
2058 }
mtklein1b249332015-07-07 12:21:21 -07002059
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002060 fConvexity = (packed >> kConvexity_SerializationShift) & 0xFF;
2061 fFillType = (packed >> kFillType_SerializationShift) & 0xFF;
reed8f086022015-06-11 14:22:19 -07002062 uint8_t dir = (packed >> kDirection_SerializationShift) & 0x3;
jvanverthb3eb6872014-10-24 07:12:51 -07002063 fIsVolatile = (packed >> kIsVolatile_SerializationShift) & 0x1;
commit-bot@chromium.orgfed2ab62014-01-23 15:16:05 +00002064 SkPathRef* pathRef = SkPathRef::CreateFromBuffer(&buffer);
ajumaf8aec582016-01-13 13:46:31 -08002065 if (!pathRef) {
2066 return 0;
2067 }
2068
2069 fPathRef.reset(pathRef);
2070 SkDEBUGCODE(this->validate();)
2071 buffer.skipToAlign4();
reed@google.comabf15c12011-01-18 20:35:51 +00002072
reed8f086022015-06-11 14:22:19 -07002073 // compatibility check
2074 if (version < kPathPrivFirstDirection_Version) {
2075 switch (dir) { // old values
2076 case 0:
2077 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
2078 break;
2079 case 1:
2080 fFirstDirection = SkPathPriv::kCW_FirstDirection;
2081 break;
2082 case 2:
2083 fFirstDirection = SkPathPriv::kCCW_FirstDirection;
2084 break;
2085 default:
2086 SkASSERT(false);
2087 }
2088 } else {
2089 fFirstDirection = dir;
2090 }
2091
ajumaf8aec582016-01-13 13:46:31 -08002092 return buffer.pos();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002093}
2094
2095///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00002096
reede05fed02014-12-15 07:59:53 -08002097#include "SkStringUtils.h"
caryclark66a5d8b2014-06-24 08:30:15 -07002098#include "SkStream.h"
reed@google.com51bbe752013-01-17 22:07:50 +00002099
reed@google.com51bbe752013-01-17 22:07:50 +00002100static void append_params(SkString* str, const char label[], const SkPoint pts[],
reede05fed02014-12-15 07:59:53 -08002101 int count, SkScalarAsStringType strType, SkScalar conicWeight = -1) {
reed@google.com51bbe752013-01-17 22:07:50 +00002102 str->append(label);
2103 str->append("(");
skia.committer@gmail.com15dd3002013-01-18 07:07:28 +00002104
reed@google.com51bbe752013-01-17 22:07:50 +00002105 const SkScalar* values = &pts[0].fX;
2106 count *= 2;
2107
2108 for (int i = 0; i < count; ++i) {
reede05fed02014-12-15 07:59:53 -08002109 SkAppendScalar(str, values[i], strType);
reed@google.com51bbe752013-01-17 22:07:50 +00002110 if (i < count - 1) {
2111 str->append(", ");
2112 }
2113 }
reed@google.com277c3f82013-05-31 15:17:50 +00002114 if (conicWeight >= 0) {
2115 str->append(", ");
reede05fed02014-12-15 07:59:53 -08002116 SkAppendScalar(str, conicWeight, strType);
reed@google.com277c3f82013-05-31 15:17:50 +00002117 }
caryclark08fa28c2014-10-23 13:08:56 -07002118 str->append(");");
reede05fed02014-12-15 07:59:53 -08002119 if (kHex_SkScalarAsStringType == strType) {
caryclark08fa28c2014-10-23 13:08:56 -07002120 str->append(" // ");
2121 for (int i = 0; i < count; ++i) {
reede05fed02014-12-15 07:59:53 -08002122 SkAppendScalarDec(str, values[i]);
caryclark08fa28c2014-10-23 13:08:56 -07002123 if (i < count - 1) {
2124 str->append(", ");
2125 }
2126 }
2127 if (conicWeight >= 0) {
2128 str->append(", ");
reede05fed02014-12-15 07:59:53 -08002129 SkAppendScalarDec(str, conicWeight);
caryclark08fa28c2014-10-23 13:08:56 -07002130 }
2131 }
2132 str->append("\n");
reed@google.com51bbe752013-01-17 22:07:50 +00002133}
2134
caryclarke9562592014-09-15 09:26:09 -07002135void SkPath::dump(SkWStream* wStream, bool forceClose, bool dumpAsHex) const {
reede05fed02014-12-15 07:59:53 -08002136 SkScalarAsStringType asType = dumpAsHex ? kHex_SkScalarAsStringType : kDec_SkScalarAsStringType;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002137 Iter iter(*this, forceClose);
2138 SkPoint pts[4];
2139 Verb verb;
2140
caryclark66a5d8b2014-06-24 08:30:15 -07002141 if (!wStream) {
2142 SkDebugf("path: forceClose=%s\n", forceClose ? "true" : "false");
2143 }
reed@google.com51bbe752013-01-17 22:07:50 +00002144 SkString builder;
2145
reed@google.com4a3b7142012-05-16 17:16:46 +00002146 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002147 switch (verb) {
2148 case kMove_Verb:
reede05fed02014-12-15 07:59:53 -08002149 append_params(&builder, "path.moveTo", &pts[0], 1, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002150 break;
2151 case kLine_Verb:
reede05fed02014-12-15 07:59:53 -08002152 append_params(&builder, "path.lineTo", &pts[1], 1, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002153 break;
2154 case kQuad_Verb:
reede05fed02014-12-15 07:59:53 -08002155 append_params(&builder, "path.quadTo", &pts[1], 2, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002156 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002157 case kConic_Verb:
reede05fed02014-12-15 07:59:53 -08002158 append_params(&builder, "path.conicTo", &pts[1], 2, asType, iter.conicWeight());
reed@google.com277c3f82013-05-31 15:17:50 +00002159 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002160 case kCubic_Verb:
reede05fed02014-12-15 07:59:53 -08002161 append_params(&builder, "path.cubicTo", &pts[1], 3, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002162 break;
2163 case kClose_Verb:
caryclark66a5d8b2014-06-24 08:30:15 -07002164 builder.append("path.close();\n");
reed@android.com8a1c16f2008-12-17 15:59:43 +00002165 break;
2166 default:
2167 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
2168 verb = kDone_Verb; // stop the loop
2169 break;
2170 }
caryclark1049f122015-04-20 08:31:59 -07002171 if (!wStream && builder.size()) {
2172 SkDebugf("%s", builder.c_str());
2173 builder.reset();
2174 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002175 }
caryclark66a5d8b2014-06-24 08:30:15 -07002176 if (wStream) {
2177 wStream->writeText(builder.c_str());
caryclark66a5d8b2014-06-24 08:30:15 -07002178 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002179}
2180
reed@android.come522ca52009-11-23 20:10:41 +00002181void SkPath::dump() const {
halcanary96fcdcc2015-08-27 07:41:13 -07002182 this->dump(nullptr, false, false);
caryclarke9562592014-09-15 09:26:09 -07002183}
2184
2185void SkPath::dumpHex() const {
halcanary96fcdcc2015-08-27 07:41:13 -07002186 this->dump(nullptr, false, true);
reed@android.come522ca52009-11-23 20:10:41 +00002187}
2188
2189#ifdef SK_DEBUG
2190void SkPath::validate() const {
reed@android.come522ca52009-11-23 20:10:41 +00002191 SkASSERT((fFillType & ~3) == 0);
reed@google.comabf15c12011-01-18 20:35:51 +00002192
djsollen@google.com077348c2012-10-22 20:23:32 +00002193#ifdef SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002194 if (!fBoundsIsDirty) {
2195 SkRect bounds;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002196
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002197 bool isFinite = compute_pt_bounds(&bounds, *fPathRef.get());
robertphillips@google.com5d8d1862012-08-15 14:36:41 +00002198 SkASSERT(SkToBool(fIsFinite) == isFinite);
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002199
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002200 if (fPathRef->countPoints() <= 1) {
reed@android.come522ca52009-11-23 20:10:41 +00002201 // if we're empty, fBounds may be empty but translated, so we can't
2202 // necessarily compare to bounds directly
2203 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
2204 // be [2, 2, 2, 2]
2205 SkASSERT(bounds.isEmpty());
2206 SkASSERT(fBounds.isEmpty());
2207 } else {
reed@google.comeac52bd2011-11-14 18:13:59 +00002208 if (bounds.isEmpty()) {
2209 SkASSERT(fBounds.isEmpty());
2210 } else {
reed@google.com3563c9e2011-11-14 19:34:57 +00002211 if (!fBounds.isEmpty()) {
2212 SkASSERT(fBounds.contains(bounds));
2213 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002214 }
reed@android.come522ca52009-11-23 20:10:41 +00002215 }
2216 }
djsollen@google.com077348c2012-10-22 20:23:32 +00002217#endif // SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002218}
djsollen@google.com077348c2012-10-22 20:23:32 +00002219#endif // SK_DEBUG
reed@android.come522ca52009-11-23 20:10:41 +00002220
reed@google.com04863fa2011-05-15 04:08:24 +00002221///////////////////////////////////////////////////////////////////////////////
2222
reed@google.com0b7b9822011-05-16 12:29:27 +00002223static int sign(SkScalar x) { return x < 0; }
2224#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00002225
robertphillipsc506e302014-09-16 09:43:31 -07002226enum DirChange {
2227 kLeft_DirChange,
2228 kRight_DirChange,
2229 kStraight_DirChange,
2230 kBackwards_DirChange,
2231
2232 kInvalid_DirChange
2233};
2234
2235
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002236static bool almost_equal(SkScalar compA, SkScalar compB) {
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002237 // The error epsilon was empirically derived; worse case round rects
2238 // with a mid point outset by 2x float epsilon in tests had an error
2239 // of 12.
2240 const int epsilon = 16;
2241 if (!SkScalarIsFinite(compA) || !SkScalarIsFinite(compB)) {
2242 return false;
2243 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002244 // no need to check for small numbers because SkPath::Iter has removed degenerate values
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002245 int aBits = SkFloatAs2sCompliment(compA);
2246 int bBits = SkFloatAs2sCompliment(compB);
2247 return aBits < bBits + epsilon && bBits < aBits + epsilon;
reed@google.com04863fa2011-05-15 04:08:24 +00002248}
2249
caryclarkb4216502015-03-02 13:02:34 -08002250static bool approximately_zero_when_compared_to(double x, double y) {
2251 return x == 0 || fabs(x) < fabs(y * FLT_EPSILON);
robertphillipsc506e302014-09-16 09:43:31 -07002252}
2253
caryclarkb4216502015-03-02 13:02:34 -08002254
reed@google.com04863fa2011-05-15 04:08:24 +00002255// only valid for a single contour
2256struct Convexicator {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002257 Convexicator()
2258 : fPtCount(0)
2259 , fConvexity(SkPath::kConvex_Convexity)
reed026beb52015-06-10 14:23:15 -07002260 , fFirstDirection(SkPathPriv::kUnknown_FirstDirection)
caryclark5ccef572015-03-02 10:07:56 -08002261 , fIsFinite(true)
2262 , fIsCurve(false) {
robertphillipsc506e302014-09-16 09:43:31 -07002263 fExpectedDir = kInvalid_DirChange;
reed@google.com04863fa2011-05-15 04:08:24 +00002264 // warnings
djsollen2f124632016-04-29 13:53:05 -07002265 fPriorPt.set(0,0);
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002266 fLastPt.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002267 fCurrPt.set(0, 0);
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002268 fLastVec.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002269 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00002270
2271 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00002272 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00002273 }
2274
2275 SkPath::Convexity getConvexity() const { return fConvexity; }
2276
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002277 /** The direction returned is only valid if the path is determined convex */
reed026beb52015-06-10 14:23:15 -07002278 SkPathPriv::FirstDirection getFirstDirection() const { return fFirstDirection; }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002279
reed@google.com04863fa2011-05-15 04:08:24 +00002280 void addPt(const SkPoint& pt) {
caryclarkd3d1a982014-12-08 04:57:38 -08002281 if (SkPath::kConcave_Convexity == fConvexity || !fIsFinite) {
reed@google.com04863fa2011-05-15 04:08:24 +00002282 return;
2283 }
2284
2285 if (0 == fPtCount) {
2286 fCurrPt = pt;
2287 ++fPtCount;
2288 } else {
2289 SkVector vec = pt - fCurrPt;
caryclarkd3d1a982014-12-08 04:57:38 -08002290 SkScalar lengthSqd = vec.lengthSqd();
2291 if (!SkScalarIsFinite(lengthSqd)) {
2292 fIsFinite = false;
caryclarke8c56662015-07-14 11:19:26 -07002293 } else if (lengthSqd) {
caryclarkb4216502015-03-02 13:02:34 -08002294 fPriorPt = fLastPt;
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002295 fLastPt = fCurrPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002296 fCurrPt = pt;
2297 if (++fPtCount == 2) {
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002298 fFirstVec = fLastVec = vec;
reed@google.com04863fa2011-05-15 04:08:24 +00002299 } else {
2300 SkASSERT(fPtCount > 2);
2301 this->addVec(vec);
2302 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002303
reed@google.com85b6e392011-05-15 20:25:17 +00002304 int sx = sign(vec.fX);
2305 int sy = sign(vec.fY);
2306 fDx += (sx != fSx);
2307 fDy += (sy != fSy);
2308 fSx = sx;
2309 fSy = sy;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002310
reed@google.com85b6e392011-05-15 20:25:17 +00002311 if (fDx > 3 || fDy > 3) {
2312 fConvexity = SkPath::kConcave_Convexity;
2313 }
reed@google.com04863fa2011-05-15 04:08:24 +00002314 }
2315 }
2316 }
2317
2318 void close() {
2319 if (fPtCount > 2) {
2320 this->addVec(fFirstVec);
2321 }
2322 }
2323
caryclarkb4216502015-03-02 13:02:34 -08002324 DirChange directionChange(const SkVector& curVec) {
2325 SkScalar cross = SkPoint::CrossProduct(fLastVec, curVec);
2326
2327 SkScalar smallest = SkTMin(fCurrPt.fX, SkTMin(fCurrPt.fY, SkTMin(fLastPt.fX, fLastPt.fY)));
2328 SkScalar largest = SkTMax(fCurrPt.fX, SkTMax(fCurrPt.fY, SkTMax(fLastPt.fX, fLastPt.fY)));
2329 largest = SkTMax(largest, -smallest);
2330
2331 if (!almost_equal(largest, largest + cross)) {
2332 int sign = SkScalarSignAsInt(cross);
2333 if (sign) {
2334 return (1 == sign) ? kRight_DirChange : kLeft_DirChange;
2335 }
2336 }
2337
2338 if (cross) {
2339 double dLastVecX = SkScalarToDouble(fLastPt.fX) - SkScalarToDouble(fPriorPt.fX);
2340 double dLastVecY = SkScalarToDouble(fLastPt.fY) - SkScalarToDouble(fPriorPt.fY);
2341 double dCurrVecX = SkScalarToDouble(fCurrPt.fX) - SkScalarToDouble(fLastPt.fX);
2342 double dCurrVecY = SkScalarToDouble(fCurrPt.fY) - SkScalarToDouble(fLastPt.fY);
2343 double dCross = dLastVecX * dCurrVecY - dLastVecY * dCurrVecX;
2344 if (!approximately_zero_when_compared_to(dCross, SkScalarToDouble(largest))) {
2345 int sign = SkScalarSignAsInt(SkDoubleToScalar(dCross));
2346 if (sign) {
2347 return (1 == sign) ? kRight_DirChange : kLeft_DirChange;
2348 }
2349 }
2350 }
2351
2352 if (!SkScalarNearlyZero(fLastVec.lengthSqd(), SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
2353 !SkScalarNearlyZero(curVec.lengthSqd(), SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
2354 fLastVec.dot(curVec) < 0.0f) {
2355 return kBackwards_DirChange;
2356 }
2357
2358 return kStraight_DirChange;
2359 }
2360
2361
caryclarkd3d1a982014-12-08 04:57:38 -08002362 bool isFinite() const {
2363 return fIsFinite;
2364 }
2365
caryclark5ccef572015-03-02 10:07:56 -08002366 void setCurve(bool isCurve) {
2367 fIsCurve = isCurve;
2368 }
2369
reed@google.com04863fa2011-05-15 04:08:24 +00002370private:
2371 void addVec(const SkVector& vec) {
2372 SkASSERT(vec.fX || vec.fY);
caryclarkb4216502015-03-02 13:02:34 -08002373 DirChange dir = this->directionChange(vec);
robertphillipsc506e302014-09-16 09:43:31 -07002374 switch (dir) {
2375 case kLeft_DirChange: // fall through
2376 case kRight_DirChange:
2377 if (kInvalid_DirChange == fExpectedDir) {
2378 fExpectedDir = dir;
reed026beb52015-06-10 14:23:15 -07002379 fFirstDirection = (kRight_DirChange == dir) ? SkPathPriv::kCW_FirstDirection
2380 : SkPathPriv::kCCW_FirstDirection;
robertphillipsc506e302014-09-16 09:43:31 -07002381 } else if (dir != fExpectedDir) {
2382 fConvexity = SkPath::kConcave_Convexity;
reed026beb52015-06-10 14:23:15 -07002383 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
robertphillipsc506e302014-09-16 09:43:31 -07002384 }
2385 fLastVec = vec;
2386 break;
2387 case kStraight_DirChange:
2388 break;
2389 case kBackwards_DirChange:
caryclark5ccef572015-03-02 10:07:56 -08002390 if (fIsCurve) {
2391 fConvexity = SkPath::kConcave_Convexity;
reed026beb52015-06-10 14:23:15 -07002392 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
caryclark5ccef572015-03-02 10:07:56 -08002393 }
robertphillipsc506e302014-09-16 09:43:31 -07002394 fLastVec = vec;
2395 break;
2396 case kInvalid_DirChange:
2397 SkFAIL("Use of invalid direction change flag");
2398 break;
reed@google.com04863fa2011-05-15 04:08:24 +00002399 }
2400 }
2401
caryclarkb4216502015-03-02 13:02:34 -08002402 SkPoint fPriorPt;
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002403 SkPoint fLastPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002404 SkPoint fCurrPt;
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002405 // fLastVec does not necessarily start at fLastPt. We only advance it when the cross product
2406 // value with the current vec is deemed to be of a significant value.
2407 SkVector fLastVec, fFirstVec;
reed@google.com04863fa2011-05-15 04:08:24 +00002408 int fPtCount; // non-degenerate points
robertphillipsc506e302014-09-16 09:43:31 -07002409 DirChange fExpectedDir;
reed@google.com04863fa2011-05-15 04:08:24 +00002410 SkPath::Convexity fConvexity;
reed026beb52015-06-10 14:23:15 -07002411 SkPathPriv::FirstDirection fFirstDirection;
reed@google.com0b7b9822011-05-16 12:29:27 +00002412 int fDx, fDy, fSx, fSy;
caryclarkd3d1a982014-12-08 04:57:38 -08002413 bool fIsFinite;
caryclark5ccef572015-03-02 10:07:56 -08002414 bool fIsCurve;
reed@google.com04863fa2011-05-15 04:08:24 +00002415};
2416
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002417SkPath::Convexity SkPath::internalGetConvexity() const {
2418 SkASSERT(kUnknown_Convexity == fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002419 SkPoint pts[4];
2420 SkPath::Verb verb;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002421 SkPath::Iter iter(*this, true);
reed@google.com04863fa2011-05-15 04:08:24 +00002422
2423 int contourCount = 0;
2424 int count;
2425 Convexicator state;
2426
caryclarkd3d1a982014-12-08 04:57:38 -08002427 if (!isFinite()) {
2428 return kUnknown_Convexity;
2429 }
caryclarke8c56662015-07-14 11:19:26 -07002430 while ((verb = iter.next(pts, true, true)) != SkPath::kDone_Verb) {
reed@google.com04863fa2011-05-15 04:08:24 +00002431 switch (verb) {
2432 case kMove_Verb:
2433 if (++contourCount > 1) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002434 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002435 return kConcave_Convexity;
2436 }
2437 pts[1] = pts[0];
caryclark5ccef572015-03-02 10:07:56 -08002438 // fall through
2439 case kLine_Verb:
reed@google.com04863fa2011-05-15 04:08:24 +00002440 count = 1;
caryclark5ccef572015-03-02 10:07:56 -08002441 state.setCurve(false);
reed@google.com04863fa2011-05-15 04:08:24 +00002442 break;
caryclark5ccef572015-03-02 10:07:56 -08002443 case kQuad_Verb:
2444 // fall through
2445 case kConic_Verb:
2446 // fall through
2447 case kCubic_Verb:
2448 count = 2 + (kCubic_Verb == verb);
2449 // As an additional enhancement, this could set curve true only
2450 // if the curve is nonlinear
2451 state.setCurve(true);
2452 break;
reed@google.com04863fa2011-05-15 04:08:24 +00002453 case kClose_Verb:
caryclark5ccef572015-03-02 10:07:56 -08002454 state.setCurve(false);
reed@google.com04863fa2011-05-15 04:08:24 +00002455 state.close();
2456 count = 0;
2457 break;
2458 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00002459 SkDEBUGFAIL("bad verb");
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002460 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002461 return kConcave_Convexity;
2462 }
2463
2464 for (int i = 1; i <= count; i++) {
2465 state.addPt(pts[i]);
2466 }
2467 // early exit
caryclarkd3d1a982014-12-08 04:57:38 -08002468 if (!state.isFinite()) {
2469 return kUnknown_Convexity;
2470 }
reed@google.com04863fa2011-05-15 04:08:24 +00002471 if (kConcave_Convexity == state.getConvexity()) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002472 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002473 return kConcave_Convexity;
2474 }
2475 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002476 fConvexity = state.getConvexity();
reed026beb52015-06-10 14:23:15 -07002477 if (kConvex_Convexity == fConvexity && SkPathPriv::kUnknown_FirstDirection == fFirstDirection) {
2478 fFirstDirection = state.getFirstDirection();
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002479 }
2480 return static_cast<Convexity>(fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002481}
reed@google.com69a99432012-01-10 18:00:10 +00002482
2483///////////////////////////////////////////////////////////////////////////////
2484
2485class ContourIter {
2486public:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002487 ContourIter(const SkPathRef& pathRef);
reed@google.com69a99432012-01-10 18:00:10 +00002488
2489 bool done() const { return fDone; }
2490 // if !done() then these may be called
2491 int count() const { return fCurrPtCount; }
2492 const SkPoint* pts() const { return fCurrPt; }
2493 void next();
2494
2495private:
2496 int fCurrPtCount;
2497 const SkPoint* fCurrPt;
2498 const uint8_t* fCurrVerb;
2499 const uint8_t* fStopVerbs;
reed@google.com277c3f82013-05-31 15:17:50 +00002500 const SkScalar* fCurrConicWeight;
reed@google.com69a99432012-01-10 18:00:10 +00002501 bool fDone;
reed@google.comd1ab9322012-01-10 18:40:03 +00002502 SkDEBUGCODE(int fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002503};
2504
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002505ContourIter::ContourIter(const SkPathRef& pathRef) {
2506 fStopVerbs = pathRef.verbsMemBegin();
reed@google.com69a99432012-01-10 18:00:10 +00002507 fDone = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002508 fCurrPt = pathRef.points();
2509 fCurrVerb = pathRef.verbs();
reed@google.com277c3f82013-05-31 15:17:50 +00002510 fCurrConicWeight = pathRef.conicWeights();
reed@google.com69a99432012-01-10 18:00:10 +00002511 fCurrPtCount = 0;
reed@google.comd1ab9322012-01-10 18:40:03 +00002512 SkDEBUGCODE(fContourCounter = 0;)
reed@google.com69a99432012-01-10 18:00:10 +00002513 this->next();
2514}
2515
2516void ContourIter::next() {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002517 if (fCurrVerb <= fStopVerbs) {
reed@google.com69a99432012-01-10 18:00:10 +00002518 fDone = true;
2519 }
2520 if (fDone) {
2521 return;
2522 }
2523
2524 // skip pts of prev contour
2525 fCurrPt += fCurrPtCount;
2526
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002527 SkASSERT(SkPath::kMove_Verb == fCurrVerb[~0]);
reed@google.com69a99432012-01-10 18:00:10 +00002528 int ptCount = 1; // moveTo
2529 const uint8_t* verbs = fCurrVerb;
2530
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002531 for (--verbs; verbs > fStopVerbs; --verbs) {
2532 switch (verbs[~0]) {
reed@google.com69a99432012-01-10 18:00:10 +00002533 case SkPath::kMove_Verb:
reed@google.com69a99432012-01-10 18:00:10 +00002534 goto CONTOUR_END;
2535 case SkPath::kLine_Verb:
2536 ptCount += 1;
2537 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002538 case SkPath::kConic_Verb:
2539 fCurrConicWeight += 1;
2540 // fall-through
reed@google.com69a99432012-01-10 18:00:10 +00002541 case SkPath::kQuad_Verb:
2542 ptCount += 2;
2543 break;
2544 case SkPath::kCubic_Verb:
2545 ptCount += 3;
2546 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002547 case SkPath::kClose_Verb:
2548 break;
2549 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00002550 SkDEBUGFAIL("unexpected verb");
reed@google.com69a99432012-01-10 18:00:10 +00002551 break;
2552 }
2553 }
2554CONTOUR_END:
2555 fCurrPtCount = ptCount;
2556 fCurrVerb = verbs;
reed@google.comd1ab9322012-01-10 18:40:03 +00002557 SkDEBUGCODE(++fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002558}
2559
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002560// returns cross product of (p1 - p0) and (p2 - p0)
reed@google.com69a99432012-01-10 18:00:10 +00002561static SkScalar cross_prod(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002562 SkScalar cross = SkPoint::CrossProduct(p1 - p0, p2 - p0);
2563 // We may get 0 when the above subtracts underflow. We expect this to be
2564 // very rare and lazily promote to double.
2565 if (0 == cross) {
2566 double p0x = SkScalarToDouble(p0.fX);
2567 double p0y = SkScalarToDouble(p0.fY);
2568
2569 double p1x = SkScalarToDouble(p1.fX);
2570 double p1y = SkScalarToDouble(p1.fY);
2571
2572 double p2x = SkScalarToDouble(p2.fX);
2573 double p2y = SkScalarToDouble(p2.fY);
2574
2575 cross = SkDoubleToScalar((p1x - p0x) * (p2y - p0y) -
2576 (p1y - p0y) * (p2x - p0x));
2577
2578 }
2579 return cross;
reed@google.com69a99432012-01-10 18:00:10 +00002580}
2581
reed@google.comc1ea60a2012-01-31 15:15:36 +00002582// Returns the first pt with the maximum Y coordinate
reed@google.com69a99432012-01-10 18:00:10 +00002583static int find_max_y(const SkPoint pts[], int count) {
2584 SkASSERT(count > 0);
2585 SkScalar max = pts[0].fY;
reed@google.comc1ea60a2012-01-31 15:15:36 +00002586 int firstIndex = 0;
reed@google.com69a99432012-01-10 18:00:10 +00002587 for (int i = 1; i < count; ++i) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002588 SkScalar y = pts[i].fY;
2589 if (y > max) {
2590 max = y;
2591 firstIndex = i;
reed@google.com69a99432012-01-10 18:00:10 +00002592 }
2593 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002594 return firstIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002595}
2596
reed@google.comcabaf1d2012-01-11 21:03:05 +00002597static int find_diff_pt(const SkPoint pts[], int index, int n, int inc) {
2598 int i = index;
2599 for (;;) {
2600 i = (i + inc) % n;
2601 if (i == index) { // we wrapped around, so abort
2602 break;
2603 }
2604 if (pts[index] != pts[i]) { // found a different point, success!
2605 break;
2606 }
2607 }
2608 return i;
2609}
2610
reed@google.comc1ea60a2012-01-31 15:15:36 +00002611/**
2612 * Starting at index, and moving forward (incrementing), find the xmin and
2613 * xmax of the contiguous points that have the same Y.
2614 */
2615static int find_min_max_x_at_y(const SkPoint pts[], int index, int n,
2616 int* maxIndexPtr) {
2617 const SkScalar y = pts[index].fY;
2618 SkScalar min = pts[index].fX;
2619 SkScalar max = min;
2620 int minIndex = index;
2621 int maxIndex = index;
2622 for (int i = index + 1; i < n; ++i) {
2623 if (pts[i].fY != y) {
2624 break;
2625 }
2626 SkScalar x = pts[i].fX;
2627 if (x < min) {
2628 min = x;
2629 minIndex = i;
2630 } else if (x > max) {
2631 max = x;
2632 maxIndex = i;
2633 }
2634 }
2635 *maxIndexPtr = maxIndex;
2636 return minIndex;
2637}
2638
reed026beb52015-06-10 14:23:15 -07002639static void crossToDir(SkScalar cross, SkPathPriv::FirstDirection* dir) {
2640 *dir = cross > 0 ? SkPathPriv::kCW_FirstDirection : SkPathPriv::kCCW_FirstDirection;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002641}
2642
reed@google.comac8543f2012-01-30 20:51:25 +00002643/*
2644 * We loop through all contours, and keep the computed cross-product of the
2645 * contour that contained the global y-max. If we just look at the first
2646 * contour, we may find one that is wound the opposite way (correctly) since
2647 * it is the interior of a hole (e.g. 'o'). Thus we must find the contour
2648 * that is outer most (or at least has the global y-max) before we can consider
2649 * its cross product.
2650 */
reed026beb52015-06-10 14:23:15 -07002651bool SkPathPriv::CheapComputeFirstDirection(const SkPath& path, FirstDirection* dir) {
herb9f4dbca2015-09-28 11:05:47 -07002652 if (kUnknown_FirstDirection != path.fFirstDirection.load()) {
2653 *dir = static_cast<FirstDirection>(path.fFirstDirection.load());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002654 return true;
2655 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002656
2657 // don't want to pay the cost for computing this if it
2658 // is unknown, so we don't call isConvex()
reed026beb52015-06-10 14:23:15 -07002659 if (SkPath::kConvex_Convexity == path.getConvexityOrUnknown()) {
2660 SkASSERT(kUnknown_FirstDirection == path.fFirstDirection);
herb9f4dbca2015-09-28 11:05:47 -07002661 *dir = static_cast<FirstDirection>(path.fFirstDirection.load());
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002662 return false;
2663 }
reed@google.com69a99432012-01-10 18:00:10 +00002664
reed026beb52015-06-10 14:23:15 -07002665 ContourIter iter(*path.fPathRef.get());
reed@google.com69a99432012-01-10 18:00:10 +00002666
reed@google.comac8543f2012-01-30 20:51:25 +00002667 // initialize with our logical y-min
reed026beb52015-06-10 14:23:15 -07002668 SkScalar ymax = path.getBounds().fTop;
reed@google.comac8543f2012-01-30 20:51:25 +00002669 SkScalar ymaxCross = 0;
2670
reed@google.com69a99432012-01-10 18:00:10 +00002671 for (; !iter.done(); iter.next()) {
2672 int n = iter.count();
reed@google.comcabaf1d2012-01-11 21:03:05 +00002673 if (n < 3) {
2674 continue;
2675 }
djsollen@google.come63793a2012-03-21 15:39:03 +00002676
reed@google.comcabaf1d2012-01-11 21:03:05 +00002677 const SkPoint* pts = iter.pts();
reed@google.com69a99432012-01-10 18:00:10 +00002678 SkScalar cross = 0;
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002679 int index = find_max_y(pts, n);
2680 if (pts[index].fY < ymax) {
2681 continue;
2682 }
2683
2684 // If there is more than 1 distinct point at the y-max, we take the
2685 // x-min and x-max of them and just subtract to compute the dir.
2686 if (pts[(index + 1) % n].fY == pts[index].fY) {
2687 int maxIndex;
2688 int minIndex = find_min_max_x_at_y(pts, index, n, &maxIndex);
2689 if (minIndex == maxIndex) {
2690 goto TRY_CROSSPROD;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002691 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002692 SkASSERT(pts[minIndex].fY == pts[index].fY);
2693 SkASSERT(pts[maxIndex].fY == pts[index].fY);
2694 SkASSERT(pts[minIndex].fX <= pts[maxIndex].fX);
2695 // we just subtract the indices, and let that auto-convert to
2696 // SkScalar, since we just want - or + to signal the direction.
2697 cross = minIndex - maxIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002698 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002699 TRY_CROSSPROD:
2700 // Find a next and prev index to use for the cross-product test,
2701 // but we try to find pts that form non-zero vectors from pts[index]
2702 //
2703 // Its possible that we can't find two non-degenerate vectors, so
2704 // we have to guard our search (e.g. all the pts could be in the
2705 // same place).
2706
2707 // we pass n - 1 instead of -1 so we don't foul up % operator by
2708 // passing it a negative LH argument.
2709 int prev = find_diff_pt(pts, index, n, n - 1);
2710 if (prev == index) {
2711 // completely degenerate, skip to next contour
reed@google.comac8543f2012-01-30 20:51:25 +00002712 continue;
2713 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002714 int next = find_diff_pt(pts, index, n, 1);
2715 SkASSERT(next != index);
2716 cross = cross_prod(pts[prev], pts[index], pts[next]);
2717 // if we get a zero and the points are horizontal, then we look at the spread in
2718 // x-direction. We really should continue to walk away from the degeneracy until
2719 // there is a divergence.
2720 if (0 == cross && pts[prev].fY == pts[index].fY && pts[next].fY == pts[index].fY) {
2721 // construct the subtract so we get the correct Direction below
2722 cross = pts[index].fX - pts[next].fX;
reed@google.com188bfcf2012-01-17 18:26:38 +00002723 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002724 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002725
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002726 if (cross) {
2727 // record our best guess so far
2728 ymax = pts[index].fY;
2729 ymaxCross = cross;
reed@google.com69a99432012-01-10 18:00:10 +00002730 }
2731 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002732 if (ymaxCross) {
2733 crossToDir(ymaxCross, dir);
reed026beb52015-06-10 14:23:15 -07002734 path.fFirstDirection = *dir;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002735 return true;
2736 } else {
2737 return false;
2738 }
reed@google.comac8543f2012-01-30 20:51:25 +00002739}
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002740
2741///////////////////////////////////////////////////////////////////////////////
2742
caryclark9aacd902015-12-14 08:38:09 -08002743static bool between(SkScalar a, SkScalar b, SkScalar c) {
2744 SkASSERT(((a <= b && b <= c) || (a >= b && b >= c)) == ((a - b) * (c - b) <= 0)
2745 || (SkScalarNearlyZero(a) && SkScalarNearlyZero(b) && SkScalarNearlyZero(c)));
2746 return (a - b) * (c - b) <= 0;
2747}
2748
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002749static SkScalar eval_cubic_coeff(SkScalar A, SkScalar B, SkScalar C,
2750 SkScalar D, SkScalar t) {
2751 return SkScalarMulAdd(SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C), t, D);
2752}
2753
2754static SkScalar eval_cubic_pts(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2755 SkScalar t) {
2756 SkScalar A = c3 + 3*(c1 - c2) - c0;
2757 SkScalar B = 3*(c2 - c1 - c1 + c0);
2758 SkScalar C = 3*(c1 - c0);
2759 SkScalar D = c0;
2760 return eval_cubic_coeff(A, B, C, D, t);
2761}
2762
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002763template <size_t N> static void find_minmax(const SkPoint pts[],
2764 SkScalar* minPtr, SkScalar* maxPtr) {
2765 SkScalar min, max;
2766 min = max = pts[0].fX;
2767 for (size_t i = 1; i < N; ++i) {
2768 min = SkMinScalar(min, pts[i].fX);
2769 max = SkMaxScalar(max, pts[i].fX);
2770 }
2771 *minPtr = min;
2772 *maxPtr = max;
2773}
2774
caryclark9cb5d752015-12-18 04:35:24 -08002775static bool checkOnCurve(SkScalar x, SkScalar y, const SkPoint& start, const SkPoint& end) {
2776 if (start.fY == end.fY) {
2777 return between(start.fX, x, end.fX) && x != end.fX;
2778 } else {
2779 return x == start.fX && y == start.fY;
2780 }
2781}
2782
caryclark9aacd902015-12-14 08:38:09 -08002783static int winding_mono_cubic(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
caryclark9cb5d752015-12-18 04:35:24 -08002784 SkScalar y0 = pts[0].fY;
2785 SkScalar y3 = pts[3].fY;
2786
2787 int dir = 1;
2788 if (y0 > y3) {
2789 SkTSwap(y0, y3);
2790 dir = -1;
2791 }
2792 if (y < y0 || y > y3) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002793 return 0;
2794 }
caryclark9cb5d752015-12-18 04:35:24 -08002795 if (checkOnCurve(x, y, pts[0], pts[3])) {
2796 *onCurveCount += 1;
caryclark9aacd902015-12-14 08:38:09 -08002797 return 0;
2798 }
caryclark9cb5d752015-12-18 04:35:24 -08002799 if (y == y3) {
2800 return 0;
2801 }
2802
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002803 // quickreject or quickaccept
2804 SkScalar min, max;
2805 find_minmax<4>(pts, &min, &max);
2806 if (x < min) {
2807 return 0;
2808 }
2809 if (x > max) {
2810 return dir;
2811 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002812
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002813 // compute the actual x(t) value
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002814 SkScalar t;
mbarbella276e6332016-05-31 14:44:01 -07002815 if (!SkCubicClipper::ChopMonoAtY(pts, y, &t)) {
2816 return 0;
2817 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002818 SkScalar xt = eval_cubic_pts(pts[0].fX, pts[1].fX, pts[2].fX, pts[3].fX, t);
caryclark9aacd902015-12-14 08:38:09 -08002819 if (SkScalarNearlyEqual(xt, x)) {
2820 if (x != pts[3].fX || y != pts[3].fY) { // don't test end points; they're start points
2821 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08002822 return 0;
caryclark9aacd902015-12-14 08:38:09 -08002823 }
2824 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002825 return xt < x ? dir : 0;
2826}
2827
caryclark9aacd902015-12-14 08:38:09 -08002828static int winding_cubic(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002829 SkPoint dst[10];
2830 int n = SkChopCubicAtYExtrema(pts, dst);
2831 int w = 0;
2832 for (int i = 0; i <= n; ++i) {
caryclark9aacd902015-12-14 08:38:09 -08002833 w += winding_mono_cubic(&dst[i * 3], x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002834 }
2835 return w;
2836}
2837
caryclark9aacd902015-12-14 08:38:09 -08002838static double conic_eval_numerator(const SkScalar src[], SkScalar w, SkScalar t) {
2839 SkASSERT(src);
2840 SkASSERT(t >= 0 && t <= 1);
2841 SkScalar src2w = src[2] * w;
2842 SkScalar C = src[0];
2843 SkScalar A = src[4] - 2 * src2w + C;
2844 SkScalar B = 2 * (src2w - C);
2845 return (A * t + B) * t + C;
2846}
2847
2848
2849static double conic_eval_denominator(SkScalar w, SkScalar t) {
2850 SkScalar B = 2 * (w - 1);
2851 SkScalar C = 1;
2852 SkScalar A = -B;
2853 return (A * t + B) * t + C;
2854}
2855
2856static int winding_mono_conic(const SkConic& conic, SkScalar x, SkScalar y, int* onCurveCount) {
2857 const SkPoint* pts = conic.fPts;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002858 SkScalar y0 = pts[0].fY;
2859 SkScalar y2 = pts[2].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002860
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002861 int dir = 1;
2862 if (y0 > y2) {
2863 SkTSwap(y0, y2);
2864 dir = -1;
2865 }
caryclark9aacd902015-12-14 08:38:09 -08002866 if (y < y0 || y > y2) {
2867 return 0;
2868 }
caryclark9cb5d752015-12-18 04:35:24 -08002869 if (checkOnCurve(x, y, pts[0], pts[2])) {
2870 *onCurveCount += 1;
2871 return 0;
2872 }
caryclark9aacd902015-12-14 08:38:09 -08002873 if (y == y2) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002874 return 0;
2875 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002876
caryclark9aacd902015-12-14 08:38:09 -08002877 SkScalar roots[2];
2878 SkScalar A = pts[2].fY;
2879 SkScalar B = pts[1].fY * conic.fW - y * conic.fW + y;
2880 SkScalar C = pts[0].fY;
2881 A += C - 2 * B; // A = a + c - 2*(b*w - yCept*w + yCept)
2882 B -= C; // B = b*w - w * yCept + yCept - a
2883 C -= y;
2884 int n = SkFindUnitQuadRoots(A, 2 * B, C, roots);
2885 SkASSERT(n <= 1);
2886 SkScalar xt;
2887 if (0 == n) {
caryclark9cb5d752015-12-18 04:35:24 -08002888 // zero roots are returned only when y0 == y
2889 // Need [0] if dir == 1
2890 // and [2] if dir == -1
2891 xt = pts[1 - dir].fX;
caryclark9aacd902015-12-14 08:38:09 -08002892 } else {
2893 SkScalar t = roots[0];
2894 xt = conic_eval_numerator(&pts[0].fX, conic.fW, t) / conic_eval_denominator(conic.fW, t);
2895 }
2896 if (SkScalarNearlyEqual(xt, x)) {
2897 if (x != pts[2].fX || y != pts[2].fY) { // don't test end points; they're start points
2898 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08002899 return 0;
caryclark9aacd902015-12-14 08:38:09 -08002900 }
2901 }
2902 return xt < x ? dir : 0;
2903}
2904
2905static bool is_mono_quad(SkScalar y0, SkScalar y1, SkScalar y2) {
2906 // return SkScalarSignAsInt(y0 - y1) + SkScalarSignAsInt(y1 - y2) != 0;
2907 if (y0 == y1) {
2908 return true;
2909 }
2910 if (y0 < y1) {
2911 return y1 <= y2;
2912 } else {
2913 return y1 >= y2;
2914 }
2915}
2916
2917static int winding_conic(const SkPoint pts[], SkScalar x, SkScalar y, SkScalar weight,
2918 int* onCurveCount) {
2919 SkConic conic(pts, weight);
caryclark9aacd902015-12-14 08:38:09 -08002920 SkConic chopped[2];
caryclark1e1e5092016-01-06 08:27:44 -08002921 // If the data points are very large, the conic may not be monotonic but may also
2922 // fail to chop. Then, the chopper does not split the original conic in two.
caryclarke114dd62016-01-06 08:15:44 -08002923 bool isMono = is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY) || !conic.chopAtYExtrema(chopped);
2924 int w = winding_mono_conic(isMono ? conic : chopped[0], x, y, onCurveCount);
2925 if (!isMono) {
caryclark9aacd902015-12-14 08:38:09 -08002926 w += winding_mono_conic(chopped[1], x, y, onCurveCount);
2927 }
2928 return w;
2929}
2930
2931static int winding_mono_quad(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
2932 SkScalar y0 = pts[0].fY;
2933 SkScalar y2 = pts[2].fY;
2934
2935 int dir = 1;
2936 if (y0 > y2) {
2937 SkTSwap(y0, y2);
2938 dir = -1;
2939 }
2940 if (y < y0 || y > y2) {
2941 return 0;
2942 }
caryclark9cb5d752015-12-18 04:35:24 -08002943 if (checkOnCurve(x, y, pts[0], pts[2])) {
2944 *onCurveCount += 1;
2945 return 0;
2946 }
caryclark9aacd902015-12-14 08:38:09 -08002947 if (y == y2) {
caryclark9aacd902015-12-14 08:38:09 -08002948 return 0;
2949 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002950 // bounds check on X (not required. is it faster?)
2951#if 0
2952 if (pts[0].fX > x && pts[1].fX > x && pts[2].fX > x) {
2953 return 0;
2954 }
2955#endif
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002956
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002957 SkScalar roots[2];
2958 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
2959 2 * (pts[1].fY - pts[0].fY),
2960 pts[0].fY - y,
2961 roots);
2962 SkASSERT(n <= 1);
2963 SkScalar xt;
2964 if (0 == n) {
caryclark9cb5d752015-12-18 04:35:24 -08002965 // zero roots are returned only when y0 == y
2966 // Need [0] if dir == 1
2967 // and [2] if dir == -1
2968 xt = pts[1 - dir].fX;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002969 } else {
2970 SkScalar t = roots[0];
2971 SkScalar C = pts[0].fX;
2972 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
2973 SkScalar B = 2 * (pts[1].fX - C);
2974 xt = SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C);
2975 }
caryclark9aacd902015-12-14 08:38:09 -08002976 if (SkScalarNearlyEqual(xt, x)) {
2977 if (x != pts[2].fX || y != pts[2].fY) { // don't test end points; they're start points
2978 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08002979 return 0;
caryclark9aacd902015-12-14 08:38:09 -08002980 }
2981 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002982 return xt < x ? dir : 0;
2983}
2984
caryclark9aacd902015-12-14 08:38:09 -08002985static int winding_quad(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002986 SkPoint dst[5];
2987 int n = 0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002988
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002989 if (!is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY)) {
2990 n = SkChopQuadAtYExtrema(pts, dst);
2991 pts = dst;
2992 }
caryclark9aacd902015-12-14 08:38:09 -08002993 int w = winding_mono_quad(pts, x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002994 if (n > 0) {
caryclark9aacd902015-12-14 08:38:09 -08002995 w += winding_mono_quad(&pts[2], x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002996 }
2997 return w;
2998}
2999
caryclark9aacd902015-12-14 08:38:09 -08003000static int winding_line(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003001 SkScalar x0 = pts[0].fX;
3002 SkScalar y0 = pts[0].fY;
3003 SkScalar x1 = pts[1].fX;
3004 SkScalar y1 = pts[1].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003005
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003006 SkScalar dy = y1 - y0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003007
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003008 int dir = 1;
3009 if (y0 > y1) {
3010 SkTSwap(y0, y1);
3011 dir = -1;
3012 }
caryclark9aacd902015-12-14 08:38:09 -08003013 if (y < y0 || y > y1) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003014 return 0;
3015 }
caryclark9cb5d752015-12-18 04:35:24 -08003016 if (checkOnCurve(x, y, pts[0], pts[1])) {
3017 *onCurveCount += 1;
3018 return 0;
3019 }
3020 if (y == y1) {
caryclark9aacd902015-12-14 08:38:09 -08003021 return 0;
3022 }
3023 SkScalar cross = SkScalarMul(x1 - x0, y - pts[0].fY) - SkScalarMul(dy, x - x0);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003024
caryclark9aacd902015-12-14 08:38:09 -08003025 if (!cross) {
fsc91065d2015-12-17 09:03:27 -08003026 // zero cross means the point is on the line, and since the case where
3027 // y of the query point is at the end point is handled above, we can be
3028 // sure that we're on the line (excluding the end point) here
caryclark9cb5d752015-12-18 04:35:24 -08003029 if (x != x1 || y != pts[1].fY) {
3030 *onCurveCount += 1;
3031 }
caryclark9aacd902015-12-14 08:38:09 -08003032 dir = 0;
3033 } else if (SkScalarSignAsInt(cross) == dir) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003034 dir = 0;
3035 }
3036 return dir;
3037}
3038
caryclark9aacd902015-12-14 08:38:09 -08003039static void tangent_cubic(const SkPoint pts[], SkScalar x, SkScalar y,
3040 SkTDArray<SkVector>* tangents) {
3041 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)
3042 && !between(pts[2].fY, y, pts[3].fY)) {
3043 return;
3044 }
3045 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)
3046 && !between(pts[2].fX, x, pts[3].fX)) {
3047 return;
3048 }
3049 SkPoint dst[10];
3050 int n = SkChopCubicAtYExtrema(pts, dst);
3051 for (int i = 0; i <= n; ++i) {
3052 SkPoint* c = &dst[i * 3];
3053 SkScalar t;
mbarbella276e6332016-05-31 14:44:01 -07003054 if (!SkCubicClipper::ChopMonoAtY(c, y, &t)) {
3055 continue;
3056 }
caryclark9aacd902015-12-14 08:38:09 -08003057 SkScalar xt = eval_cubic_pts(c[0].fX, c[1].fX, c[2].fX, c[3].fX, t);
3058 if (!SkScalarNearlyEqual(x, xt)) {
3059 continue;
3060 }
3061 SkVector tangent;
3062 SkEvalCubicAt(c, t, nullptr, &tangent, nullptr);
3063 tangents->push(tangent);
3064 }
3065}
3066
3067static void tangent_conic(const SkPoint pts[], SkScalar x, SkScalar y, SkScalar w,
3068 SkTDArray<SkVector>* tangents) {
3069 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)) {
3070 return;
3071 }
3072 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)) {
3073 return;
3074 }
3075 SkScalar roots[2];
3076 SkScalar A = pts[2].fY;
3077 SkScalar B = pts[1].fY * w - y * w + y;
3078 SkScalar C = pts[0].fY;
3079 A += C - 2 * B; // A = a + c - 2*(b*w - yCept*w + yCept)
3080 B -= C; // B = b*w - w * yCept + yCept - a
3081 C -= y;
3082 int n = SkFindUnitQuadRoots(A, 2 * B, C, roots);
3083 for (int index = 0; index < n; ++index) {
3084 SkScalar t = roots[index];
3085 SkScalar xt = conic_eval_numerator(&pts[0].fX, w, t) / conic_eval_denominator(w, t);
3086 if (!SkScalarNearlyEqual(x, xt)) {
3087 continue;
3088 }
3089 SkConic conic(pts, w);
3090 tangents->push(conic.evalTangentAt(t));
3091 }
3092}
3093
3094static void tangent_quad(const SkPoint pts[], SkScalar x, SkScalar y,
3095 SkTDArray<SkVector>* tangents) {
3096 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)) {
3097 return;
3098 }
3099 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)) {
3100 return;
3101 }
3102 SkScalar roots[2];
3103 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
3104 2 * (pts[1].fY - pts[0].fY),
3105 pts[0].fY - y,
3106 roots);
3107 for (int index = 0; index < n; ++index) {
3108 SkScalar t = roots[index];
3109 SkScalar C = pts[0].fX;
3110 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
3111 SkScalar B = 2 * (pts[1].fX - C);
3112 SkScalar xt = (A * t + B) * t + C;
3113 if (!SkScalarNearlyEqual(x, xt)) {
3114 continue;
3115 }
3116 tangents->push(SkEvalQuadTangentAt(pts, t));
3117 }
3118}
3119
3120static void tangent_line(const SkPoint pts[], SkScalar x, SkScalar y,
3121 SkTDArray<SkVector>* tangents) {
3122 SkScalar y0 = pts[0].fY;
3123 SkScalar y1 = pts[1].fY;
3124 if (!between(y0, y, y1)) {
3125 return;
3126 }
3127 SkScalar x0 = pts[0].fX;
3128 SkScalar x1 = pts[1].fX;
3129 if (!between(x0, x, x1)) {
3130 return;
3131 }
3132 SkScalar dx = x1 - x0;
3133 SkScalar dy = y1 - y0;
3134 if (!SkScalarNearlyEqual((x - x0) * dy, dx * (y - y0))) {
3135 return;
3136 }
3137 SkVector v;
3138 v.set(dx, dy);
3139 tangents->push(v);
3140}
3141
reed@google.com4db592c2013-10-30 17:39:43 +00003142static bool contains_inclusive(const SkRect& r, SkScalar x, SkScalar y) {
3143 return r.fLeft <= x && x <= r.fRight && r.fTop <= y && y <= r.fBottom;
3144}
3145
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003146bool SkPath::contains(SkScalar x, SkScalar y) const {
3147 bool isInverse = this->isInverseFillType();
3148 if (this->isEmpty()) {
3149 return isInverse;
3150 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003151
reed@google.com4db592c2013-10-30 17:39:43 +00003152 if (!contains_inclusive(this->getBounds(), x, y)) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003153 return isInverse;
3154 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003155
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003156 SkPath::Iter iter(*this, true);
3157 bool done = false;
3158 int w = 0;
caryclark9aacd902015-12-14 08:38:09 -08003159 int onCurveCount = 0;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003160 do {
3161 SkPoint pts[4];
3162 switch (iter.next(pts, false)) {
3163 case SkPath::kMove_Verb:
3164 case SkPath::kClose_Verb:
3165 break;
3166 case SkPath::kLine_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003167 w += winding_line(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003168 break;
3169 case SkPath::kQuad_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003170 w += winding_quad(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003171 break;
reed@google.com277c3f82013-05-31 15:17:50 +00003172 case SkPath::kConic_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003173 w += winding_conic(pts, x, y, iter.conicWeight(), &onCurveCount);
reed@google.com277c3f82013-05-31 15:17:50 +00003174 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003175 case SkPath::kCubic_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003176 w += winding_cubic(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003177 break;
3178 case SkPath::kDone_Verb:
3179 done = true;
3180 break;
reed@google.com277c3f82013-05-31 15:17:50 +00003181 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003182 } while (!done);
caryclark9aacd902015-12-14 08:38:09 -08003183 bool evenOddFill = SkPath::kEvenOdd_FillType == this->getFillType()
3184 || SkPath::kInverseEvenOdd_FillType == this->getFillType();
3185 if (evenOddFill) {
3186 w &= 1;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003187 }
caryclark9aacd902015-12-14 08:38:09 -08003188 if (w) {
3189 return !isInverse;
3190 }
3191 if (onCurveCount <= 1) {
3192 return SkToBool(onCurveCount) ^ isInverse;
3193 }
3194 if ((onCurveCount & 1) || evenOddFill) {
3195 return SkToBool(onCurveCount & 1) ^ isInverse;
3196 }
halcanary9d524f22016-03-29 09:03:52 -07003197 // If the point touches an even number of curves, and the fill is winding, check for
caryclark9aacd902015-12-14 08:38:09 -08003198 // coincidence. Count coincidence as places where the on curve points have identical tangents.
3199 iter.setPath(*this, true);
caryclark9cb5d752015-12-18 04:35:24 -08003200 done = false;
caryclark9aacd902015-12-14 08:38:09 -08003201 SkTDArray<SkVector> tangents;
3202 do {
3203 SkPoint pts[4];
3204 int oldCount = tangents.count();
3205 switch (iter.next(pts, false)) {
3206 case SkPath::kMove_Verb:
3207 case SkPath::kClose_Verb:
3208 break;
3209 case SkPath::kLine_Verb:
3210 tangent_line(pts, x, y, &tangents);
3211 break;
3212 case SkPath::kQuad_Verb:
3213 tangent_quad(pts, x, y, &tangents);
3214 break;
3215 case SkPath::kConic_Verb:
3216 tangent_conic(pts, x, y, iter.conicWeight(), &tangents);
3217 break;
3218 case SkPath::kCubic_Verb:
3219 tangent_cubic(pts, x, y, &tangents);
3220 break;
3221 case SkPath::kDone_Verb:
3222 done = true;
3223 break;
3224 }
3225 if (tangents.count() > oldCount) {
3226 int last = tangents.count() - 1;
3227 const SkVector& tangent = tangents[last];
3228 if (SkScalarNearlyZero(tangent.lengthSqd())) {
3229 tangents.remove(last);
3230 } else {
3231 for (int index = 0; index < last; ++index) {
3232 const SkVector& test = tangents[index];
3233 if (SkScalarNearlyZero(test.cross(tangent))
caryclark9cb5d752015-12-18 04:35:24 -08003234 && SkScalarSignAsInt(tangent.fX * test.fX) <= 0
3235 && SkScalarSignAsInt(tangent.fY * test.fY) <= 0) {
caryclark9aacd902015-12-14 08:38:09 -08003236 tangents.remove(last);
3237 tangents.removeShuffle(index);
3238 break;
3239 }
3240 }
3241 }
3242 }
3243 } while (!done);
3244 return SkToBool(tangents.count()) ^ isInverse;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003245}
fmalitaaa0df4e2015-12-01 09:13:23 -08003246
3247int SkPath::ConvertConicToQuads(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2,
3248 SkScalar w, SkPoint pts[], int pow2) {
3249 const SkConic conic(p0, p1, p2, w);
3250 return conic.chopIntoQuadsPOW2(pts, pow2);
3251}