blob: 45429a6e1253bd9f5b93481b9a3e5d48af2508ee [file] [log] [blame]
epoger@google.comec3ed6a2011-07-28 14:26:00 +00001/*
2 * Copyright 2006 The Android Open Source Project
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
djsollen@google.com94e75ee2012-06-08 18:30:46 +00008#include "SkBuffer.h"
humper@google.com75e3ca12013-04-08 21:44:11 +00009#include "SkErrorInternals.h"
reed220f9262014-12-17 08:21:04 -080010#include "SkGeometry.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000011#include "SkMath.h"
humper@google.com75e3ca12013-04-08 21:44:11 +000012#include "SkPath.h"
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +000013#include "SkPathRef.h"
reed@google.com4ed0fb72012-12-12 20:48:18 +000014#include "SkRRect.h"
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +000015#include "SkThread.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000016
17////////////////////////////////////////////////////////////////////////////
18
reed@google.com3563c9e2011-11-14 19:34:57 +000019/**
20 * Path.bounds is defined to be the bounds of all the control points.
21 * If we called bounds.join(r) we would skip r if r was empty, which breaks
22 * our promise. Hence we have a custom joiner that doesn't look at emptiness
23 */
24static void joinNoEmptyChecks(SkRect* dst, const SkRect& src) {
25 dst->fLeft = SkMinScalar(dst->fLeft, src.fLeft);
26 dst->fTop = SkMinScalar(dst->fTop, src.fTop);
27 dst->fRight = SkMaxScalar(dst->fRight, src.fRight);
28 dst->fBottom = SkMaxScalar(dst->fBottom, src.fBottom);
29}
30
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000031static bool is_degenerate(const SkPath& path) {
32 SkPath::Iter iter(path, false);
33 SkPoint pts[4];
34 return SkPath::kDone_Verb == iter.next(pts);
35}
36
bsalomon@google.com30c174b2012-11-13 14:36:42 +000037class SkAutoDisableDirectionCheck {
38public:
39 SkAutoDisableDirectionCheck(SkPath* path) : fPath(path) {
40 fSaved = static_cast<SkPath::Direction>(fPath->fDirection);
41 }
42
43 ~SkAutoDisableDirectionCheck() {
44 fPath->fDirection = fSaved;
45 }
46
47private:
48 SkPath* fPath;
49 SkPath::Direction fSaved;
50};
commit-bot@chromium.orge61a86c2013-11-18 16:03:59 +000051#define SkAutoDisableDirectionCheck(...) SK_REQUIRE_LOCAL_VAR(SkAutoDisableDirectionCheck)
bsalomon@google.com30c174b2012-11-13 14:36:42 +000052
reed@android.com8a1c16f2008-12-17 15:59:43 +000053/* This guy's constructor/destructor bracket a path editing operation. It is
54 used when we know the bounds of the amount we are going to add to the path
55 (usually a new contour, but not required).
reed@google.comabf15c12011-01-18 20:35:51 +000056
reed@android.com8a1c16f2008-12-17 15:59:43 +000057 It captures some state about the path up front (i.e. if it already has a
robertphillips@google.comca0c8382013-09-26 12:18:23 +000058 cached bounds), and then if it can, it updates the cache bounds explicitly,
reed@android.comd252db02009-04-01 18:31:44 +000059 avoiding the need to revisit all of the points in getBounds().
reed@google.comabf15c12011-01-18 20:35:51 +000060
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000061 It also notes if the path was originally degenerate, and if so, sets
62 isConvex to true. Thus it can only be used if the contour being added is
robertphillips@google.com466310d2013-12-03 16:43:54 +000063 convex.
reed@android.com8a1c16f2008-12-17 15:59:43 +000064 */
65class SkAutoPathBoundsUpdate {
66public:
67 SkAutoPathBoundsUpdate(SkPath* path, const SkRect& r) : fRect(r) {
68 this->init(path);
69 }
70
71 SkAutoPathBoundsUpdate(SkPath* path, SkScalar left, SkScalar top,
72 SkScalar right, SkScalar bottom) {
73 fRect.set(left, top, right, bottom);
74 this->init(path);
75 }
reed@google.comabf15c12011-01-18 20:35:51 +000076
reed@android.com8a1c16f2008-12-17 15:59:43 +000077 ~SkAutoPathBoundsUpdate() {
reed@google.com44699382013-10-31 17:28:30 +000078 fPath->setConvexity(fDegenerate ? SkPath::kConvex_Convexity
79 : SkPath::kUnknown_Convexity);
robertphillips@google.comca0c8382013-09-26 12:18:23 +000080 if (fEmpty || fHasValidBounds) {
81 fPath->setBounds(fRect);
reed@android.com8a1c16f2008-12-17 15:59:43 +000082 }
83 }
reed@google.comabf15c12011-01-18 20:35:51 +000084
reed@android.com8a1c16f2008-12-17 15:59:43 +000085private:
reed@android.com6b82d1a2009-06-03 02:35:01 +000086 SkPath* fPath;
87 SkRect fRect;
robertphillips@google.comca0c8382013-09-26 12:18:23 +000088 bool fHasValidBounds;
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000089 bool fDegenerate;
reed@android.com6b82d1a2009-06-03 02:35:01 +000090 bool fEmpty;
reed@google.comabf15c12011-01-18 20:35:51 +000091
reed@android.com6b82d1a2009-06-03 02:35:01 +000092 void init(SkPath* path) {
robertphillips@google.comca0c8382013-09-26 12:18:23 +000093 // Cannot use fRect for our bounds unless we know it is sorted
94 fRect.sort();
reed@android.com8a1c16f2008-12-17 15:59:43 +000095 fPath = path;
reed@google.coma8790de2012-10-24 21:04:04 +000096 // Mark the path's bounds as dirty if (1) they are, or (2) the path
97 // is non-finite, and therefore its bounds are not meaningful
robertphillips@google.comca0c8382013-09-26 12:18:23 +000098 fHasValidBounds = path->hasComputedBounds() && path->isFinite();
reed@android.com8a1c16f2008-12-17 15:59:43 +000099 fEmpty = path->isEmpty();
robertphillips@google.comca0c8382013-09-26 12:18:23 +0000100 if (fHasValidBounds && !fEmpty) {
101 joinNoEmptyChecks(&fRect, fPath->getBounds());
102 }
103 fDegenerate = is_degenerate(*path);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000104 }
105};
commit-bot@chromium.orge61a86c2013-11-18 16:03:59 +0000106#define SkAutoPathBoundsUpdate(...) SK_REQUIRE_LOCAL_VAR(SkAutoPathBoundsUpdate)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000107
reed@android.com8a1c16f2008-12-17 15:59:43 +0000108////////////////////////////////////////////////////////////////////////////
109
110/*
111 Stores the verbs and points as they are given to us, with exceptions:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000112 - we only record "Close" if it was immediately preceeded by Move | Line | Quad | Cubic
reed@android.com8a1c16f2008-12-17 15:59:43 +0000113 - we insert a Move(0,0) if Line | Quad | Cubic is our first command
114
115 The iterator does more cleanup, especially if forceClose == true
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000116 1. If we encounter degenerate segments, remove them
117 2. if we encounter Close, return a cons'd up Line() first (if the curr-pt != start-pt)
118 3. if we encounter Move without a preceeding Close, and forceClose is true, goto #2
119 4. if we encounter Line | Quad | Cubic after Close, cons up a Move
reed@android.com8a1c16f2008-12-17 15:59:43 +0000120*/
121
122////////////////////////////////////////////////////////////////////////////
123
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000124// flag to require a moveTo if we begin with something else, like lineTo etc.
125#define INITIAL_LASTMOVETOINDEX_VALUE ~0
126
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000127SkPath::SkPath()
djsollen523cda32015-02-17 08:06:32 -0800128 : fPathRef(SkPathRef::CreateEmpty()) {
bungeman@google.coma5809a32013-06-21 15:13:34 +0000129 this->resetFields();
jvanverthb3eb6872014-10-24 07:12:51 -0700130 fIsVolatile = false;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000131}
132
133void SkPath::resetFields() {
134 //fPathRef is assumed to have been emptied by the caller.
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000135 fLastMoveToIndex = INITIAL_LASTMOVETOINDEX_VALUE;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000136 fFillType = kWinding_FillType;
reed@google.com04863fa2011-05-15 04:08:24 +0000137 fConvexity = kUnknown_Convexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000138 fDirection = kUnknown_Direction;
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000139
140 // We don't touch Android's fSourcePath. It's used to track texture garbage collection, so we
141 // don't want to muck with it if it's been set to something non-NULL.
reed@android.com6b82d1a2009-06-03 02:35:01 +0000142}
reed@android.com8a1c16f2008-12-17 15:59:43 +0000143
bungeman@google.coma5809a32013-06-21 15:13:34 +0000144SkPath::SkPath(const SkPath& that)
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000145 : fPathRef(SkRef(that.fPathRef.get())) {
bungeman@google.coma5809a32013-06-21 15:13:34 +0000146 this->copyFields(that);
147 SkDEBUGCODE(that.validate();)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000148}
149
150SkPath::~SkPath() {
151 SkDEBUGCODE(this->validate();)
152}
153
bungeman@google.coma5809a32013-06-21 15:13:34 +0000154SkPath& SkPath::operator=(const SkPath& that) {
155 SkDEBUGCODE(that.validate();)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000156
bungeman@google.coma5809a32013-06-21 15:13:34 +0000157 if (this != &that) {
158 fPathRef.reset(SkRef(that.fPathRef.get()));
159 this->copyFields(that);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000160 }
161 SkDEBUGCODE(this->validate();)
162 return *this;
163}
164
bungeman@google.coma5809a32013-06-21 15:13:34 +0000165void SkPath::copyFields(const SkPath& that) {
166 //fPathRef is assumed to have been set by the caller.
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000167 fLastMoveToIndex = that.fLastMoveToIndex;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000168 fFillType = that.fFillType;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000169 fConvexity = that.fConvexity;
170 fDirection = that.fDirection;
jvanverthb3eb6872014-10-24 07:12:51 -0700171 fIsVolatile = that.fIsVolatile;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000172}
173
djsollen@google.com9c1a9672013-08-09 13:49:13 +0000174bool operator==(const SkPath& a, const SkPath& b) {
reed@android.com6b82d1a2009-06-03 02:35:01 +0000175 // note: don't need to look at isConvex or bounds, since just comparing the
176 // raw data is sufficient.
reed@android.com3abec1d2009-03-02 05:36:20 +0000177 return &a == &b ||
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000178 (a.fFillType == b.fFillType && *a.fPathRef.get() == *b.fPathRef.get());
reed@android.com3abec1d2009-03-02 05:36:20 +0000179}
180
bungeman@google.coma5809a32013-06-21 15:13:34 +0000181void SkPath::swap(SkPath& that) {
182 SkASSERT(&that != NULL);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000183
bungeman@google.coma5809a32013-06-21 15:13:34 +0000184 if (this != &that) {
185 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);
189 SkTSwap<uint8_t>(fDirection, that.fDirection);
jvanverthb3eb6872014-10-24 07:12:51 -0700190 SkTSwap<SkBool8>(fIsVolatile, that.fIsVolatile);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000191 }
192}
193
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000194static inline bool check_edge_against_rect(const SkPoint& p0,
195 const SkPoint& p1,
196 const SkRect& rect,
197 SkPath::Direction dir) {
198 const SkPoint* edgeBegin;
199 SkVector v;
200 if (SkPath::kCW_Direction == dir) {
201 v = p1 - p0;
202 edgeBegin = &p0;
203 } else {
204 v = p0 - p1;
205 edgeBegin = &p1;
206 }
207 if (v.fX || v.fY) {
208 // check the cross product of v with the vec from edgeBegin to each rect corner
209 SkScalar yL = SkScalarMul(v.fY, rect.fLeft - edgeBegin->fX);
210 SkScalar xT = SkScalarMul(v.fX, rect.fTop - edgeBegin->fY);
211 SkScalar yR = SkScalarMul(v.fY, rect.fRight - edgeBegin->fX);
212 SkScalar xB = SkScalarMul(v.fX, rect.fBottom - edgeBegin->fY);
213 if ((xT < yL) || (xT < yR) || (xB < yL) || (xB < yR)) {
214 return false;
215 }
216 }
217 return true;
218}
219
220bool SkPath::conservativelyContainsRect(const SkRect& rect) const {
221 // This only handles non-degenerate convex paths currently.
222 if (kConvex_Convexity != this->getConvexity()) {
223 return false;
224 }
skia.committer@gmail.comcec8de62012-11-14 02:01:22 +0000225
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000226 Direction direction;
227 if (!this->cheapComputeDirection(&direction)) {
228 return false;
229 }
230
231 SkPoint firstPt;
232 SkPoint prevPt;
233 RawIter iter(*this);
234 SkPath::Verb verb;
235 SkPoint pts[4];
236 SkDEBUGCODE(int moveCnt = 0;)
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000237 SkDEBUGCODE(int segmentCount = 0;)
238 SkDEBUGCODE(int closeCount = 0;)
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000239
240 while ((verb = iter.next(pts)) != kDone_Verb) {
241 int nextPt = -1;
242 switch (verb) {
243 case kMove_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000244 SkASSERT(!segmentCount && !closeCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000245 SkDEBUGCODE(++moveCnt);
246 firstPt = prevPt = pts[0];
247 break;
248 case kLine_Verb:
249 nextPt = 1;
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000250 SkASSERT(moveCnt && !closeCount);
251 SkDEBUGCODE(++segmentCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000252 break;
253 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000254 case kConic_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000255 SkASSERT(moveCnt && !closeCount);
256 SkDEBUGCODE(++segmentCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000257 nextPt = 2;
258 break;
259 case kCubic_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000260 SkASSERT(moveCnt && !closeCount);
261 SkDEBUGCODE(++segmentCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000262 nextPt = 3;
263 break;
264 case kClose_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000265 SkDEBUGCODE(++closeCount;)
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000266 break;
267 default:
268 SkDEBUGFAIL("unknown verb");
269 }
270 if (-1 != nextPt) {
reed220f9262014-12-17 08:21:04 -0800271 if (SkPath::kConic_Verb == verb) {
272 SkConic orig;
273 orig.set(pts, iter.conicWeight());
274 SkPoint quadPts[5];
275 int count = orig.chopIntoQuadsPOW2(quadPts, 1);
276 SK_ALWAYSBREAK(2 == count);
277
278 if (!check_edge_against_rect(quadPts[0], quadPts[2], rect, direction)) {
279 return false;
280 }
281 if (!check_edge_against_rect(quadPts[2], quadPts[4], rect, direction)) {
282 return false;
283 }
284 } else {
285 if (!check_edge_against_rect(prevPt, pts[nextPt], rect, direction)) {
286 return false;
287 }
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000288 }
289 prevPt = pts[nextPt];
290 }
291 }
292
293 return check_edge_against_rect(prevPt, firstPt, rect, direction);
294}
295
robertphillips@google.com7101abe2013-10-29 22:45:37 +0000296uint32_t SkPath::getGenerationID() const {
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000297 uint32_t genID = fPathRef->genID();
djsollen523cda32015-02-17 08:06:32 -0800298#ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000299 SkASSERT((unsigned)fFillType < (1 << (32 - kPathRefGenIDBitCnt)));
300 genID |= static_cast<uint32_t>(fFillType) << kPathRefGenIDBitCnt;
301#endif
302 return genID;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000303}
djsollen@google.come63793a2012-03-21 15:39:03 +0000304
reed@android.com8a1c16f2008-12-17 15:59:43 +0000305void SkPath::reset() {
306 SkDEBUGCODE(this->validate();)
307
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000308 fPathRef.reset(SkPathRef::CreateEmpty());
bungeman@google.coma5809a32013-06-21 15:13:34 +0000309 this->resetFields();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000310}
311
312void SkPath::rewind() {
313 SkDEBUGCODE(this->validate();)
314
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000315 SkPathRef::Rewind(&fPathRef);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000316 this->resetFields();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000317}
318
reed@google.com7e6c4d12012-05-10 14:05:43 +0000319bool SkPath::isLine(SkPoint line[2]) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000320 int verbCount = fPathRef->countVerbs();
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000321
commit-bot@chromium.orga62efcc2013-08-05 13:23:13 +0000322 if (2 == verbCount) {
323 SkASSERT(kMove_Verb == fPathRef->atVerb(0));
324 if (kLine_Verb == fPathRef->atVerb(1)) {
325 SkASSERT(2 == fPathRef->countPoints());
reed@google.com7e6c4d12012-05-10 14:05:43 +0000326 if (line) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000327 const SkPoint* pts = fPathRef->points();
reed@google.com7e6c4d12012-05-10 14:05:43 +0000328 line[0] = pts[0];
329 line[1] = pts[1];
330 }
331 return true;
332 }
333 }
334 return false;
335}
336
caryclark@google.comf1316942011-07-26 19:54:45 +0000337/*
338 Determines if path is a rect by keeping track of changes in direction
339 and looking for a loop either clockwise or counterclockwise.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000340
caryclark@google.comf1316942011-07-26 19:54:45 +0000341 The direction is computed such that:
342 0: vertical up
caryclark@google.comf68154a2012-11-21 15:18:06 +0000343 1: horizontal left
caryclark@google.comf1316942011-07-26 19:54:45 +0000344 2: vertical down
caryclark@google.comf68154a2012-11-21 15:18:06 +0000345 3: horizontal right
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000346
caryclark@google.comf1316942011-07-26 19:54:45 +0000347A rectangle cycles up/right/down/left or up/left/down/right.
348
349The test fails if:
350 The path is closed, and followed by a line.
351 A second move creates a new endpoint.
352 A diagonal line is parsed.
353 There's more than four changes of direction.
354 There's a discontinuity on the line (e.g., a move in the middle)
355 The line reverses direction.
caryclark@google.comf1316942011-07-26 19:54:45 +0000356 The path contains a quadratic or cubic.
357 The path contains fewer than four points.
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000358 *The rectangle doesn't complete a cycle.
359 *The final point isn't equal to the first point.
360
361 *These last two conditions we relax if we have a 3-edge path that would
362 form a rectangle if it were closed (as we do when we fill a path)
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000363
caryclark@google.comf1316942011-07-26 19:54:45 +0000364It's OK if the path has:
365 Several colinear line segments composing a rectangle side.
366 Single points on the rectangle side.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000367
caryclark@google.comf1316942011-07-26 19:54:45 +0000368The direction takes advantage of the corners found since opposite sides
369must travel in opposite directions.
370
371FIXME: Allow colinear quads and cubics to be treated like lines.
372FIXME: If the API passes fill-only, return true if the filled stroke
373 is a rectangle, though the caller failed to close the path.
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000374
375 first,last,next direction state-machine:
376 0x1 is set if the segment is horizontal
377 0x2 is set if the segment is moving to the right or down
378 thus:
379 two directions are opposites iff (dirA ^ dirB) == 0x2
380 two directions are perpendicular iff (dirA ^ dirB) == 0x1
skia.committer@gmail.comf5e67c12014-01-16 07:01:48 +0000381
caryclark@google.comf1316942011-07-26 19:54:45 +0000382 */
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000383static int rect_make_dir(SkScalar dx, SkScalar dy) {
384 return ((0 != dx) << 0) | ((dx > 0 || dy > 0) << 1);
385}
caryclark@google.comf68154a2012-11-21 15:18:06 +0000386bool SkPath::isRectContour(bool allowPartial, int* currVerb, const SkPoint** ptsPtr,
387 bool* isClosed, Direction* direction) const {
caryclark@google.comf1316942011-07-26 19:54:45 +0000388 int corners = 0;
389 SkPoint first, last;
caryclark@google.com56f233a2012-11-19 13:06:06 +0000390 const SkPoint* pts = *ptsPtr;
caryclark@google.combfe90372012-11-21 13:56:20 +0000391 const SkPoint* savePts = NULL;
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000392 first.set(0, 0);
393 last.set(0, 0);
394 int firstDirection = 0;
395 int lastDirection = 0;
396 int nextDirection = 0;
397 bool closedOrMoved = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000398 bool autoClose = false;
caryclark95bc5f32015-04-08 08:34:15 -0700399 bool insertClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000400 int verbCnt = fPathRef->countVerbs();
caryclark@google.com56f233a2012-11-19 13:06:06 +0000401 while (*currVerb < verbCnt && (!allowPartial || !autoClose)) {
caryclark95bc5f32015-04-08 08:34:15 -0700402 uint8_t verb = insertClose ? (uint8_t) kClose_Verb : fPathRef->atVerb(*currVerb);
403 switch (verb) {
caryclark@google.comf1316942011-07-26 19:54:45 +0000404 case kClose_Verb:
caryclark@google.com56f233a2012-11-19 13:06:06 +0000405 savePts = pts;
406 pts = *ptsPtr;
caryclark@google.comf1316942011-07-26 19:54:45 +0000407 autoClose = true;
caryclark95bc5f32015-04-08 08:34:15 -0700408 insertClose = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000409 case kLine_Verb: {
410 SkScalar left = last.fX;
411 SkScalar top = last.fY;
412 SkScalar right = pts->fX;
413 SkScalar bottom = pts->fY;
414 ++pts;
415 if (left != right && top != bottom) {
416 return false; // diagonal
417 }
418 if (left == right && top == bottom) {
419 break; // single point on side OK
420 }
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000421 nextDirection = rect_make_dir(right - left, bottom - top);
caryclark@google.comf1316942011-07-26 19:54:45 +0000422 if (0 == corners) {
423 firstDirection = nextDirection;
424 first = last;
425 last = pts[-1];
426 corners = 1;
427 closedOrMoved = false;
428 break;
429 }
430 if (closedOrMoved) {
431 return false; // closed followed by a line
432 }
caryclark@google.combfe90372012-11-21 13:56:20 +0000433 if (autoClose && nextDirection == firstDirection) {
434 break; // colinear with first
435 }
caryclark@google.comf1316942011-07-26 19:54:45 +0000436 closedOrMoved = autoClose;
437 if (lastDirection != nextDirection) {
438 if (++corners > 4) {
439 return false; // too many direction changes
440 }
441 }
442 last = pts[-1];
443 if (lastDirection == nextDirection) {
444 break; // colinear segment
445 }
446 // Possible values for corners are 2, 3, and 4.
447 // When corners == 3, nextDirection opposes firstDirection.
448 // Otherwise, nextDirection at corner 2 opposes corner 4.
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000449 int turn = firstDirection ^ (corners - 1);
caryclark@google.comf1316942011-07-26 19:54:45 +0000450 int directionCycle = 3 == corners ? 0 : nextDirection ^ turn;
451 if ((directionCycle ^ turn) != nextDirection) {
452 return false; // direction didn't follow cycle
453 }
454 break;
455 }
456 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000457 case kConic_Verb:
caryclark@google.comf1316942011-07-26 19:54:45 +0000458 case kCubic_Verb:
459 return false; // quadratic, cubic not allowed
460 case kMove_Verb:
caryclark95bc5f32015-04-08 08:34:15 -0700461 if (allowPartial && !autoClose && firstDirection) {
462 insertClose = true;
463 *currVerb -= 1; // try move again afterwards
464 goto addMissingClose;
465 }
caryclark@google.comf1316942011-07-26 19:54:45 +0000466 last = *pts++;
467 closedOrMoved = true;
468 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000469 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000470 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000471 break;
caryclark@google.comf1316942011-07-26 19:54:45 +0000472 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000473 *currVerb += 1;
caryclark@google.comf1316942011-07-26 19:54:45 +0000474 lastDirection = nextDirection;
caryclark95bc5f32015-04-08 08:34:15 -0700475addMissingClose:
476 ;
caryclark@google.comf1316942011-07-26 19:54:45 +0000477 }
478 // Success if 4 corners and first point equals last
caryclark@google.combfe90372012-11-21 13:56:20 +0000479 bool result = 4 == corners && (first == last || autoClose);
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000480 if (!result) {
481 // check if we are just an incomplete rectangle, in which case we can
482 // return true, but not claim to be closed.
483 // e.g.
484 // 3 sided rectangle
485 // 4 sided but the last edge is not long enough to reach the start
486 //
487 SkScalar closeX = first.x() - last.x();
488 SkScalar closeY = first.y() - last.y();
489 if (closeX && closeY) {
490 return false; // we're diagonal, abort (can we ever reach this?)
491 }
492 int closeDirection = rect_make_dir(closeX, closeY);
493 // make sure the close-segment doesn't double-back on itself
494 if (3 == corners || (4 == corners && closeDirection == lastDirection)) {
495 result = true;
496 autoClose = false; // we are not closed
497 }
498 }
caryclark@google.combfe90372012-11-21 13:56:20 +0000499 if (savePts) {
500 *ptsPtr = savePts;
501 }
caryclark@google.comf68154a2012-11-21 15:18:06 +0000502 if (result && isClosed) {
503 *isClosed = autoClose;
504 }
505 if (result && direction) {
sugoi@google.com12b4e272012-12-06 20:13:11 +0000506 *direction = firstDirection == ((lastDirection + 1) & 3) ? kCCW_Direction : kCW_Direction;
caryclark@google.comf68154a2012-11-21 15:18:06 +0000507 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000508 return result;
509}
510
robertphillips4f662e62014-12-29 14:06:51 -0800511bool SkPath::isRect(SkRect* rect, bool* isClosed, Direction* direction) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000512 SkDEBUGCODE(this->validate();)
513 int currVerb = 0;
514 const SkPoint* pts = fPathRef->points();
robertphillipsfe7c4272014-12-29 11:36:39 -0800515 const SkPoint* first = pts;
robertphillips4f662e62014-12-29 14:06:51 -0800516 if (!this->isRectContour(false, &currVerb, &pts, isClosed, direction)) {
robertphillipsfe7c4272014-12-29 11:36:39 -0800517 return false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000518 }
robertphillipsfe7c4272014-12-29 11:36:39 -0800519 if (rect) {
robertphillips4f662e62014-12-29 14:06:51 -0800520 int32_t num = SkToS32(pts - first);
521 if (num) {
522 rect->set(first, num);
robertphillipsfe7c4272014-12-29 11:36:39 -0800523 } else {
524 // 'pts' isn't updated for open rects
525 *rect = this->getBounds();
526 }
527 }
528 return true;
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000529}
skia.committer@gmail.com020b25b2013-06-22 07:00:58 +0000530
caryclark95bc5f32015-04-08 08:34:15 -0700531bool SkPath::isNestedFillRects(SkRect rects[2], Direction dirs[2]) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000532 SkDEBUGCODE(this->validate();)
533 int currVerb = 0;
534 const SkPoint* pts = fPathRef->points();
535 const SkPoint* first = pts;
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000536 Direction testDirs[2];
537 if (!isRectContour(true, &currVerb, &pts, NULL, &testDirs[0])) {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000538 return false;
539 }
540 const SkPoint* last = pts;
541 SkRect testRects[2];
caryclark95bc5f32015-04-08 08:34:15 -0700542 bool isClosed;
543 if (isRectContour(false, &currVerb, &pts, &isClosed, &testDirs[1])) {
scroggo@google.com614f9e32013-05-09 18:05:32 +0000544 testRects[0].set(first, SkToS32(last - first));
caryclark95bc5f32015-04-08 08:34:15 -0700545 if (!isClosed) {
546 pts = fPathRef->points() + fPathRef->countPoints();
547 }
scroggo@google.com614f9e32013-05-09 18:05:32 +0000548 testRects[1].set(last, SkToS32(pts - last));
caryclark@google.com56f233a2012-11-19 13:06:06 +0000549 if (testRects[0].contains(testRects[1])) {
550 if (rects) {
551 rects[0] = testRects[0];
552 rects[1] = testRects[1];
553 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000554 if (dirs) {
555 dirs[0] = testDirs[0];
556 dirs[1] = testDirs[1];
557 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000558 return true;
559 }
560 if (testRects[1].contains(testRects[0])) {
561 if (rects) {
562 rects[0] = testRects[1];
563 rects[1] = testRects[0];
564 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000565 if (dirs) {
566 dirs[0] = testDirs[1];
567 dirs[1] = testDirs[0];
568 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000569 return true;
570 }
571 }
572 return false;
573}
574
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000575int SkPath::countPoints() const {
576 return fPathRef->countPoints();
577}
578
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000579int SkPath::getPoints(SkPoint dst[], int max) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000580 SkDEBUGCODE(this->validate();)
581
582 SkASSERT(max >= 0);
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000583 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000584 int count = SkMin32(max, fPathRef->countPoints());
585 memcpy(dst, fPathRef->points(), count * sizeof(SkPoint));
586 return fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000587}
588
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000589SkPoint SkPath::getPoint(int index) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000590 if ((unsigned)index < (unsigned)fPathRef->countPoints()) {
591 return fPathRef->atPoint(index);
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000592 }
593 return SkPoint::Make(0, 0);
594}
595
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000596int SkPath::countVerbs() const {
597 return fPathRef->countVerbs();
598}
599
600static inline void copy_verbs_reverse(uint8_t* inorderDst,
601 const uint8_t* reversedSrc,
602 int count) {
603 for (int i = 0; i < count; ++i) {
604 inorderDst[i] = reversedSrc[~i];
605 }
606}
607
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000608int SkPath::getVerbs(uint8_t dst[], int max) const {
609 SkDEBUGCODE(this->validate();)
610
611 SkASSERT(max >= 0);
612 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000613 int count = SkMin32(max, fPathRef->countVerbs());
614 copy_verbs_reverse(dst, fPathRef->verbs(), count);
615 return fPathRef->countVerbs();
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000616}
617
reed@google.com294dd7b2011-10-11 11:58:32 +0000618bool SkPath::getLastPt(SkPoint* lastPt) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000619 SkDEBUGCODE(this->validate();)
620
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000621 int count = fPathRef->countPoints();
reed@google.com294dd7b2011-10-11 11:58:32 +0000622 if (count > 0) {
623 if (lastPt) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000624 *lastPt = fPathRef->atPoint(count - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000625 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000626 return true;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000627 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000628 if (lastPt) {
629 lastPt->set(0, 0);
630 }
631 return false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000632}
633
634void SkPath::setLastPt(SkScalar x, SkScalar y) {
635 SkDEBUGCODE(this->validate();)
636
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000637 int count = fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000638 if (count == 0) {
639 this->moveTo(x, y);
640 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000641 SkPathRef::Editor ed(&fPathRef);
642 ed.atPoint(count-1)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000643 }
644}
645
reed@google.com04863fa2011-05-15 04:08:24 +0000646void SkPath::setConvexity(Convexity c) {
647 if (fConvexity != c) {
648 fConvexity = c;
reed@google.com04863fa2011-05-15 04:08:24 +0000649 }
650}
651
reed@android.com8a1c16f2008-12-17 15:59:43 +0000652//////////////////////////////////////////////////////////////////////////////
653// Construction methods
654
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000655#define DIRTY_AFTER_EDIT \
656 do { \
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000657 fConvexity = kUnknown_Convexity; \
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000658 fDirection = kUnknown_Direction; \
reed@google.comb54455e2011-05-16 14:16:04 +0000659 } while (0)
660
reed@android.com8a1c16f2008-12-17 15:59:43 +0000661void SkPath::incReserve(U16CPU inc) {
662 SkDEBUGCODE(this->validate();)
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000663 SkPathRef::Editor(&fPathRef, inc, inc);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000664 SkDEBUGCODE(this->validate();)
665}
666
667void SkPath::moveTo(SkScalar x, SkScalar y) {
668 SkDEBUGCODE(this->validate();)
669
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000670 SkPathRef::Editor ed(&fPathRef);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000671
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000672 // remember our index
673 fLastMoveToIndex = fPathRef->countPoints();
674
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000675 ed.growForVerb(kMove_Verb)->set(x, y);
bsalomonb17c1292014-08-28 14:04:55 -0700676
677 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000678}
679
680void SkPath::rMoveTo(SkScalar x, SkScalar y) {
681 SkPoint pt;
682 this->getLastPt(&pt);
683 this->moveTo(pt.fX + x, pt.fY + y);
684}
685
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000686void SkPath::injectMoveToIfNeeded() {
687 if (fLastMoveToIndex < 0) {
688 SkScalar x, y;
689 if (fPathRef->countVerbs() == 0) {
690 x = y = 0;
691 } else {
692 const SkPoint& pt = fPathRef->atPoint(~fLastMoveToIndex);
693 x = pt.fX;
694 y = pt.fY;
695 }
696 this->moveTo(x, y);
697 }
698}
699
reed@android.com8a1c16f2008-12-17 15:59:43 +0000700void SkPath::lineTo(SkScalar x, SkScalar y) {
701 SkDEBUGCODE(this->validate();)
702
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000703 this->injectMoveToIfNeeded();
704
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000705 SkPathRef::Editor ed(&fPathRef);
706 ed.growForVerb(kLine_Verb)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000707
reed@google.comb54455e2011-05-16 14:16:04 +0000708 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000709}
710
711void SkPath::rLineTo(SkScalar x, SkScalar y) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000712 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000713 SkPoint pt;
714 this->getLastPt(&pt);
715 this->lineTo(pt.fX + x, pt.fY + y);
716}
717
718void SkPath::quadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
719 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000720
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000721 this->injectMoveToIfNeeded();
722
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000723 SkPathRef::Editor ed(&fPathRef);
724 SkPoint* pts = ed.growForVerb(kQuad_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000725 pts[0].set(x1, y1);
726 pts[1].set(x2, y2);
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000727
reed@google.comb54455e2011-05-16 14:16:04 +0000728 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000729}
730
731void SkPath::rQuadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000732 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000733 SkPoint pt;
734 this->getLastPt(&pt);
735 this->quadTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2);
736}
737
reed@google.com277c3f82013-05-31 15:17:50 +0000738void SkPath::conicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
739 SkScalar w) {
740 // check for <= 0 or NaN with this test
741 if (!(w > 0)) {
742 this->lineTo(x2, y2);
743 } else if (!SkScalarIsFinite(w)) {
744 this->lineTo(x1, y1);
745 this->lineTo(x2, y2);
746 } else if (SK_Scalar1 == w) {
747 this->quadTo(x1, y1, x2, y2);
748 } else {
749 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000750
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000751 this->injectMoveToIfNeeded();
752
reed@google.com277c3f82013-05-31 15:17:50 +0000753 SkPathRef::Editor ed(&fPathRef);
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000754 SkPoint* pts = ed.growForVerb(kConic_Verb, w);
reed@google.com277c3f82013-05-31 15:17:50 +0000755 pts[0].set(x1, y1);
756 pts[1].set(x2, y2);
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000757
reed@google.com277c3f82013-05-31 15:17:50 +0000758 DIRTY_AFTER_EDIT;
759 }
760}
761
762void SkPath::rConicTo(SkScalar dx1, SkScalar dy1, SkScalar dx2, SkScalar dy2,
763 SkScalar w) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000764 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@google.com277c3f82013-05-31 15:17:50 +0000765 SkPoint pt;
766 this->getLastPt(&pt);
767 this->conicTo(pt.fX + dx1, pt.fY + dy1, pt.fX + dx2, pt.fY + dy2, w);
768}
769
reed@android.com8a1c16f2008-12-17 15:59:43 +0000770void SkPath::cubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
771 SkScalar x3, SkScalar y3) {
772 SkDEBUGCODE(this->validate();)
773
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000774 this->injectMoveToIfNeeded();
775
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000776 SkPathRef::Editor ed(&fPathRef);
777 SkPoint* pts = ed.growForVerb(kCubic_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000778 pts[0].set(x1, y1);
779 pts[1].set(x2, y2);
780 pts[2].set(x3, y3);
reed@android.com8a1c16f2008-12-17 15:59:43 +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::rCubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
786 SkScalar x3, SkScalar y3) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000787 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000788 SkPoint pt;
789 this->getLastPt(&pt);
790 this->cubicTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2,
791 pt.fX + x3, pt.fY + y3);
792}
793
794void SkPath::close() {
795 SkDEBUGCODE(this->validate();)
796
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000797 int count = fPathRef->countVerbs();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000798 if (count > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000799 switch (fPathRef->atVerb(count - 1)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000800 case kLine_Verb:
801 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000802 case kConic_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +0000803 case kCubic_Verb:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000804 case kMove_Verb: {
805 SkPathRef::Editor ed(&fPathRef);
806 ed.growForVerb(kClose_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000807 break;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000808 }
reed@google.com277c3f82013-05-31 15:17:50 +0000809 case kClose_Verb:
reed@google.comfa2f2a42013-05-30 15:29:48 +0000810 // don't add a close if it's the first verb or a repeat
reed@google.com7950a9e2013-05-30 14:57:55 +0000811 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000812 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000813 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000814 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000815 }
816 }
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000817
818 // signal that we need a moveTo to follow us (unless we're done)
819#if 0
820 if (fLastMoveToIndex >= 0) {
821 fLastMoveToIndex = ~fLastMoveToIndex;
822 }
823#else
824 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
825#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000826}
827
828///////////////////////////////////////////////////////////////////////////////
reed@google.comabf15c12011-01-18 20:35:51 +0000829
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000830static void assert_known_direction(int dir) {
831 SkASSERT(SkPath::kCW_Direction == dir || SkPath::kCCW_Direction == dir);
832}
833
reed@android.com8a1c16f2008-12-17 15:59:43 +0000834void SkPath::addRect(const SkRect& rect, Direction dir) {
835 this->addRect(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, dir);
836}
837
838void SkPath::addRect(SkScalar left, SkScalar top, SkScalar right,
839 SkScalar bottom, Direction dir) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000840 assert_known_direction(dir);
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000841 fDirection = this->hasOnlyMoveTos() ? dir : kUnknown_Direction;
842 SkAutoDisableDirectionCheck addc(this);
843
reed@android.com8a1c16f2008-12-17 15:59:43 +0000844 SkAutoPathBoundsUpdate apbu(this, left, top, right, bottom);
845
846 this->incReserve(5);
847
848 this->moveTo(left, top);
849 if (dir == kCCW_Direction) {
850 this->lineTo(left, bottom);
851 this->lineTo(right, bottom);
852 this->lineTo(right, top);
853 } else {
854 this->lineTo(right, top);
855 this->lineTo(right, bottom);
856 this->lineTo(left, bottom);
857 }
858 this->close();
859}
860
reed@google.com744faba2012-05-29 19:54:52 +0000861void SkPath::addPoly(const SkPoint pts[], int count, bool close) {
862 SkDEBUGCODE(this->validate();)
863 if (count <= 0) {
864 return;
865 }
866
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000867 fLastMoveToIndex = fPathRef->countPoints();
868
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000869 // +close makes room for the extra kClose_Verb
870 SkPathRef::Editor ed(&fPathRef, count+close, count);
871
872 ed.growForVerb(kMove_Verb)->set(pts[0].fX, pts[0].fY);
reed@google.com744faba2012-05-29 19:54:52 +0000873 if (count > 1) {
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000874 SkPoint* p = ed.growForRepeatedVerb(kLine_Verb, count - 1);
875 memcpy(p, &pts[1], (count-1) * sizeof(SkPoint));
reed@google.com744faba2012-05-29 19:54:52 +0000876 }
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000877
reed@google.com744faba2012-05-29 19:54:52 +0000878 if (close) {
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000879 ed.growForVerb(kClose_Verb);
caryclark63c684a2015-02-25 09:04:04 -0800880 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
reed@google.com744faba2012-05-29 19:54:52 +0000881 }
882
reed@google.com744faba2012-05-29 19:54:52 +0000883 DIRTY_AFTER_EDIT;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000884 SkDEBUGCODE(this->validate();)
reed@google.com744faba2012-05-29 19:54:52 +0000885}
886
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000887#include "SkGeometry.h"
888
reedf90ea012015-01-29 12:03:58 -0800889static bool arc_is_lone_point(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
890 SkPoint* pt) {
891 if (0 == sweepAngle && (0 == startAngle || SkIntToScalar(360) == startAngle)) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000892 // Chrome uses this path to move into and out of ovals. If not
893 // treated as a special case the moves can distort the oval's
894 // bounding box (and break the circle special case).
reedf90ea012015-01-29 12:03:58 -0800895 pt->set(oval.fRight, oval.centerY());
896 return true;
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000897 } else if (0 == oval.width() && 0 == oval.height()) {
898 // Chrome will sometimes create 0 radius round rects. Having degenerate
899 // quad segments in the path prevents the path from being recognized as
900 // a rect.
901 // TODO: optimizing the case where only one of width or height is zero
902 // should also be considered. This case, however, doesn't seem to be
903 // as common as the single point case.
reedf90ea012015-01-29 12:03:58 -0800904 pt->set(oval.fRight, oval.fTop);
905 return true;
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000906 }
reedf90ea012015-01-29 12:03:58 -0800907 return false;
908}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000909
reedd5d27d92015-02-09 13:54:43 -0800910// Return the unit vectors pointing at the start/stop points for the given start/sweep angles
911//
912static void angles_to_unit_vectors(SkScalar startAngle, SkScalar sweepAngle,
913 SkVector* startV, SkVector* stopV, SkRotationDirection* dir) {
914 startV->fY = SkScalarSinCos(SkDegreesToRadians(startAngle), &startV->fX);
915 stopV->fY = SkScalarSinCos(SkDegreesToRadians(startAngle + sweepAngle), &stopV->fX);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000916
917 /* If the sweep angle is nearly (but less than) 360, then due to precision
reedd5d27d92015-02-09 13:54:43 -0800918 loss in radians-conversion and/or sin/cos, we may end up with coincident
919 vectors, which will fool SkBuildQuadArc into doing nothing (bad) instead
920 of drawing a nearly complete circle (good).
921 e.g. canvas.drawArc(0, 359.99, ...)
922 -vs- canvas.drawArc(0, 359.9, ...)
923 We try to detect this edge case, and tweak the stop vector
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000924 */
reedd5d27d92015-02-09 13:54:43 -0800925 if (*startV == *stopV) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000926 SkScalar sw = SkScalarAbs(sweepAngle);
927 if (sw < SkIntToScalar(360) && sw > SkIntToScalar(359)) {
928 SkScalar stopRad = SkDegreesToRadians(startAngle + sweepAngle);
929 // make a guess at a tiny angle (in radians) to tweak by
930 SkScalar deltaRad = SkScalarCopySign(SK_Scalar1/512, sweepAngle);
931 // not sure how much will be enough, so we use a loop
932 do {
933 stopRad -= deltaRad;
reedd5d27d92015-02-09 13:54:43 -0800934 stopV->fY = SkScalarSinCos(stopRad, &stopV->fX);
935 } while (*startV == *stopV);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000936 }
937 }
reedd5d27d92015-02-09 13:54:43 -0800938 *dir = sweepAngle > 0 ? kCW_SkRotationDirection : kCCW_SkRotationDirection;
939}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000940
reed9e779d42015-02-17 11:43:14 -0800941/**
942 * If this returns 0, then the caller should just line-to the singlePt, else it should
943 * ignore singlePt and append the specified number of conics.
944 */
reedd5d27d92015-02-09 13:54:43 -0800945static int build_arc_conics(const SkRect& oval, const SkVector& start, const SkVector& stop,
reed9e779d42015-02-17 11:43:14 -0800946 SkRotationDirection dir, SkConic conics[SkConic::kMaxConicsForArc],
947 SkPoint* singlePt) {
reedd5d27d92015-02-09 13:54:43 -0800948 SkMatrix matrix;
949
950 matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height()));
951 matrix.postTranslate(oval.centerX(), oval.centerY());
952
reed9e779d42015-02-17 11:43:14 -0800953 int count = SkConic::BuildUnitArc(start, stop, dir, &matrix, conics);
954 if (0 == count) {
955 matrix.mapXY(start.x(), start.y(), singlePt);
956 }
957 return count;
reedd5d27d92015-02-09 13:54:43 -0800958}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000959
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +0000960void SkPath::addRoundRect(const SkRect& rect, const SkScalar radii[],
reed@android.com8a1c16f2008-12-17 15:59:43 +0000961 Direction dir) {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +0000962 SkRRect rrect;
963 rrect.setRectRadii(rect, (const SkVector*) radii);
964 this->addRRect(rrect, dir);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000965}
966
reed@google.com4ed0fb72012-12-12 20:48:18 +0000967void SkPath::addRRect(const SkRRect& rrect, Direction dir) {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +0000968 assert_known_direction(dir);
969
970 if (rrect.isEmpty()) {
971 return;
972 }
973
reed@google.com4ed0fb72012-12-12 20:48:18 +0000974 const SkRect& bounds = rrect.getBounds();
975
976 if (rrect.isRect()) {
977 this->addRect(bounds, dir);
978 } else if (rrect.isOval()) {
979 this->addOval(bounds, dir);
reed@google.com4ed0fb72012-12-12 20:48:18 +0000980 } else {
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +0000981 fDirection = this->hasOnlyMoveTos() ? dir : kUnknown_Direction;
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +0000982
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +0000983 SkAutoPathBoundsUpdate apbu(this, bounds);
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +0000984 SkAutoDisableDirectionCheck addc(this);
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +0000985
reed1b28a3a2014-12-17 14:42:09 -0800986 const SkScalar L = bounds.fLeft;
987 const SkScalar T = bounds.fTop;
988 const SkScalar R = bounds.fRight;
989 const SkScalar B = bounds.fBottom;
990 const SkScalar W = SK_ScalarRoot2Over2;
991
992 this->incReserve(13);
993 if (kCW_Direction == dir) {
994 this->moveTo(L, B - rrect.fRadii[SkRRect::kLowerLeft_Corner].fY);
995
996 this->lineTo(L, T + rrect.fRadii[SkRRect::kUpperLeft_Corner].fY);
997 this->conicTo(L, T, L + rrect.fRadii[SkRRect::kUpperLeft_Corner].fX, T, W);
998
999 this->lineTo(R - rrect.fRadii[SkRRect::kUpperRight_Corner].fX, T);
1000 this->conicTo(R, T, R, T + rrect.fRadii[SkRRect::kUpperRight_Corner].fY, W);
1001
1002 this->lineTo(R, B - rrect.fRadii[SkRRect::kLowerRight_Corner].fY);
1003 this->conicTo(R, B, R - rrect.fRadii[SkRRect::kLowerRight_Corner].fX, B, W);
1004
1005 this->lineTo(L + rrect.fRadii[SkRRect::kLowerLeft_Corner].fX, B);
1006 this->conicTo(L, B, L, B - rrect.fRadii[SkRRect::kLowerLeft_Corner].fY, W);
1007 } else {
1008 this->moveTo(L, T + rrect.fRadii[SkRRect::kUpperLeft_Corner].fY);
1009
1010 this->lineTo(L, B - rrect.fRadii[SkRRect::kLowerLeft_Corner].fY);
1011 this->conicTo(L, B, L + rrect.fRadii[SkRRect::kLowerLeft_Corner].fX, B, W);
1012
1013 this->lineTo(R - rrect.fRadii[SkRRect::kLowerRight_Corner].fX, B);
1014 this->conicTo(R, B, R, B - rrect.fRadii[SkRRect::kLowerRight_Corner].fY, W);
1015
1016 this->lineTo(R, T + rrect.fRadii[SkRRect::kUpperRight_Corner].fY);
1017 this->conicTo(R, T, R - rrect.fRadii[SkRRect::kUpperRight_Corner].fX, T, W);
1018
1019 this->lineTo(L + rrect.fRadii[SkRRect::kUpperLeft_Corner].fX, T);
1020 this->conicTo(L, T, L, T + rrect.fRadii[SkRRect::kUpperLeft_Corner].fY, W);
1021 }
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001022 this->close();
reed@google.com4ed0fb72012-12-12 20:48:18 +00001023 }
reed5bcbe912014-12-15 12:28:33 -08001024 SkDEBUGCODE(fPathRef->validate();)
reed@google.com4ed0fb72012-12-12 20:48:18 +00001025}
1026
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001027bool SkPath::hasOnlyMoveTos() const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001028 int count = fPathRef->countVerbs();
1029 const uint8_t* verbs = const_cast<const SkPathRef*>(fPathRef.get())->verbsMemBegin();
1030 for (int i = 0; i < count; ++i) {
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001031 if (*verbs == kLine_Verb ||
1032 *verbs == kQuad_Verb ||
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001033 *verbs == kConic_Verb ||
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001034 *verbs == kCubic_Verb) {
1035 return false;
1036 }
1037 ++verbs;
1038 }
1039 return true;
1040}
1041
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001042void SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
1043 Direction dir) {
1044 assert_known_direction(dir);
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001045
humper@google.com75e3ca12013-04-08 21:44:11 +00001046 if (rx < 0 || ry < 0) {
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001047 SkErrorInternals::SetError( kInvalidArgument_SkError,
humper@google.com75e3ca12013-04-08 21:44:11 +00001048 "I got %f and %f as radii to SkPath::AddRoundRect, "
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001049 "but negative radii are not allowed.",
humper@google.com75e3ca12013-04-08 21:44:11 +00001050 SkScalarToDouble(rx), SkScalarToDouble(ry) );
1051 return;
1052 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001053
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001054 SkRRect rrect;
1055 rrect.setRectXY(rect, rx, ry);
1056 this->addRRect(rrect, dir);
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001057}
1058
reed@android.com8a1c16f2008-12-17 15:59:43 +00001059void SkPath::addOval(const SkRect& oval, Direction dir) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +00001060 assert_known_direction(dir);
1061
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001062 /* If addOval() is called after previous moveTo(),
1063 this path is still marked as an oval. This is used to
1064 fit into WebKit's calling sequences.
1065 We can't simply check isEmpty() in this case, as additional
1066 moveTo() would mark the path non empty.
1067 */
robertphillips@google.com466310d2013-12-03 16:43:54 +00001068 bool isOval = hasOnlyMoveTos();
1069 if (isOval) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001070 fDirection = dir;
1071 } else {
1072 fDirection = kUnknown_Direction;
1073 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001074
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001075 SkAutoDisableDirectionCheck addc(this);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001076
reed@android.com8a1c16f2008-12-17 15:59:43 +00001077 SkAutoPathBoundsUpdate apbu(this, oval);
1078
reed220f9262014-12-17 08:21:04 -08001079 const SkScalar L = oval.fLeft;
1080 const SkScalar T = oval.fTop;
1081 const SkScalar R = oval.fRight;
1082 const SkScalar B = oval.fBottom;
1083 const SkScalar cx = oval.centerX();
1084 const SkScalar cy = oval.centerY();
1085 const SkScalar weight = SK_ScalarRoot2Over2;
1086
1087 this->incReserve(9); // move + 4 conics
1088 this->moveTo(R, cy);
1089 if (dir == kCCW_Direction) {
1090 this->conicTo(R, T, cx, T, weight);
1091 this->conicTo(L, T, L, cy, weight);
1092 this->conicTo(L, B, cx, B, weight);
1093 this->conicTo(R, B, R, cy, weight);
1094 } else {
1095 this->conicTo(R, B, cx, B, weight);
1096 this->conicTo(L, B, L, cy, weight);
1097 this->conicTo(L, T, cx, T, weight);
1098 this->conicTo(R, T, R, cy, weight);
1099 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001100 this->close();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001101
robertphillips@google.com466310d2013-12-03 16:43:54 +00001102 SkPathRef::Editor ed(&fPathRef);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001103
robertphillips@google.com466310d2013-12-03 16:43:54 +00001104 ed.setIsOval(isOval);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001105}
1106
reed@android.com8a1c16f2008-12-17 15:59:43 +00001107void SkPath::addCircle(SkScalar x, SkScalar y, SkScalar r, Direction dir) {
1108 if (r > 0) {
1109 SkRect rect;
1110 rect.set(x - r, y - r, x + r, y + r);
1111 this->addOval(rect, dir);
1112 }
1113}
1114
reed@android.com8a1c16f2008-12-17 15:59:43 +00001115void SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
1116 bool forceMoveTo) {
1117 if (oval.width() < 0 || oval.height() < 0) {
1118 return;
1119 }
1120
reedf90ea012015-01-29 12:03:58 -08001121 if (fPathRef->countVerbs() == 0) {
1122 forceMoveTo = true;
1123 }
1124
1125 SkPoint lonePt;
1126 if (arc_is_lone_point(oval, startAngle, sweepAngle, &lonePt)) {
1127 forceMoveTo ? this->moveTo(lonePt) : this->lineTo(lonePt);
1128 return;
1129 }
1130
reedd5d27d92015-02-09 13:54:43 -08001131 SkVector startV, stopV;
1132 SkRotationDirection dir;
1133 angles_to_unit_vectors(startAngle, sweepAngle, &startV, &stopV, &dir);
1134
reed9e779d42015-02-17 11:43:14 -08001135 SkPoint singlePt;
reedd5d27d92015-02-09 13:54:43 -08001136 SkConic conics[SkConic::kMaxConicsForArc];
reed9e779d42015-02-17 11:43:14 -08001137 int count = build_arc_conics(oval, startV, stopV, dir, conics, &singlePt);
reedd5d27d92015-02-09 13:54:43 -08001138 if (count) {
1139 this->incReserve(count * 2 + 1);
1140 const SkPoint& pt = conics[0].fPts[0];
1141 forceMoveTo ? this->moveTo(pt) : this->lineTo(pt);
1142 for (int i = 0; i < count; ++i) {
1143 this->conicTo(conics[i].fPts[1], conics[i].fPts[2], conics[i].fW);
1144 }
reed9e779d42015-02-17 11:43:14 -08001145 } else {
1146 forceMoveTo ? this->moveTo(singlePt) : this->lineTo(singlePt);
reedd5d27d92015-02-09 13:54:43 -08001147 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001148}
1149
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001150void SkPath::addArc(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001151 if (oval.isEmpty() || 0 == sweepAngle) {
1152 return;
1153 }
1154
1155 const SkScalar kFullCircleAngle = SkIntToScalar(360);
1156
1157 if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
1158 this->addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction);
reedc7789042015-01-29 12:59:11 -08001159 } else {
1160 this->arcTo(oval, startAngle, sweepAngle, true);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001161 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001162}
1163
1164/*
1165 Need to handle the case when the angle is sharp, and our computed end-points
1166 for the arc go behind pt1 and/or p2...
1167*/
reedc7789042015-01-29 12:59:11 -08001168void SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2, SkScalar radius) {
reeda8b326c2014-12-09 11:50:32 -08001169 if (radius == 0) {
1170 this->lineTo(x1, y1);
1171 return;
1172 }
1173
1174 SkVector before, after;
reed@google.comabf15c12011-01-18 20:35:51 +00001175
reed@android.com8a1c16f2008-12-17 15:59:43 +00001176 // need to know our prev pt so we can construct tangent vectors
1177 {
1178 SkPoint start;
1179 this->getLastPt(&start);
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001180 // Handle degenerate cases by adding a line to the first point and
1181 // bailing out.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001182 before.setNormalize(x1 - start.fX, y1 - start.fY);
1183 after.setNormalize(x2 - x1, y2 - y1);
1184 }
reed@google.comabf15c12011-01-18 20:35:51 +00001185
reed@android.com8a1c16f2008-12-17 15:59:43 +00001186 SkScalar cosh = SkPoint::DotProduct(before, after);
1187 SkScalar sinh = SkPoint::CrossProduct(before, after);
1188
1189 if (SkScalarNearlyZero(sinh)) { // angle is too tight
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001190 this->lineTo(x1, y1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001191 return;
1192 }
reed@google.comabf15c12011-01-18 20:35:51 +00001193
reed@android.com8a1c16f2008-12-17 15:59:43 +00001194 SkScalar dist = SkScalarMulDiv(radius, SK_Scalar1 - cosh, sinh);
1195 if (dist < 0) {
1196 dist = -dist;
1197 }
1198
1199 SkScalar xx = x1 - SkScalarMul(dist, before.fX);
1200 SkScalar yy = y1 - SkScalarMul(dist, before.fY);
1201 SkRotationDirection arcDir;
1202
1203 // now turn before/after into normals
1204 if (sinh > 0) {
1205 before.rotateCCW();
1206 after.rotateCCW();
1207 arcDir = kCW_SkRotationDirection;
1208 } else {
1209 before.rotateCW();
1210 after.rotateCW();
1211 arcDir = kCCW_SkRotationDirection;
1212 }
1213
1214 SkMatrix matrix;
1215 SkPoint pts[kSkBuildQuadArcStorage];
reed@google.comabf15c12011-01-18 20:35:51 +00001216
reed@android.com8a1c16f2008-12-17 15:59:43 +00001217 matrix.setScale(radius, radius);
1218 matrix.postTranslate(xx - SkScalarMul(radius, before.fX),
1219 yy - SkScalarMul(radius, before.fY));
reed@google.comabf15c12011-01-18 20:35:51 +00001220
reed@android.com8a1c16f2008-12-17 15:59:43 +00001221 int count = SkBuildQuadArc(before, after, arcDir, &matrix, pts);
reed@google.comabf15c12011-01-18 20:35:51 +00001222
reed@android.com8a1c16f2008-12-17 15:59:43 +00001223 this->incReserve(count);
1224 // [xx,yy] == pts[0]
1225 this->lineTo(xx, yy);
1226 for (int i = 1; i < count; i += 2) {
1227 this->quadTo(pts[i], pts[i+1]);
1228 }
1229}
1230
1231///////////////////////////////////////////////////////////////////////////////
1232
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001233void SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy, AddPathMode mode) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001234 SkMatrix matrix;
1235
1236 matrix.setTranslate(dx, dy);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001237 this->addPath(path, matrix, mode);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001238}
1239
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001240void SkPath::addPath(const SkPath& path, const SkMatrix& matrix, AddPathMode mode) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001241 SkPathRef::Editor(&fPathRef, path.countVerbs(), path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001242
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001243 RawIter iter(path);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001244 SkPoint pts[4];
1245 Verb verb;
1246
1247 SkMatrix::MapPtsProc proc = matrix.getMapPtsProc();
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001248 bool firstVerb = true;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001249 while ((verb = iter.next(pts)) != kDone_Verb) {
1250 switch (verb) {
1251 case kMove_Verb:
1252 proc(matrix, &pts[0], &pts[0], 1);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001253 if (firstVerb && mode == kExtend_AddPathMode && !isEmpty()) {
1254 injectMoveToIfNeeded(); // In case last contour is closed
1255 this->lineTo(pts[0]);
1256 } else {
1257 this->moveTo(pts[0]);
1258 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001259 break;
1260 case kLine_Verb:
1261 proc(matrix, &pts[1], &pts[1], 1);
1262 this->lineTo(pts[1]);
1263 break;
1264 case kQuad_Verb:
1265 proc(matrix, &pts[1], &pts[1], 2);
1266 this->quadTo(pts[1], pts[2]);
1267 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001268 case kConic_Verb:
1269 proc(matrix, &pts[1], &pts[1], 2);
1270 this->conicTo(pts[1], pts[2], iter.conicWeight());
1271 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001272 case kCubic_Verb:
1273 proc(matrix, &pts[1], &pts[1], 3);
1274 this->cubicTo(pts[1], pts[2], pts[3]);
1275 break;
1276 case kClose_Verb:
1277 this->close();
1278 break;
1279 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001280 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001281 }
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001282 firstVerb = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001283 }
1284}
1285
1286///////////////////////////////////////////////////////////////////////////////
1287
reed@google.com277c3f82013-05-31 15:17:50 +00001288static int pts_in_verb(unsigned verb) {
1289 static const uint8_t gPtsInVerb[] = {
1290 1, // kMove
1291 1, // kLine
1292 2, // kQuad
1293 2, // kConic
1294 3, // kCubic
1295 0, // kClose
1296 0 // kDone
1297 };
1298
1299 SkASSERT(verb < SK_ARRAY_COUNT(gPtsInVerb));
1300 return gPtsInVerb[verb];
1301}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001302
reed@android.com8a1c16f2008-12-17 15:59:43 +00001303// ignore the last point of the 1st contour
1304void SkPath::reversePathTo(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001305 int i, vcount = path.fPathRef->countVerbs();
1306 // exit early if the path is empty, or just has a moveTo.
1307 if (vcount < 2) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001308 return;
1309 }
1310
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001311 SkPathRef::Editor(&fPathRef, vcount, path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001312
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001313 const uint8_t* verbs = path.fPathRef->verbs();
1314 const SkPoint* pts = path.fPathRef->points();
reed@google.com277c3f82013-05-31 15:17:50 +00001315 const SkScalar* conicWeights = path.fPathRef->conicWeights();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001316
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001317 SkASSERT(verbs[~0] == kMove_Verb);
1318 for (i = 1; i < vcount; ++i) {
reed@google.com277c3f82013-05-31 15:17:50 +00001319 unsigned v = verbs[~i];
1320 int n = pts_in_verb(v);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001321 if (n == 0) {
1322 break;
1323 }
1324 pts += n;
reed@google.com277c3f82013-05-31 15:17:50 +00001325 conicWeights += (SkPath::kConic_Verb == v);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001326 }
1327
1328 while (--i > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001329 switch (verbs[~i]) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001330 case kLine_Verb:
1331 this->lineTo(pts[-1].fX, pts[-1].fY);
1332 break;
1333 case kQuad_Verb:
1334 this->quadTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY);
1335 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001336 case kConic_Verb:
1337 this->conicTo(pts[-1], pts[-2], *--conicWeights);
1338 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001339 case kCubic_Verb:
1340 this->cubicTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY,
1341 pts[-3].fX, pts[-3].fY);
1342 break;
1343 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001344 SkDEBUGFAIL("bad verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001345 break;
1346 }
reed@google.com277c3f82013-05-31 15:17:50 +00001347 pts -= pts_in_verb(verbs[~i]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001348 }
1349}
1350
reed@google.com63d73742012-01-10 15:33:12 +00001351void SkPath::reverseAddPath(const SkPath& src) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001352 SkPathRef::Editor ed(&fPathRef, src.fPathRef->countPoints(), src.fPathRef->countVerbs());
reed@google.com63d73742012-01-10 15:33:12 +00001353
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001354 const SkPoint* pts = src.fPathRef->pointsEnd();
1355 // we will iterator through src's verbs backwards
1356 const uint8_t* verbs = src.fPathRef->verbsMemBegin(); // points at the last verb
1357 const uint8_t* verbsEnd = src.fPathRef->verbs(); // points just past the first verb
reed@google.com277c3f82013-05-31 15:17:50 +00001358 const SkScalar* conicWeights = src.fPathRef->conicWeightsEnd();
reed@google.com63d73742012-01-10 15:33:12 +00001359
1360 bool needMove = true;
1361 bool needClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001362 while (verbs < verbsEnd) {
1363 uint8_t v = *(verbs++);
reed@google.com277c3f82013-05-31 15:17:50 +00001364 int n = pts_in_verb(v);
reed@google.com63d73742012-01-10 15:33:12 +00001365
1366 if (needMove) {
1367 --pts;
1368 this->moveTo(pts->fX, pts->fY);
1369 needMove = false;
1370 }
1371 pts -= n;
1372 switch (v) {
1373 case kMove_Verb:
1374 if (needClose) {
1375 this->close();
1376 needClose = false;
1377 }
1378 needMove = true;
1379 pts += 1; // so we see the point in "if (needMove)" above
1380 break;
1381 case kLine_Verb:
1382 this->lineTo(pts[0]);
1383 break;
1384 case kQuad_Verb:
1385 this->quadTo(pts[1], pts[0]);
1386 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001387 case kConic_Verb:
1388 this->conicTo(pts[1], pts[0], *--conicWeights);
1389 break;
reed@google.com63d73742012-01-10 15:33:12 +00001390 case kCubic_Verb:
1391 this->cubicTo(pts[2], pts[1], pts[0]);
1392 break;
1393 case kClose_Verb:
1394 needClose = true;
1395 break;
1396 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00001397 SkDEBUGFAIL("unexpected verb");
reed@google.com63d73742012-01-10 15:33:12 +00001398 }
1399 }
1400}
1401
reed@android.com8a1c16f2008-12-17 15:59:43 +00001402///////////////////////////////////////////////////////////////////////////////
1403
1404void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
1405 SkMatrix matrix;
1406
1407 matrix.setTranslate(dx, dy);
1408 this->transform(matrix, dst);
1409}
1410
reed@android.com8a1c16f2008-12-17 15:59:43 +00001411static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
1412 int level = 2) {
1413 if (--level >= 0) {
1414 SkPoint tmp[7];
1415
1416 SkChopCubicAtHalf(pts, tmp);
1417 subdivide_cubic_to(path, &tmp[0], level);
1418 subdivide_cubic_to(path, &tmp[3], level);
1419 } else {
1420 path->cubicTo(pts[1], pts[2], pts[3]);
1421 }
1422}
1423
1424void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
1425 SkDEBUGCODE(this->validate();)
1426 if (dst == NULL) {
1427 dst = (SkPath*)this;
1428 }
1429
tomhudson@google.com8d430182011-06-06 19:11:19 +00001430 if (matrix.hasPerspective()) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001431 SkPath tmp;
1432 tmp.fFillType = fFillType;
1433
1434 SkPath::Iter iter(*this, false);
1435 SkPoint pts[4];
1436 SkPath::Verb verb;
1437
reed@google.com4a3b7142012-05-16 17:16:46 +00001438 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001439 switch (verb) {
1440 case kMove_Verb:
1441 tmp.moveTo(pts[0]);
1442 break;
1443 case kLine_Verb:
1444 tmp.lineTo(pts[1]);
1445 break;
1446 case kQuad_Verb:
reed220f9262014-12-17 08:21:04 -08001447 // promote the quad to a conic
1448 tmp.conicTo(pts[1], pts[2],
1449 SkConic::TransformW(pts, SK_Scalar1, matrix));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001450 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001451 case kConic_Verb:
reed220f9262014-12-17 08:21:04 -08001452 tmp.conicTo(pts[1], pts[2],
1453 SkConic::TransformW(pts, iter.conicWeight(), matrix));
reed@google.com277c3f82013-05-31 15:17:50 +00001454 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001455 case kCubic_Verb:
1456 subdivide_cubic_to(&tmp, pts);
1457 break;
1458 case kClose_Verb:
1459 tmp.close();
1460 break;
1461 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001462 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001463 break;
1464 }
1465 }
1466
1467 dst->swap(tmp);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001468 SkPathRef::Editor ed(&dst->fPathRef);
1469 matrix.mapPoints(ed.points(), ed.pathRef()->countPoints());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001470 dst->fDirection = kUnknown_Direction;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001471 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001472 SkPathRef::CreateTransformedCopy(&dst->fPathRef, *fPathRef.get(), matrix);
1473
reed@android.com8a1c16f2008-12-17 15:59:43 +00001474 if (this != dst) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001475 dst->fFillType = fFillType;
reed@google.com2a6f8ab2011-10-25 18:41:23 +00001476 dst->fConvexity = fConvexity;
jvanverthb3eb6872014-10-24 07:12:51 -07001477 dst->fIsVolatile = fIsVolatile;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001478 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001479
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001480 if (kUnknown_Direction == fDirection) {
1481 dst->fDirection = kUnknown_Direction;
1482 } else {
1483 SkScalar det2x2 =
1484 SkScalarMul(matrix.get(SkMatrix::kMScaleX), matrix.get(SkMatrix::kMScaleY)) -
1485 SkScalarMul(matrix.get(SkMatrix::kMSkewX), matrix.get(SkMatrix::kMSkewY));
1486 if (det2x2 < 0) {
1487 dst->fDirection = SkPath::OppositeDirection(static_cast<Direction>(fDirection));
1488 } else if (det2x2 > 0) {
1489 dst->fDirection = fDirection;
1490 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001491 dst->fConvexity = kUnknown_Convexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001492 dst->fDirection = kUnknown_Direction;
1493 }
1494 }
1495
reed@android.com8a1c16f2008-12-17 15:59:43 +00001496 SkDEBUGCODE(dst->validate();)
1497 }
1498}
1499
reed@android.com8a1c16f2008-12-17 15:59:43 +00001500///////////////////////////////////////////////////////////////////////////////
1501///////////////////////////////////////////////////////////////////////////////
1502
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001503enum SegmentState {
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001504 kEmptyContour_SegmentState, // The current contour is empty. We may be
1505 // starting processing or we may have just
1506 // closed a contour.
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001507 kAfterMove_SegmentState, // We have seen a move, but nothing else.
1508 kAfterPrimitive_SegmentState // We have seen a primitive but not yet
1509 // closed the path. Also the initial state.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001510};
1511
1512SkPath::Iter::Iter() {
1513#ifdef SK_DEBUG
1514 fPts = NULL;
reed@google.com277c3f82013-05-31 15:17:50 +00001515 fConicWeights = NULL;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001516 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001517 fForceClose = fCloseLine = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001518 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001519#endif
1520 // need to init enough to make next() harmlessly return kDone_Verb
1521 fVerbs = NULL;
1522 fVerbStop = NULL;
1523 fNeedClose = false;
1524}
1525
1526SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
1527 this->setPath(path, forceClose);
1528}
1529
1530void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001531 fPts = path.fPathRef->points();
1532 fVerbs = path.fPathRef->verbs();
1533 fVerbStop = path.fPathRef->verbsMemBegin();
reed@google.com277c3f82013-05-31 15:17:50 +00001534 fConicWeights = path.fPathRef->conicWeights() - 1; // begin one behind
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001535 fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org72785c42011-12-29 21:03:28 +00001536 fMoveTo.fX = fMoveTo.fY = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001537 fForceClose = SkToU8(forceClose);
1538 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001539 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001540}
1541
1542bool SkPath::Iter::isClosedContour() const {
1543 if (fVerbs == NULL || fVerbs == fVerbStop) {
1544 return false;
1545 }
1546 if (fForceClose) {
1547 return true;
1548 }
1549
1550 const uint8_t* verbs = fVerbs;
1551 const uint8_t* stop = fVerbStop;
reed@google.comabf15c12011-01-18 20:35:51 +00001552
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001553 if (kMove_Verb == *(verbs - 1)) {
1554 verbs -= 1; // skip the initial moveto
reed@android.com8a1c16f2008-12-17 15:59:43 +00001555 }
1556
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001557 while (verbs > stop) {
1558 // verbs points one beyond the current verb, decrement first.
1559 unsigned v = *(--verbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001560 if (kMove_Verb == v) {
1561 break;
1562 }
1563 if (kClose_Verb == v) {
1564 return true;
1565 }
1566 }
1567 return false;
1568}
1569
1570SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001571 SkASSERT(pts);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001572 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001573 // A special case: if both points are NaN, SkPoint::operation== returns
1574 // false, but the iterator expects that they are treated as the same.
1575 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001576 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1577 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001578 return kClose_Verb;
1579 }
1580
reed@google.com9e25dbf2012-05-15 17:05:38 +00001581 pts[0] = fLastPt;
1582 pts[1] = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001583 fLastPt = fMoveTo;
1584 fCloseLine = true;
1585 return kLine_Verb;
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +00001586 } else {
1587 pts[0] = fMoveTo;
1588 return kClose_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001589 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001590}
1591
reed@google.com9e25dbf2012-05-15 17:05:38 +00001592const SkPoint& SkPath::Iter::cons_moveTo() {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001593 if (fSegmentState == kAfterMove_SegmentState) {
1594 // Set the first return pt to the move pt
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001595 fSegmentState = kAfterPrimitive_SegmentState;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001596 return fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001597 } else {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001598 SkASSERT(fSegmentState == kAfterPrimitive_SegmentState);
1599 // Set the first return pt to the last pt of the previous primitive.
reed@google.com9e25dbf2012-05-15 17:05:38 +00001600 return fPts[-1];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001601 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001602}
1603
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001604void SkPath::Iter::consumeDegenerateSegments() {
1605 // We need to step over anything that will not move the current draw point
1606 // forward before the next move is seen
1607 const uint8_t* lastMoveVerb = 0;
1608 const SkPoint* lastMovePt = 0;
robertphillipsb8de1f42015-02-23 11:17:01 -08001609 const SkScalar* lastMoveWeight = NULL;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001610 SkPoint lastPt = fLastPt;
1611 while (fVerbs != fVerbStop) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001612 unsigned verb = *(fVerbs - 1); // fVerbs is one beyond the current verb
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001613 switch (verb) {
1614 case kMove_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001615 // Keep a record of this most recent move
1616 lastMoveVerb = fVerbs;
1617 lastMovePt = fPts;
robertphillipsb8de1f42015-02-23 11:17:01 -08001618 lastMoveWeight = fConicWeights;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001619 lastPt = fPts[0];
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001620 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001621 fPts++;
1622 break;
1623
1624 case kClose_Verb:
schenney@chromium.org7e963602012-06-13 17:05:43 +00001625 // A close when we are in a segment is always valid except when it
1626 // follows a move which follows a segment.
1627 if (fSegmentState == kAfterPrimitive_SegmentState && !lastMoveVerb) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001628 return;
1629 }
1630 // A close at any other time must be ignored
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001631 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001632 break;
1633
1634 case kLine_Verb:
1635 if (!IsLineDegenerate(lastPt, fPts[0])) {
1636 if (lastMoveVerb) {
1637 fVerbs = lastMoveVerb;
1638 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001639 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001640 return;
1641 }
1642 return;
1643 }
1644 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001645 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001646 fPts++;
1647 break;
1648
reed@google.com277c3f82013-05-31 15:17:50 +00001649 case kConic_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001650 case kQuad_Verb:
1651 if (!IsQuadDegenerate(lastPt, fPts[0], fPts[1])) {
1652 if (lastMoveVerb) {
1653 fVerbs = lastMoveVerb;
1654 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001655 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001656 return;
1657 }
1658 return;
1659 }
1660 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001661 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001662 fPts += 2;
reed@google.com277c3f82013-05-31 15:17:50 +00001663 fConicWeights += (kConic_Verb == verb);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001664 break;
1665
1666 case kCubic_Verb:
1667 if (!IsCubicDegenerate(lastPt, fPts[0], fPts[1], fPts[2])) {
1668 if (lastMoveVerb) {
1669 fVerbs = lastMoveVerb;
1670 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001671 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001672 return;
1673 }
1674 return;
1675 }
1676 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001677 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001678 fPts += 3;
1679 break;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001680
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001681 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001682 SkDEBUGFAIL("Should never see kDone_Verb");
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001683 }
1684 }
1685}
1686
reed@google.com4a3b7142012-05-16 17:16:46 +00001687SkPath::Verb SkPath::Iter::doNext(SkPoint ptsParam[4]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001688 SkASSERT(ptsParam);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001689
reed@android.com8a1c16f2008-12-17 15:59:43 +00001690 if (fVerbs == fVerbStop) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001691 // Close the curve if requested and if there is some curve to close
1692 if (fNeedClose && fSegmentState == kAfterPrimitive_SegmentState) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001693 if (kLine_Verb == this->autoClose(ptsParam)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001694 return kLine_Verb;
1695 }
1696 fNeedClose = false;
1697 return kClose_Verb;
1698 }
1699 return kDone_Verb;
1700 }
1701
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001702 // fVerbs is one beyond the current verb, decrement first
1703 unsigned verb = *(--fVerbs);
reed@google.com9e25dbf2012-05-15 17:05:38 +00001704 const SkPoint* SK_RESTRICT srcPts = fPts;
1705 SkPoint* SK_RESTRICT pts = ptsParam;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001706
1707 switch (verb) {
1708 case kMove_Verb:
1709 if (fNeedClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001710 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001711 verb = this->autoClose(pts);
1712 if (verb == kClose_Verb) {
1713 fNeedClose = false;
1714 }
1715 return (Verb)verb;
1716 }
1717 if (fVerbs == fVerbStop) { // might be a trailing moveto
1718 return kDone_Verb;
1719 }
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001720 fMoveTo = *srcPts;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001721 pts[0] = *srcPts;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001722 srcPts += 1;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001723 fSegmentState = kAfterMove_SegmentState;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001724 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001725 fNeedClose = fForceClose;
1726 break;
1727 case kLine_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001728 pts[0] = this->cons_moveTo();
1729 pts[1] = srcPts[0];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001730 fLastPt = srcPts[0];
1731 fCloseLine = false;
1732 srcPts += 1;
1733 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001734 case kConic_Verb:
1735 fConicWeights += 1;
1736 // fall-through
reed@android.com8a1c16f2008-12-17 15:59:43 +00001737 case kQuad_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001738 pts[0] = this->cons_moveTo();
1739 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001740 fLastPt = srcPts[1];
1741 srcPts += 2;
1742 break;
1743 case kCubic_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001744 pts[0] = this->cons_moveTo();
1745 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001746 fLastPt = srcPts[2];
1747 srcPts += 3;
1748 break;
1749 case kClose_Verb:
1750 verb = this->autoClose(pts);
1751 if (verb == kLine_Verb) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001752 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001753 } else {
1754 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001755 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001756 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001757 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001758 break;
1759 }
1760 fPts = srcPts;
1761 return (Verb)verb;
1762}
1763
1764///////////////////////////////////////////////////////////////////////////////
1765
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001766SkPath::RawIter::RawIter() {
1767#ifdef SK_DEBUG
1768 fPts = NULL;
reed@google.com277c3f82013-05-31 15:17:50 +00001769 fConicWeights = NULL;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001770 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
1771#endif
1772 // need to init enough to make next() harmlessly return kDone_Verb
1773 fVerbs = NULL;
1774 fVerbStop = NULL;
1775}
1776
1777SkPath::RawIter::RawIter(const SkPath& path) {
1778 this->setPath(path);
1779}
1780
1781void SkPath::RawIter::setPath(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001782 fPts = path.fPathRef->points();
1783 fVerbs = path.fPathRef->verbs();
1784 fVerbStop = path.fPathRef->verbsMemBegin();
reed@google.com277c3f82013-05-31 15:17:50 +00001785 fConicWeights = path.fPathRef->conicWeights() - 1; // begin one behind
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001786 fMoveTo.fX = fMoveTo.fY = 0;
1787 fLastPt.fX = fLastPt.fY = 0;
1788}
1789
1790SkPath::Verb SkPath::RawIter::next(SkPoint pts[4]) {
bsalomon49f085d2014-09-05 13:34:00 -07001791 SkASSERT(pts);
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001792 if (fVerbs == fVerbStop) {
1793 return kDone_Verb;
1794 }
1795
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001796 // fVerbs points one beyond next verb so decrement first.
1797 unsigned verb = *(--fVerbs);
1798 const SkPoint* srcPts = fPts;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001799
1800 switch (verb) {
1801 case kMove_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001802 pts[0] = *srcPts;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001803 fMoveTo = srcPts[0];
1804 fLastPt = fMoveTo;
1805 srcPts += 1;
1806 break;
1807 case kLine_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001808 pts[0] = fLastPt;
1809 pts[1] = srcPts[0];
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001810 fLastPt = srcPts[0];
1811 srcPts += 1;
1812 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001813 case kConic_Verb:
1814 fConicWeights += 1;
1815 // fall-through
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001816 case kQuad_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001817 pts[0] = fLastPt;
1818 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001819 fLastPt = srcPts[1];
1820 srcPts += 2;
1821 break;
1822 case kCubic_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001823 pts[0] = fLastPt;
1824 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001825 fLastPt = srcPts[2];
1826 srcPts += 3;
1827 break;
1828 case kClose_Verb:
1829 fLastPt = fMoveTo;
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001830 pts[0] = fMoveTo;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001831 break;
1832 }
1833 fPts = srcPts;
1834 return (Verb)verb;
1835}
1836
1837///////////////////////////////////////////////////////////////////////////////
1838
reed@android.com8a1c16f2008-12-17 15:59:43 +00001839/*
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001840 Format in compressed buffer: [ptCount, verbCount, pts[], verbs[]]
reed@android.com8a1c16f2008-12-17 15:59:43 +00001841*/
1842
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00001843size_t SkPath::writeToMemory(void* storage) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001844 SkDEBUGCODE(this->validate();)
1845
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001846 if (NULL == storage) {
robertphillips@google.comca0c8382013-09-26 12:18:23 +00001847 const int byteCount = sizeof(int32_t) + fPathRef->writeSize();
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001848 return SkAlign4(byteCount);
1849 }
1850
1851 SkWBuffer buffer(storage);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001852
robertphillips@google.com466310d2013-12-03 16:43:54 +00001853 int32_t packed = (fConvexity << kConvexity_SerializationShift) |
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001854 (fFillType << kFillType_SerializationShift) |
jvanverthb3eb6872014-10-24 07:12:51 -07001855 (fDirection << kDirection_SerializationShift) |
1856 (fIsVolatile << kIsVolatile_SerializationShift);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001857
robertphillips@google.com2972bb52012-08-07 17:32:51 +00001858 buffer.write32(packed);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001859
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001860 fPathRef->writeToBuffer(&buffer);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001861
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001862 buffer.padToAlign4();
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00001863 return buffer.pos();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001864}
1865
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00001866size_t SkPath::readFromMemory(const void* storage, size_t length) {
1867 SkRBufferWithSizeCheck buffer(storage, length);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001868
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00001869 int32_t packed;
1870 if (!buffer.readS32(&packed)) {
1871 return 0;
1872 }
1873
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001874 fConvexity = (packed >> kConvexity_SerializationShift) & 0xFF;
1875 fFillType = (packed >> kFillType_SerializationShift) & 0xFF;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001876 fDirection = (packed >> kDirection_SerializationShift) & 0x3;
jvanverthb3eb6872014-10-24 07:12:51 -07001877 fIsVolatile = (packed >> kIsVolatile_SerializationShift) & 0x1;
commit-bot@chromium.orgfed2ab62014-01-23 15:16:05 +00001878 SkPathRef* pathRef = SkPathRef::CreateFromBuffer(&buffer);
reed@google.comabf15c12011-01-18 20:35:51 +00001879
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00001880 size_t sizeRead = 0;
1881 if (buffer.isValid()) {
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00001882 fPathRef.reset(pathRef);
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00001883 SkDEBUGCODE(this->validate();)
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00001884 buffer.skipToAlign4();
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00001885 sizeRead = buffer.pos();
bsalomon49f085d2014-09-05 13:34:00 -07001886 } else if (pathRef) {
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00001887 // If the buffer is not valid, pathRef should be NULL
1888 sk_throw();
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00001889 }
1890 return sizeRead;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001891}
1892
1893///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00001894
reede05fed02014-12-15 07:59:53 -08001895#include "SkStringUtils.h"
caryclark66a5d8b2014-06-24 08:30:15 -07001896#include "SkStream.h"
reed@google.com51bbe752013-01-17 22:07:50 +00001897
reed@google.com51bbe752013-01-17 22:07:50 +00001898static void append_params(SkString* str, const char label[], const SkPoint pts[],
reede05fed02014-12-15 07:59:53 -08001899 int count, SkScalarAsStringType strType, SkScalar conicWeight = -1) {
reed@google.com51bbe752013-01-17 22:07:50 +00001900 str->append(label);
1901 str->append("(");
skia.committer@gmail.com15dd3002013-01-18 07:07:28 +00001902
reed@google.com51bbe752013-01-17 22:07:50 +00001903 const SkScalar* values = &pts[0].fX;
1904 count *= 2;
1905
1906 for (int i = 0; i < count; ++i) {
reede05fed02014-12-15 07:59:53 -08001907 SkAppendScalar(str, values[i], strType);
reed@google.com51bbe752013-01-17 22:07:50 +00001908 if (i < count - 1) {
1909 str->append(", ");
1910 }
1911 }
reed@google.com277c3f82013-05-31 15:17:50 +00001912 if (conicWeight >= 0) {
1913 str->append(", ");
reede05fed02014-12-15 07:59:53 -08001914 SkAppendScalar(str, conicWeight, strType);
reed@google.com277c3f82013-05-31 15:17:50 +00001915 }
caryclark08fa28c2014-10-23 13:08:56 -07001916 str->append(");");
reede05fed02014-12-15 07:59:53 -08001917 if (kHex_SkScalarAsStringType == strType) {
caryclark08fa28c2014-10-23 13:08:56 -07001918 str->append(" // ");
1919 for (int i = 0; i < count; ++i) {
reede05fed02014-12-15 07:59:53 -08001920 SkAppendScalarDec(str, values[i]);
caryclark08fa28c2014-10-23 13:08:56 -07001921 if (i < count - 1) {
1922 str->append(", ");
1923 }
1924 }
1925 if (conicWeight >= 0) {
1926 str->append(", ");
reede05fed02014-12-15 07:59:53 -08001927 SkAppendScalarDec(str, conicWeight);
caryclark08fa28c2014-10-23 13:08:56 -07001928 }
1929 }
1930 str->append("\n");
reed@google.com51bbe752013-01-17 22:07:50 +00001931}
1932
caryclarke9562592014-09-15 09:26:09 -07001933void SkPath::dump(SkWStream* wStream, bool forceClose, bool dumpAsHex) const {
reede05fed02014-12-15 07:59:53 -08001934 SkScalarAsStringType asType = dumpAsHex ? kHex_SkScalarAsStringType : kDec_SkScalarAsStringType;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001935 Iter iter(*this, forceClose);
1936 SkPoint pts[4];
1937 Verb verb;
1938
caryclark66a5d8b2014-06-24 08:30:15 -07001939 if (!wStream) {
1940 SkDebugf("path: forceClose=%s\n", forceClose ? "true" : "false");
1941 }
reed@google.com51bbe752013-01-17 22:07:50 +00001942 SkString builder;
1943
reed@google.com4a3b7142012-05-16 17:16:46 +00001944 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001945 switch (verb) {
1946 case kMove_Verb:
reede05fed02014-12-15 07:59:53 -08001947 append_params(&builder, "path.moveTo", &pts[0], 1, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001948 break;
1949 case kLine_Verb:
reede05fed02014-12-15 07:59:53 -08001950 append_params(&builder, "path.lineTo", &pts[1], 1, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001951 break;
1952 case kQuad_Verb:
reede05fed02014-12-15 07:59:53 -08001953 append_params(&builder, "path.quadTo", &pts[1], 2, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001954 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001955 case kConic_Verb:
reede05fed02014-12-15 07:59:53 -08001956 append_params(&builder, "path.conicTo", &pts[1], 2, asType, iter.conicWeight());
reed@google.com277c3f82013-05-31 15:17:50 +00001957 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001958 case kCubic_Verb:
reede05fed02014-12-15 07:59:53 -08001959 append_params(&builder, "path.cubicTo", &pts[1], 3, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001960 break;
1961 case kClose_Verb:
caryclark66a5d8b2014-06-24 08:30:15 -07001962 builder.append("path.close();\n");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001963 break;
1964 default:
1965 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
1966 verb = kDone_Verb; // stop the loop
1967 break;
1968 }
caryclark1049f122015-04-20 08:31:59 -07001969 if (!wStream && builder.size()) {
1970 SkDebugf("%s", builder.c_str());
1971 builder.reset();
1972 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001973 }
caryclark66a5d8b2014-06-24 08:30:15 -07001974 if (wStream) {
1975 wStream->writeText(builder.c_str());
caryclark66a5d8b2014-06-24 08:30:15 -07001976 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001977}
1978
reed@android.come522ca52009-11-23 20:10:41 +00001979void SkPath::dump() const {
caryclarke9562592014-09-15 09:26:09 -07001980 this->dump(NULL, false, false);
1981}
1982
1983void SkPath::dumpHex() const {
1984 this->dump(NULL, false, true);
reed@android.come522ca52009-11-23 20:10:41 +00001985}
1986
1987#ifdef SK_DEBUG
1988void SkPath::validate() const {
1989 SkASSERT(this != NULL);
1990 SkASSERT((fFillType & ~3) == 0);
reed@google.comabf15c12011-01-18 20:35:51 +00001991
djsollen@google.com077348c2012-10-22 20:23:32 +00001992#ifdef SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00001993 if (!fBoundsIsDirty) {
1994 SkRect bounds;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00001995
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001996 bool isFinite = compute_pt_bounds(&bounds, *fPathRef.get());
robertphillips@google.com5d8d1862012-08-15 14:36:41 +00001997 SkASSERT(SkToBool(fIsFinite) == isFinite);
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00001998
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001999 if (fPathRef->countPoints() <= 1) {
reed@android.come522ca52009-11-23 20:10:41 +00002000 // if we're empty, fBounds may be empty but translated, so we can't
2001 // necessarily compare to bounds directly
2002 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
2003 // be [2, 2, 2, 2]
2004 SkASSERT(bounds.isEmpty());
2005 SkASSERT(fBounds.isEmpty());
2006 } else {
reed@google.comeac52bd2011-11-14 18:13:59 +00002007 if (bounds.isEmpty()) {
2008 SkASSERT(fBounds.isEmpty());
2009 } else {
reed@google.com3563c9e2011-11-14 19:34:57 +00002010 if (!fBounds.isEmpty()) {
2011 SkASSERT(fBounds.contains(bounds));
2012 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002013 }
reed@android.come522ca52009-11-23 20:10:41 +00002014 }
2015 }
djsollen@google.com077348c2012-10-22 20:23:32 +00002016#endif // SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002017}
djsollen@google.com077348c2012-10-22 20:23:32 +00002018#endif // SK_DEBUG
reed@android.come522ca52009-11-23 20:10:41 +00002019
reed@google.com04863fa2011-05-15 04:08:24 +00002020///////////////////////////////////////////////////////////////////////////////
2021
reed@google.com0b7b9822011-05-16 12:29:27 +00002022static int sign(SkScalar x) { return x < 0; }
2023#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00002024
robertphillipsc506e302014-09-16 09:43:31 -07002025enum DirChange {
2026 kLeft_DirChange,
2027 kRight_DirChange,
2028 kStraight_DirChange,
2029 kBackwards_DirChange,
2030
2031 kInvalid_DirChange
2032};
2033
2034
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002035static bool almost_equal(SkScalar compA, SkScalar compB) {
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002036 // The error epsilon was empirically derived; worse case round rects
2037 // with a mid point outset by 2x float epsilon in tests had an error
2038 // of 12.
2039 const int epsilon = 16;
2040 if (!SkScalarIsFinite(compA) || !SkScalarIsFinite(compB)) {
2041 return false;
2042 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002043 // no need to check for small numbers because SkPath::Iter has removed degenerate values
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002044 int aBits = SkFloatAs2sCompliment(compA);
2045 int bBits = SkFloatAs2sCompliment(compB);
2046 return aBits < bBits + epsilon && bBits < aBits + epsilon;
reed@google.com04863fa2011-05-15 04:08:24 +00002047}
2048
caryclarkb4216502015-03-02 13:02:34 -08002049static bool approximately_zero_when_compared_to(double x, double y) {
2050 return x == 0 || fabs(x) < fabs(y * FLT_EPSILON);
robertphillipsc506e302014-09-16 09:43:31 -07002051}
2052
caryclarkb4216502015-03-02 13:02:34 -08002053
reed@google.com04863fa2011-05-15 04:08:24 +00002054// only valid for a single contour
2055struct Convexicator {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002056 Convexicator()
2057 : fPtCount(0)
2058 , fConvexity(SkPath::kConvex_Convexity)
caryclarkd3d1a982014-12-08 04:57:38 -08002059 , fDirection(SkPath::kUnknown_Direction)
caryclark5ccef572015-03-02 10:07:56 -08002060 , fIsFinite(true)
2061 , fIsCurve(false) {
robertphillipsc506e302014-09-16 09:43:31 -07002062 fExpectedDir = kInvalid_DirChange;
reed@google.com04863fa2011-05-15 04:08:24 +00002063 // warnings
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002064 fLastPt.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002065 fCurrPt.set(0, 0);
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002066 fLastVec.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002067 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00002068
2069 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00002070 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00002071 }
2072
2073 SkPath::Convexity getConvexity() const { return fConvexity; }
2074
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002075 /** The direction returned is only valid if the path is determined convex */
2076 SkPath::Direction getDirection() const { return fDirection; }
2077
reed@google.com04863fa2011-05-15 04:08:24 +00002078 void addPt(const SkPoint& pt) {
caryclarkd3d1a982014-12-08 04:57:38 -08002079 if (SkPath::kConcave_Convexity == fConvexity || !fIsFinite) {
reed@google.com04863fa2011-05-15 04:08:24 +00002080 return;
2081 }
2082
2083 if (0 == fPtCount) {
2084 fCurrPt = pt;
2085 ++fPtCount;
2086 } else {
2087 SkVector vec = pt - fCurrPt;
caryclarkd3d1a982014-12-08 04:57:38 -08002088 SkScalar lengthSqd = vec.lengthSqd();
2089 if (!SkScalarIsFinite(lengthSqd)) {
2090 fIsFinite = false;
2091 } else if (!SkScalarNearlyZero(lengthSqd, SK_ScalarNearlyZero*SK_ScalarNearlyZero)) {
caryclarkb4216502015-03-02 13:02:34 -08002092 fPriorPt = fLastPt;
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002093 fLastPt = fCurrPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002094 fCurrPt = pt;
2095 if (++fPtCount == 2) {
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002096 fFirstVec = fLastVec = vec;
reed@google.com04863fa2011-05-15 04:08:24 +00002097 } else {
2098 SkASSERT(fPtCount > 2);
2099 this->addVec(vec);
2100 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002101
reed@google.com85b6e392011-05-15 20:25:17 +00002102 int sx = sign(vec.fX);
2103 int sy = sign(vec.fY);
2104 fDx += (sx != fSx);
2105 fDy += (sy != fSy);
2106 fSx = sx;
2107 fSy = sy;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002108
reed@google.com85b6e392011-05-15 20:25:17 +00002109 if (fDx > 3 || fDy > 3) {
2110 fConvexity = SkPath::kConcave_Convexity;
2111 }
reed@google.com04863fa2011-05-15 04:08:24 +00002112 }
2113 }
2114 }
2115
2116 void close() {
2117 if (fPtCount > 2) {
2118 this->addVec(fFirstVec);
2119 }
2120 }
2121
caryclarkb4216502015-03-02 13:02:34 -08002122 DirChange directionChange(const SkVector& curVec) {
2123 SkScalar cross = SkPoint::CrossProduct(fLastVec, curVec);
2124
2125 SkScalar smallest = SkTMin(fCurrPt.fX, SkTMin(fCurrPt.fY, SkTMin(fLastPt.fX, fLastPt.fY)));
2126 SkScalar largest = SkTMax(fCurrPt.fX, SkTMax(fCurrPt.fY, SkTMax(fLastPt.fX, fLastPt.fY)));
2127 largest = SkTMax(largest, -smallest);
2128
2129 if (!almost_equal(largest, largest + cross)) {
2130 int sign = SkScalarSignAsInt(cross);
2131 if (sign) {
2132 return (1 == sign) ? kRight_DirChange : kLeft_DirChange;
2133 }
2134 }
2135
2136 if (cross) {
2137 double dLastVecX = SkScalarToDouble(fLastPt.fX) - SkScalarToDouble(fPriorPt.fX);
2138 double dLastVecY = SkScalarToDouble(fLastPt.fY) - SkScalarToDouble(fPriorPt.fY);
2139 double dCurrVecX = SkScalarToDouble(fCurrPt.fX) - SkScalarToDouble(fLastPt.fX);
2140 double dCurrVecY = SkScalarToDouble(fCurrPt.fY) - SkScalarToDouble(fLastPt.fY);
2141 double dCross = dLastVecX * dCurrVecY - dLastVecY * dCurrVecX;
2142 if (!approximately_zero_when_compared_to(dCross, SkScalarToDouble(largest))) {
2143 int sign = SkScalarSignAsInt(SkDoubleToScalar(dCross));
2144 if (sign) {
2145 return (1 == sign) ? kRight_DirChange : kLeft_DirChange;
2146 }
2147 }
2148 }
2149
2150 if (!SkScalarNearlyZero(fLastVec.lengthSqd(), SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
2151 !SkScalarNearlyZero(curVec.lengthSqd(), SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
2152 fLastVec.dot(curVec) < 0.0f) {
2153 return kBackwards_DirChange;
2154 }
2155
2156 return kStraight_DirChange;
2157 }
2158
2159
caryclarkd3d1a982014-12-08 04:57:38 -08002160 bool isFinite() const {
2161 return fIsFinite;
2162 }
2163
caryclark5ccef572015-03-02 10:07:56 -08002164 void setCurve(bool isCurve) {
2165 fIsCurve = isCurve;
2166 }
2167
reed@google.com04863fa2011-05-15 04:08:24 +00002168private:
2169 void addVec(const SkVector& vec) {
2170 SkASSERT(vec.fX || vec.fY);
caryclarkb4216502015-03-02 13:02:34 -08002171 DirChange dir = this->directionChange(vec);
robertphillipsc506e302014-09-16 09:43:31 -07002172 switch (dir) {
2173 case kLeft_DirChange: // fall through
2174 case kRight_DirChange:
2175 if (kInvalid_DirChange == fExpectedDir) {
2176 fExpectedDir = dir;
2177 fDirection = (kRight_DirChange == dir) ? SkPath::kCW_Direction
2178 : SkPath::kCCW_Direction;
2179 } else if (dir != fExpectedDir) {
2180 fConvexity = SkPath::kConcave_Convexity;
2181 fDirection = SkPath::kUnknown_Direction;
2182 }
2183 fLastVec = vec;
2184 break;
2185 case kStraight_DirChange:
2186 break;
2187 case kBackwards_DirChange:
caryclark5ccef572015-03-02 10:07:56 -08002188 if (fIsCurve) {
2189 fConvexity = SkPath::kConcave_Convexity;
2190 fDirection = SkPath::kUnknown_Direction;
2191 }
robertphillipsc506e302014-09-16 09:43:31 -07002192 fLastVec = vec;
2193 break;
2194 case kInvalid_DirChange:
2195 SkFAIL("Use of invalid direction change flag");
2196 break;
reed@google.com04863fa2011-05-15 04:08:24 +00002197 }
2198 }
2199
caryclarkb4216502015-03-02 13:02:34 -08002200 SkPoint fPriorPt;
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002201 SkPoint fLastPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002202 SkPoint fCurrPt;
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002203 // fLastVec does not necessarily start at fLastPt. We only advance it when the cross product
2204 // value with the current vec is deemed to be of a significant value.
2205 SkVector fLastVec, fFirstVec;
reed@google.com04863fa2011-05-15 04:08:24 +00002206 int fPtCount; // non-degenerate points
robertphillipsc506e302014-09-16 09:43:31 -07002207 DirChange fExpectedDir;
reed@google.com04863fa2011-05-15 04:08:24 +00002208 SkPath::Convexity fConvexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002209 SkPath::Direction fDirection;
reed@google.com0b7b9822011-05-16 12:29:27 +00002210 int fDx, fDy, fSx, fSy;
caryclarkd3d1a982014-12-08 04:57:38 -08002211 bool fIsFinite;
caryclark5ccef572015-03-02 10:07:56 -08002212 bool fIsCurve;
reed@google.com04863fa2011-05-15 04:08:24 +00002213};
2214
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002215SkPath::Convexity SkPath::internalGetConvexity() const {
2216 SkASSERT(kUnknown_Convexity == fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002217 SkPoint pts[4];
2218 SkPath::Verb verb;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002219 SkPath::Iter iter(*this, true);
reed@google.com04863fa2011-05-15 04:08:24 +00002220
2221 int contourCount = 0;
2222 int count;
2223 Convexicator state;
2224
caryclarkd3d1a982014-12-08 04:57:38 -08002225 if (!isFinite()) {
2226 return kUnknown_Convexity;
2227 }
reed@google.com04863fa2011-05-15 04:08:24 +00002228 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
2229 switch (verb) {
2230 case kMove_Verb:
2231 if (++contourCount > 1) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002232 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002233 return kConcave_Convexity;
2234 }
2235 pts[1] = pts[0];
caryclark5ccef572015-03-02 10:07:56 -08002236 // fall through
2237 case kLine_Verb:
reed@google.com04863fa2011-05-15 04:08:24 +00002238 count = 1;
caryclark5ccef572015-03-02 10:07:56 -08002239 state.setCurve(false);
reed@google.com04863fa2011-05-15 04:08:24 +00002240 break;
caryclark5ccef572015-03-02 10:07:56 -08002241 case kQuad_Verb:
2242 // fall through
2243 case kConic_Verb:
2244 // fall through
2245 case kCubic_Verb:
2246 count = 2 + (kCubic_Verb == verb);
2247 // As an additional enhancement, this could set curve true only
2248 // if the curve is nonlinear
2249 state.setCurve(true);
2250 break;
reed@google.com04863fa2011-05-15 04:08:24 +00002251 case kClose_Verb:
caryclark5ccef572015-03-02 10:07:56 -08002252 state.setCurve(false);
reed@google.com04863fa2011-05-15 04:08:24 +00002253 state.close();
2254 count = 0;
2255 break;
2256 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00002257 SkDEBUGFAIL("bad verb");
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002258 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002259 return kConcave_Convexity;
2260 }
2261
2262 for (int i = 1; i <= count; i++) {
2263 state.addPt(pts[i]);
2264 }
2265 // early exit
caryclarkd3d1a982014-12-08 04:57:38 -08002266 if (!state.isFinite()) {
2267 return kUnknown_Convexity;
2268 }
reed@google.com04863fa2011-05-15 04:08:24 +00002269 if (kConcave_Convexity == state.getConvexity()) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002270 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002271 return kConcave_Convexity;
2272 }
2273 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002274 fConvexity = state.getConvexity();
2275 if (kConvex_Convexity == fConvexity && kUnknown_Direction == fDirection) {
2276 fDirection = state.getDirection();
2277 }
2278 return static_cast<Convexity>(fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002279}
reed@google.com69a99432012-01-10 18:00:10 +00002280
2281///////////////////////////////////////////////////////////////////////////////
2282
2283class ContourIter {
2284public:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002285 ContourIter(const SkPathRef& pathRef);
reed@google.com69a99432012-01-10 18:00:10 +00002286
2287 bool done() const { return fDone; }
2288 // if !done() then these may be called
2289 int count() const { return fCurrPtCount; }
2290 const SkPoint* pts() const { return fCurrPt; }
2291 void next();
2292
2293private:
2294 int fCurrPtCount;
2295 const SkPoint* fCurrPt;
2296 const uint8_t* fCurrVerb;
2297 const uint8_t* fStopVerbs;
reed@google.com277c3f82013-05-31 15:17:50 +00002298 const SkScalar* fCurrConicWeight;
reed@google.com69a99432012-01-10 18:00:10 +00002299 bool fDone;
reed@google.comd1ab9322012-01-10 18:40:03 +00002300 SkDEBUGCODE(int fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002301};
2302
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002303ContourIter::ContourIter(const SkPathRef& pathRef) {
2304 fStopVerbs = pathRef.verbsMemBegin();
reed@google.com69a99432012-01-10 18:00:10 +00002305 fDone = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002306 fCurrPt = pathRef.points();
2307 fCurrVerb = pathRef.verbs();
reed@google.com277c3f82013-05-31 15:17:50 +00002308 fCurrConicWeight = pathRef.conicWeights();
reed@google.com69a99432012-01-10 18:00:10 +00002309 fCurrPtCount = 0;
reed@google.comd1ab9322012-01-10 18:40:03 +00002310 SkDEBUGCODE(fContourCounter = 0;)
reed@google.com69a99432012-01-10 18:00:10 +00002311 this->next();
2312}
2313
2314void ContourIter::next() {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002315 if (fCurrVerb <= fStopVerbs) {
reed@google.com69a99432012-01-10 18:00:10 +00002316 fDone = true;
2317 }
2318 if (fDone) {
2319 return;
2320 }
2321
2322 // skip pts of prev contour
2323 fCurrPt += fCurrPtCount;
2324
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002325 SkASSERT(SkPath::kMove_Verb == fCurrVerb[~0]);
reed@google.com69a99432012-01-10 18:00:10 +00002326 int ptCount = 1; // moveTo
2327 const uint8_t* verbs = fCurrVerb;
2328
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002329 for (--verbs; verbs > fStopVerbs; --verbs) {
2330 switch (verbs[~0]) {
reed@google.com69a99432012-01-10 18:00:10 +00002331 case SkPath::kMove_Verb:
reed@google.com69a99432012-01-10 18:00:10 +00002332 goto CONTOUR_END;
2333 case SkPath::kLine_Verb:
2334 ptCount += 1;
2335 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002336 case SkPath::kConic_Verb:
2337 fCurrConicWeight += 1;
2338 // fall-through
reed@google.com69a99432012-01-10 18:00:10 +00002339 case SkPath::kQuad_Verb:
2340 ptCount += 2;
2341 break;
2342 case SkPath::kCubic_Verb:
2343 ptCount += 3;
2344 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002345 case SkPath::kClose_Verb:
2346 break;
2347 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00002348 SkDEBUGFAIL("unexpected verb");
reed@google.com69a99432012-01-10 18:00:10 +00002349 break;
2350 }
2351 }
2352CONTOUR_END:
2353 fCurrPtCount = ptCount;
2354 fCurrVerb = verbs;
reed@google.comd1ab9322012-01-10 18:40:03 +00002355 SkDEBUGCODE(++fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002356}
2357
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002358// returns cross product of (p1 - p0) and (p2 - p0)
reed@google.com69a99432012-01-10 18:00:10 +00002359static SkScalar cross_prod(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002360 SkScalar cross = SkPoint::CrossProduct(p1 - p0, p2 - p0);
2361 // We may get 0 when the above subtracts underflow. We expect this to be
2362 // very rare and lazily promote to double.
2363 if (0 == cross) {
2364 double p0x = SkScalarToDouble(p0.fX);
2365 double p0y = SkScalarToDouble(p0.fY);
2366
2367 double p1x = SkScalarToDouble(p1.fX);
2368 double p1y = SkScalarToDouble(p1.fY);
2369
2370 double p2x = SkScalarToDouble(p2.fX);
2371 double p2y = SkScalarToDouble(p2.fY);
2372
2373 cross = SkDoubleToScalar((p1x - p0x) * (p2y - p0y) -
2374 (p1y - p0y) * (p2x - p0x));
2375
2376 }
2377 return cross;
reed@google.com69a99432012-01-10 18:00:10 +00002378}
2379
reed@google.comc1ea60a2012-01-31 15:15:36 +00002380// Returns the first pt with the maximum Y coordinate
reed@google.com69a99432012-01-10 18:00:10 +00002381static int find_max_y(const SkPoint pts[], int count) {
2382 SkASSERT(count > 0);
2383 SkScalar max = pts[0].fY;
reed@google.comc1ea60a2012-01-31 15:15:36 +00002384 int firstIndex = 0;
reed@google.com69a99432012-01-10 18:00:10 +00002385 for (int i = 1; i < count; ++i) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002386 SkScalar y = pts[i].fY;
2387 if (y > max) {
2388 max = y;
2389 firstIndex = i;
reed@google.com69a99432012-01-10 18:00:10 +00002390 }
2391 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002392 return firstIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002393}
2394
reed@google.comcabaf1d2012-01-11 21:03:05 +00002395static int find_diff_pt(const SkPoint pts[], int index, int n, int inc) {
2396 int i = index;
2397 for (;;) {
2398 i = (i + inc) % n;
2399 if (i == index) { // we wrapped around, so abort
2400 break;
2401 }
2402 if (pts[index] != pts[i]) { // found a different point, success!
2403 break;
2404 }
2405 }
2406 return i;
2407}
2408
reed@google.comc1ea60a2012-01-31 15:15:36 +00002409/**
2410 * Starting at index, and moving forward (incrementing), find the xmin and
2411 * xmax of the contiguous points that have the same Y.
2412 */
2413static int find_min_max_x_at_y(const SkPoint pts[], int index, int n,
2414 int* maxIndexPtr) {
2415 const SkScalar y = pts[index].fY;
2416 SkScalar min = pts[index].fX;
2417 SkScalar max = min;
2418 int minIndex = index;
2419 int maxIndex = index;
2420 for (int i = index + 1; i < n; ++i) {
2421 if (pts[i].fY != y) {
2422 break;
2423 }
2424 SkScalar x = pts[i].fX;
2425 if (x < min) {
2426 min = x;
2427 minIndex = i;
2428 } else if (x > max) {
2429 max = x;
2430 maxIndex = i;
2431 }
2432 }
2433 *maxIndexPtr = maxIndex;
2434 return minIndex;
2435}
2436
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002437static void crossToDir(SkScalar cross, SkPath::Direction* dir) {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002438 *dir = cross > 0 ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002439}
2440
reed@google.comac8543f2012-01-30 20:51:25 +00002441/*
2442 * We loop through all contours, and keep the computed cross-product of the
2443 * contour that contained the global y-max. If we just look at the first
2444 * contour, we may find one that is wound the opposite way (correctly) since
2445 * it is the interior of a hole (e.g. 'o'). Thus we must find the contour
2446 * that is outer most (or at least has the global y-max) before we can consider
2447 * its cross product.
2448 */
reed@google.com69a99432012-01-10 18:00:10 +00002449bool SkPath::cheapComputeDirection(Direction* dir) const {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002450 if (kUnknown_Direction != fDirection) {
2451 *dir = static_cast<Direction>(fDirection);
2452 return true;
2453 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002454
2455 // don't want to pay the cost for computing this if it
2456 // is unknown, so we don't call isConvex()
2457 if (kConvex_Convexity == this->getConvexityOrUnknown()) {
2458 SkASSERT(kUnknown_Direction == fDirection);
2459 *dir = static_cast<Direction>(fDirection);
2460 return false;
2461 }
reed@google.com69a99432012-01-10 18:00:10 +00002462
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002463 ContourIter iter(*fPathRef.get());
reed@google.com69a99432012-01-10 18:00:10 +00002464
reed@google.comac8543f2012-01-30 20:51:25 +00002465 // initialize with our logical y-min
2466 SkScalar ymax = this->getBounds().fTop;
2467 SkScalar ymaxCross = 0;
2468
reed@google.com69a99432012-01-10 18:00:10 +00002469 for (; !iter.done(); iter.next()) {
2470 int n = iter.count();
reed@google.comcabaf1d2012-01-11 21:03:05 +00002471 if (n < 3) {
2472 continue;
2473 }
djsollen@google.come63793a2012-03-21 15:39:03 +00002474
reed@google.comcabaf1d2012-01-11 21:03:05 +00002475 const SkPoint* pts = iter.pts();
reed@google.com69a99432012-01-10 18:00:10 +00002476 SkScalar cross = 0;
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002477 int index = find_max_y(pts, n);
2478 if (pts[index].fY < ymax) {
2479 continue;
2480 }
2481
2482 // If there is more than 1 distinct point at the y-max, we take the
2483 // x-min and x-max of them and just subtract to compute the dir.
2484 if (pts[(index + 1) % n].fY == pts[index].fY) {
2485 int maxIndex;
2486 int minIndex = find_min_max_x_at_y(pts, index, n, &maxIndex);
2487 if (minIndex == maxIndex) {
2488 goto TRY_CROSSPROD;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002489 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002490 SkASSERT(pts[minIndex].fY == pts[index].fY);
2491 SkASSERT(pts[maxIndex].fY == pts[index].fY);
2492 SkASSERT(pts[minIndex].fX <= pts[maxIndex].fX);
2493 // we just subtract the indices, and let that auto-convert to
2494 // SkScalar, since we just want - or + to signal the direction.
2495 cross = minIndex - maxIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002496 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002497 TRY_CROSSPROD:
2498 // Find a next and prev index to use for the cross-product test,
2499 // but we try to find pts that form non-zero vectors from pts[index]
2500 //
2501 // Its possible that we can't find two non-degenerate vectors, so
2502 // we have to guard our search (e.g. all the pts could be in the
2503 // same place).
2504
2505 // we pass n - 1 instead of -1 so we don't foul up % operator by
2506 // passing it a negative LH argument.
2507 int prev = find_diff_pt(pts, index, n, n - 1);
2508 if (prev == index) {
2509 // completely degenerate, skip to next contour
reed@google.comac8543f2012-01-30 20:51:25 +00002510 continue;
2511 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002512 int next = find_diff_pt(pts, index, n, 1);
2513 SkASSERT(next != index);
2514 cross = cross_prod(pts[prev], pts[index], pts[next]);
2515 // if we get a zero and the points are horizontal, then we look at the spread in
2516 // x-direction. We really should continue to walk away from the degeneracy until
2517 // there is a divergence.
2518 if (0 == cross && pts[prev].fY == pts[index].fY && pts[next].fY == pts[index].fY) {
2519 // construct the subtract so we get the correct Direction below
2520 cross = pts[index].fX - pts[next].fX;
reed@google.com188bfcf2012-01-17 18:26:38 +00002521 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002522 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002523
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002524 if (cross) {
2525 // record our best guess so far
2526 ymax = pts[index].fY;
2527 ymaxCross = cross;
reed@google.com69a99432012-01-10 18:00:10 +00002528 }
2529 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002530 if (ymaxCross) {
2531 crossToDir(ymaxCross, dir);
2532 fDirection = *dir;
2533 return true;
2534 } else {
2535 return false;
2536 }
reed@google.comac8543f2012-01-30 20:51:25 +00002537}
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002538
2539///////////////////////////////////////////////////////////////////////////////
2540
2541static SkScalar eval_cubic_coeff(SkScalar A, SkScalar B, SkScalar C,
2542 SkScalar D, SkScalar t) {
2543 return SkScalarMulAdd(SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C), t, D);
2544}
2545
2546static SkScalar eval_cubic_pts(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2547 SkScalar t) {
2548 SkScalar A = c3 + 3*(c1 - c2) - c0;
2549 SkScalar B = 3*(c2 - c1 - c1 + c0);
2550 SkScalar C = 3*(c1 - c0);
2551 SkScalar D = c0;
2552 return eval_cubic_coeff(A, B, C, D, t);
2553}
2554
2555/* Given 4 cubic points (either Xs or Ys), and a target X or Y, compute the
2556 t value such that cubic(t) = target
2557 */
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002558static void chopMonoCubicAt(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002559 SkScalar target, SkScalar* t) {
2560 // SkASSERT(c0 <= c1 && c1 <= c2 && c2 <= c3);
2561 SkASSERT(c0 < target && target < c3);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002562
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002563 SkScalar D = c0 - target;
2564 SkScalar A = c3 + 3*(c1 - c2) - c0;
2565 SkScalar B = 3*(c2 - c1 - c1 + c0);
2566 SkScalar C = 3*(c1 - c0);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002567
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002568 const SkScalar TOLERANCE = SK_Scalar1 / 4096;
2569 SkScalar minT = 0;
2570 SkScalar maxT = SK_Scalar1;
2571 SkScalar mid;
2572 int i;
2573 for (i = 0; i < 16; i++) {
2574 mid = SkScalarAve(minT, maxT);
2575 SkScalar delta = eval_cubic_coeff(A, B, C, D, mid);
2576 if (delta < 0) {
2577 minT = mid;
2578 delta = -delta;
2579 } else {
2580 maxT = mid;
2581 }
2582 if (delta < TOLERANCE) {
2583 break;
2584 }
2585 }
2586 *t = mid;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002587}
2588
2589template <size_t N> static void find_minmax(const SkPoint pts[],
2590 SkScalar* minPtr, SkScalar* maxPtr) {
2591 SkScalar min, max;
2592 min = max = pts[0].fX;
2593 for (size_t i = 1; i < N; ++i) {
2594 min = SkMinScalar(min, pts[i].fX);
2595 max = SkMaxScalar(max, pts[i].fX);
2596 }
2597 *minPtr = min;
2598 *maxPtr = max;
2599}
2600
2601static int winding_mono_cubic(const SkPoint pts[], SkScalar x, SkScalar y) {
2602 SkPoint storage[4];
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002603
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002604 int dir = 1;
2605 if (pts[0].fY > pts[3].fY) {
2606 storage[0] = pts[3];
2607 storage[1] = pts[2];
2608 storage[2] = pts[1];
2609 storage[3] = pts[0];
2610 pts = storage;
2611 dir = -1;
2612 }
2613 if (y < pts[0].fY || y >= pts[3].fY) {
2614 return 0;
2615 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002616
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002617 // quickreject or quickaccept
2618 SkScalar min, max;
2619 find_minmax<4>(pts, &min, &max);
2620 if (x < min) {
2621 return 0;
2622 }
2623 if (x > max) {
2624 return dir;
2625 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002626
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002627 // compute the actual x(t) value
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002628 SkScalar t;
2629 chopMonoCubicAt(pts[0].fY, pts[1].fY, pts[2].fY, pts[3].fY, y, &t);
2630 SkScalar xt = eval_cubic_pts(pts[0].fX, pts[1].fX, pts[2].fX, pts[3].fX, t);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002631 return xt < x ? dir : 0;
2632}
2633
2634static int winding_cubic(const SkPoint pts[], SkScalar x, SkScalar y) {
2635 SkPoint dst[10];
2636 int n = SkChopCubicAtYExtrema(pts, dst);
2637 int w = 0;
2638 for (int i = 0; i <= n; ++i) {
2639 w += winding_mono_cubic(&dst[i * 3], x, y);
2640 }
2641 return w;
2642}
2643
2644static int winding_mono_quad(const SkPoint pts[], SkScalar x, SkScalar y) {
2645 SkScalar y0 = pts[0].fY;
2646 SkScalar y2 = pts[2].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002647
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002648 int dir = 1;
2649 if (y0 > y2) {
2650 SkTSwap(y0, y2);
2651 dir = -1;
2652 }
2653 if (y < y0 || y >= y2) {
2654 return 0;
2655 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002656
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002657 // bounds check on X (not required. is it faster?)
2658#if 0
2659 if (pts[0].fX > x && pts[1].fX > x && pts[2].fX > x) {
2660 return 0;
2661 }
2662#endif
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002663
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002664 SkScalar roots[2];
2665 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
2666 2 * (pts[1].fY - pts[0].fY),
2667 pts[0].fY - y,
2668 roots);
2669 SkASSERT(n <= 1);
2670 SkScalar xt;
2671 if (0 == n) {
2672 SkScalar mid = SkScalarAve(y0, y2);
2673 // Need [0] and [2] if dir == 1
2674 // and [2] and [0] if dir == -1
2675 xt = y < mid ? pts[1 - dir].fX : pts[dir - 1].fX;
2676 } else {
2677 SkScalar t = roots[0];
2678 SkScalar C = pts[0].fX;
2679 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
2680 SkScalar B = 2 * (pts[1].fX - C);
2681 xt = SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C);
2682 }
2683 return xt < x ? dir : 0;
2684}
2685
2686static bool is_mono_quad(SkScalar y0, SkScalar y1, SkScalar y2) {
2687 // return SkScalarSignAsInt(y0 - y1) + SkScalarSignAsInt(y1 - y2) != 0;
2688 if (y0 == y1) {
2689 return true;
2690 }
2691 if (y0 < y1) {
2692 return y1 <= y2;
2693 } else {
2694 return y1 >= y2;
2695 }
2696}
2697
2698static int winding_quad(const SkPoint pts[], SkScalar x, SkScalar y) {
2699 SkPoint dst[5];
2700 int n = 0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002701
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002702 if (!is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY)) {
2703 n = SkChopQuadAtYExtrema(pts, dst);
2704 pts = dst;
2705 }
2706 int w = winding_mono_quad(pts, x, y);
2707 if (n > 0) {
2708 w += winding_mono_quad(&pts[2], x, y);
2709 }
2710 return w;
2711}
2712
2713static int winding_line(const SkPoint pts[], SkScalar x, SkScalar y) {
2714 SkScalar x0 = pts[0].fX;
2715 SkScalar y0 = pts[0].fY;
2716 SkScalar x1 = pts[1].fX;
2717 SkScalar y1 = pts[1].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002718
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002719 SkScalar dy = y1 - y0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002720
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002721 int dir = 1;
2722 if (y0 > y1) {
2723 SkTSwap(y0, y1);
2724 dir = -1;
2725 }
2726 if (y < y0 || y >= y1) {
2727 return 0;
2728 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002729
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002730 SkScalar cross = SkScalarMul(x1 - x0, y - pts[0].fY) -
2731 SkScalarMul(dy, x - pts[0].fX);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002732
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002733 if (SkScalarSignAsInt(cross) == dir) {
2734 dir = 0;
2735 }
2736 return dir;
2737}
2738
reed@google.com4db592c2013-10-30 17:39:43 +00002739static bool contains_inclusive(const SkRect& r, SkScalar x, SkScalar y) {
2740 return r.fLeft <= x && x <= r.fRight && r.fTop <= y && y <= r.fBottom;
2741}
2742
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002743bool SkPath::contains(SkScalar x, SkScalar y) const {
2744 bool isInverse = this->isInverseFillType();
2745 if (this->isEmpty()) {
2746 return isInverse;
2747 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002748
reed@google.com4db592c2013-10-30 17:39:43 +00002749 if (!contains_inclusive(this->getBounds(), x, y)) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002750 return isInverse;
2751 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002752
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002753 SkPath::Iter iter(*this, true);
2754 bool done = false;
2755 int w = 0;
2756 do {
2757 SkPoint pts[4];
2758 switch (iter.next(pts, false)) {
2759 case SkPath::kMove_Verb:
2760 case SkPath::kClose_Verb:
2761 break;
2762 case SkPath::kLine_Verb:
2763 w += winding_line(pts, x, y);
2764 break;
2765 case SkPath::kQuad_Verb:
2766 w += winding_quad(pts, x, y);
2767 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002768 case SkPath::kConic_Verb:
2769 SkASSERT(0);
2770 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002771 case SkPath::kCubic_Verb:
2772 w += winding_cubic(pts, x, y);
2773 break;
2774 case SkPath::kDone_Verb:
2775 done = true;
2776 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002777 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002778 } while (!done);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002779
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002780 switch (this->getFillType()) {
2781 case SkPath::kEvenOdd_FillType:
2782 case SkPath::kInverseEvenOdd_FillType:
2783 w &= 1;
2784 break;
reed@google.come9bb6232012-07-11 18:56:10 +00002785 default:
2786 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002787 }
2788 return SkToBool(w);
2789}