blob: aa99ce4f0e1da80840eededc3cfb82bc9c97a00d [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
caryclarkaec25102015-04-29 08:28:30 -0700634void SkPath::setPt(int index, SkScalar x, SkScalar y) {
635 SkDEBUGCODE(this->validate();)
636
637 int count = fPathRef->countPoints();
638 if (count <= index) {
639 return;
640 } else {
641 SkPathRef::Editor ed(&fPathRef);
642 ed.atPoint(index)->set(x, y);
643 }
644}
645
reed@android.com8a1c16f2008-12-17 15:59:43 +0000646void SkPath::setLastPt(SkScalar x, SkScalar y) {
647 SkDEBUGCODE(this->validate();)
648
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000649 int count = fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000650 if (count == 0) {
651 this->moveTo(x, y);
652 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000653 SkPathRef::Editor ed(&fPathRef);
654 ed.atPoint(count-1)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000655 }
656}
657
reed@google.com04863fa2011-05-15 04:08:24 +0000658void SkPath::setConvexity(Convexity c) {
659 if (fConvexity != c) {
660 fConvexity = c;
reed@google.com04863fa2011-05-15 04:08:24 +0000661 }
662}
663
reed@android.com8a1c16f2008-12-17 15:59:43 +0000664//////////////////////////////////////////////////////////////////////////////
665// Construction methods
666
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000667#define DIRTY_AFTER_EDIT \
668 do { \
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000669 fConvexity = kUnknown_Convexity; \
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000670 fDirection = kUnknown_Direction; \
reed@google.comb54455e2011-05-16 14:16:04 +0000671 } while (0)
672
reed@android.com8a1c16f2008-12-17 15:59:43 +0000673void SkPath::incReserve(U16CPU inc) {
674 SkDEBUGCODE(this->validate();)
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000675 SkPathRef::Editor(&fPathRef, inc, inc);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000676 SkDEBUGCODE(this->validate();)
677}
678
679void SkPath::moveTo(SkScalar x, SkScalar y) {
680 SkDEBUGCODE(this->validate();)
681
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000682 SkPathRef::Editor ed(&fPathRef);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000683
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000684 // remember our index
685 fLastMoveToIndex = fPathRef->countPoints();
686
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000687 ed.growForVerb(kMove_Verb)->set(x, y);
bsalomonb17c1292014-08-28 14:04:55 -0700688
689 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000690}
691
692void SkPath::rMoveTo(SkScalar x, SkScalar y) {
693 SkPoint pt;
694 this->getLastPt(&pt);
695 this->moveTo(pt.fX + x, pt.fY + y);
696}
697
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000698void SkPath::injectMoveToIfNeeded() {
699 if (fLastMoveToIndex < 0) {
700 SkScalar x, y;
701 if (fPathRef->countVerbs() == 0) {
702 x = y = 0;
703 } else {
704 const SkPoint& pt = fPathRef->atPoint(~fLastMoveToIndex);
705 x = pt.fX;
706 y = pt.fY;
707 }
708 this->moveTo(x, y);
709 }
710}
711
reed@android.com8a1c16f2008-12-17 15:59:43 +0000712void SkPath::lineTo(SkScalar x, SkScalar y) {
713 SkDEBUGCODE(this->validate();)
714
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000715 this->injectMoveToIfNeeded();
716
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000717 SkPathRef::Editor ed(&fPathRef);
718 ed.growForVerb(kLine_Verb)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000719
reed@google.comb54455e2011-05-16 14:16:04 +0000720 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000721}
722
723void SkPath::rLineTo(SkScalar x, SkScalar y) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000724 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000725 SkPoint pt;
726 this->getLastPt(&pt);
727 this->lineTo(pt.fX + x, pt.fY + y);
728}
729
730void SkPath::quadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
731 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000732
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000733 this->injectMoveToIfNeeded();
734
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000735 SkPathRef::Editor ed(&fPathRef);
736 SkPoint* pts = ed.growForVerb(kQuad_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000737 pts[0].set(x1, y1);
738 pts[1].set(x2, y2);
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000739
reed@google.comb54455e2011-05-16 14:16:04 +0000740 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000741}
742
743void SkPath::rQuadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000744 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000745 SkPoint pt;
746 this->getLastPt(&pt);
747 this->quadTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2);
748}
749
reed@google.com277c3f82013-05-31 15:17:50 +0000750void SkPath::conicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
751 SkScalar w) {
752 // check for <= 0 or NaN with this test
753 if (!(w > 0)) {
754 this->lineTo(x2, y2);
755 } else if (!SkScalarIsFinite(w)) {
756 this->lineTo(x1, y1);
757 this->lineTo(x2, y2);
758 } else if (SK_Scalar1 == w) {
759 this->quadTo(x1, y1, x2, y2);
760 } else {
761 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000762
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000763 this->injectMoveToIfNeeded();
764
reed@google.com277c3f82013-05-31 15:17:50 +0000765 SkPathRef::Editor ed(&fPathRef);
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000766 SkPoint* pts = ed.growForVerb(kConic_Verb, w);
reed@google.com277c3f82013-05-31 15:17:50 +0000767 pts[0].set(x1, y1);
768 pts[1].set(x2, y2);
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000769
reed@google.com277c3f82013-05-31 15:17:50 +0000770 DIRTY_AFTER_EDIT;
771 }
772}
773
774void SkPath::rConicTo(SkScalar dx1, SkScalar dy1, SkScalar dx2, SkScalar dy2,
775 SkScalar w) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000776 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@google.com277c3f82013-05-31 15:17:50 +0000777 SkPoint pt;
778 this->getLastPt(&pt);
779 this->conicTo(pt.fX + dx1, pt.fY + dy1, pt.fX + dx2, pt.fY + dy2, w);
780}
781
reed@android.com8a1c16f2008-12-17 15:59:43 +0000782void SkPath::cubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
783 SkScalar x3, SkScalar y3) {
784 SkDEBUGCODE(this->validate();)
785
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000786 this->injectMoveToIfNeeded();
787
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000788 SkPathRef::Editor ed(&fPathRef);
789 SkPoint* pts = ed.growForVerb(kCubic_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000790 pts[0].set(x1, y1);
791 pts[1].set(x2, y2);
792 pts[2].set(x3, y3);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000793
reed@google.comb54455e2011-05-16 14:16:04 +0000794 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000795}
796
797void SkPath::rCubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
798 SkScalar x3, SkScalar y3) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000799 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000800 SkPoint pt;
801 this->getLastPt(&pt);
802 this->cubicTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2,
803 pt.fX + x3, pt.fY + y3);
804}
805
806void SkPath::close() {
807 SkDEBUGCODE(this->validate();)
808
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000809 int count = fPathRef->countVerbs();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000810 if (count > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000811 switch (fPathRef->atVerb(count - 1)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000812 case kLine_Verb:
813 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000814 case kConic_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +0000815 case kCubic_Verb:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000816 case kMove_Verb: {
817 SkPathRef::Editor ed(&fPathRef);
818 ed.growForVerb(kClose_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000819 break;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000820 }
reed@google.com277c3f82013-05-31 15:17:50 +0000821 case kClose_Verb:
reed@google.comfa2f2a42013-05-30 15:29:48 +0000822 // don't add a close if it's the first verb or a repeat
reed@google.com7950a9e2013-05-30 14:57:55 +0000823 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000824 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000825 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000826 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000827 }
828 }
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000829
830 // signal that we need a moveTo to follow us (unless we're done)
831#if 0
832 if (fLastMoveToIndex >= 0) {
833 fLastMoveToIndex = ~fLastMoveToIndex;
834 }
835#else
836 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
837#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000838}
839
840///////////////////////////////////////////////////////////////////////////////
reed@google.comabf15c12011-01-18 20:35:51 +0000841
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000842static void assert_known_direction(int dir) {
843 SkASSERT(SkPath::kCW_Direction == dir || SkPath::kCCW_Direction == dir);
844}
845
reed@android.com8a1c16f2008-12-17 15:59:43 +0000846void SkPath::addRect(const SkRect& rect, Direction dir) {
847 this->addRect(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, dir);
848}
849
850void SkPath::addRect(SkScalar left, SkScalar top, SkScalar right,
851 SkScalar bottom, Direction dir) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000852 assert_known_direction(dir);
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000853 fDirection = this->hasOnlyMoveTos() ? dir : kUnknown_Direction;
854 SkAutoDisableDirectionCheck addc(this);
855
reed@android.com8a1c16f2008-12-17 15:59:43 +0000856 SkAutoPathBoundsUpdate apbu(this, left, top, right, bottom);
857
858 this->incReserve(5);
859
860 this->moveTo(left, top);
861 if (dir == kCCW_Direction) {
862 this->lineTo(left, bottom);
863 this->lineTo(right, bottom);
864 this->lineTo(right, top);
865 } else {
866 this->lineTo(right, top);
867 this->lineTo(right, bottom);
868 this->lineTo(left, bottom);
869 }
870 this->close();
871}
872
reed@google.com744faba2012-05-29 19:54:52 +0000873void SkPath::addPoly(const SkPoint pts[], int count, bool close) {
874 SkDEBUGCODE(this->validate();)
875 if (count <= 0) {
876 return;
877 }
878
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000879 fLastMoveToIndex = fPathRef->countPoints();
880
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000881 // +close makes room for the extra kClose_Verb
882 SkPathRef::Editor ed(&fPathRef, count+close, count);
883
884 ed.growForVerb(kMove_Verb)->set(pts[0].fX, pts[0].fY);
reed@google.com744faba2012-05-29 19:54:52 +0000885 if (count > 1) {
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000886 SkPoint* p = ed.growForRepeatedVerb(kLine_Verb, count - 1);
887 memcpy(p, &pts[1], (count-1) * sizeof(SkPoint));
reed@google.com744faba2012-05-29 19:54:52 +0000888 }
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000889
reed@google.com744faba2012-05-29 19:54:52 +0000890 if (close) {
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000891 ed.growForVerb(kClose_Verb);
caryclark63c684a2015-02-25 09:04:04 -0800892 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
reed@google.com744faba2012-05-29 19:54:52 +0000893 }
894
reed@google.com744faba2012-05-29 19:54:52 +0000895 DIRTY_AFTER_EDIT;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000896 SkDEBUGCODE(this->validate();)
reed@google.com744faba2012-05-29 19:54:52 +0000897}
898
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000899#include "SkGeometry.h"
900
reedf90ea012015-01-29 12:03:58 -0800901static bool arc_is_lone_point(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
902 SkPoint* pt) {
903 if (0 == sweepAngle && (0 == startAngle || SkIntToScalar(360) == startAngle)) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000904 // Chrome uses this path to move into and out of ovals. If not
905 // treated as a special case the moves can distort the oval's
906 // bounding box (and break the circle special case).
reedf90ea012015-01-29 12:03:58 -0800907 pt->set(oval.fRight, oval.centerY());
908 return true;
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000909 } else if (0 == oval.width() && 0 == oval.height()) {
910 // Chrome will sometimes create 0 radius round rects. Having degenerate
911 // quad segments in the path prevents the path from being recognized as
912 // a rect.
913 // TODO: optimizing the case where only one of width or height is zero
914 // should also be considered. This case, however, doesn't seem to be
915 // as common as the single point case.
reedf90ea012015-01-29 12:03:58 -0800916 pt->set(oval.fRight, oval.fTop);
917 return true;
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000918 }
reedf90ea012015-01-29 12:03:58 -0800919 return false;
920}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000921
reedd5d27d92015-02-09 13:54:43 -0800922// Return the unit vectors pointing at the start/stop points for the given start/sweep angles
923//
924static void angles_to_unit_vectors(SkScalar startAngle, SkScalar sweepAngle,
925 SkVector* startV, SkVector* stopV, SkRotationDirection* dir) {
926 startV->fY = SkScalarSinCos(SkDegreesToRadians(startAngle), &startV->fX);
927 stopV->fY = SkScalarSinCos(SkDegreesToRadians(startAngle + sweepAngle), &stopV->fX);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000928
929 /* If the sweep angle is nearly (but less than) 360, then due to precision
reedd5d27d92015-02-09 13:54:43 -0800930 loss in radians-conversion and/or sin/cos, we may end up with coincident
931 vectors, which will fool SkBuildQuadArc into doing nothing (bad) instead
932 of drawing a nearly complete circle (good).
933 e.g. canvas.drawArc(0, 359.99, ...)
934 -vs- canvas.drawArc(0, 359.9, ...)
935 We try to detect this edge case, and tweak the stop vector
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000936 */
reedd5d27d92015-02-09 13:54:43 -0800937 if (*startV == *stopV) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000938 SkScalar sw = SkScalarAbs(sweepAngle);
939 if (sw < SkIntToScalar(360) && sw > SkIntToScalar(359)) {
940 SkScalar stopRad = SkDegreesToRadians(startAngle + sweepAngle);
941 // make a guess at a tiny angle (in radians) to tweak by
942 SkScalar deltaRad = SkScalarCopySign(SK_Scalar1/512, sweepAngle);
943 // not sure how much will be enough, so we use a loop
944 do {
945 stopRad -= deltaRad;
reedd5d27d92015-02-09 13:54:43 -0800946 stopV->fY = SkScalarSinCos(stopRad, &stopV->fX);
947 } while (*startV == *stopV);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000948 }
949 }
reedd5d27d92015-02-09 13:54:43 -0800950 *dir = sweepAngle > 0 ? kCW_SkRotationDirection : kCCW_SkRotationDirection;
951}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000952
reed9e779d42015-02-17 11:43:14 -0800953/**
954 * If this returns 0, then the caller should just line-to the singlePt, else it should
955 * ignore singlePt and append the specified number of conics.
956 */
reedd5d27d92015-02-09 13:54:43 -0800957static int build_arc_conics(const SkRect& oval, const SkVector& start, const SkVector& stop,
reed9e779d42015-02-17 11:43:14 -0800958 SkRotationDirection dir, SkConic conics[SkConic::kMaxConicsForArc],
959 SkPoint* singlePt) {
reedd5d27d92015-02-09 13:54:43 -0800960 SkMatrix matrix;
961
962 matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height()));
963 matrix.postTranslate(oval.centerX(), oval.centerY());
964
reed9e779d42015-02-17 11:43:14 -0800965 int count = SkConic::BuildUnitArc(start, stop, dir, &matrix, conics);
966 if (0 == count) {
967 matrix.mapXY(start.x(), start.y(), singlePt);
968 }
969 return count;
reedd5d27d92015-02-09 13:54:43 -0800970}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000971
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +0000972void SkPath::addRoundRect(const SkRect& rect, const SkScalar radii[],
reed@android.com8a1c16f2008-12-17 15:59:43 +0000973 Direction dir) {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +0000974 SkRRect rrect;
975 rrect.setRectRadii(rect, (const SkVector*) radii);
976 this->addRRect(rrect, dir);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000977}
978
reed@google.com4ed0fb72012-12-12 20:48:18 +0000979void SkPath::addRRect(const SkRRect& rrect, Direction dir) {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +0000980 assert_known_direction(dir);
981
982 if (rrect.isEmpty()) {
983 return;
984 }
985
reed@google.com4ed0fb72012-12-12 20:48:18 +0000986 const SkRect& bounds = rrect.getBounds();
987
988 if (rrect.isRect()) {
989 this->addRect(bounds, dir);
990 } else if (rrect.isOval()) {
991 this->addOval(bounds, dir);
reed@google.com4ed0fb72012-12-12 20:48:18 +0000992 } else {
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +0000993 fDirection = this->hasOnlyMoveTos() ? dir : kUnknown_Direction;
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +0000994
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +0000995 SkAutoPathBoundsUpdate apbu(this, bounds);
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +0000996 SkAutoDisableDirectionCheck addc(this);
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +0000997
reed1b28a3a2014-12-17 14:42:09 -0800998 const SkScalar L = bounds.fLeft;
999 const SkScalar T = bounds.fTop;
1000 const SkScalar R = bounds.fRight;
1001 const SkScalar B = bounds.fBottom;
1002 const SkScalar W = SK_ScalarRoot2Over2;
1003
1004 this->incReserve(13);
1005 if (kCW_Direction == dir) {
1006 this->moveTo(L, B - rrect.fRadii[SkRRect::kLowerLeft_Corner].fY);
1007
1008 this->lineTo(L, T + rrect.fRadii[SkRRect::kUpperLeft_Corner].fY);
1009 this->conicTo(L, T, L + rrect.fRadii[SkRRect::kUpperLeft_Corner].fX, T, W);
1010
1011 this->lineTo(R - rrect.fRadii[SkRRect::kUpperRight_Corner].fX, T);
1012 this->conicTo(R, T, R, T + rrect.fRadii[SkRRect::kUpperRight_Corner].fY, W);
1013
1014 this->lineTo(R, B - rrect.fRadii[SkRRect::kLowerRight_Corner].fY);
1015 this->conicTo(R, B, R - rrect.fRadii[SkRRect::kLowerRight_Corner].fX, B, W);
1016
1017 this->lineTo(L + rrect.fRadii[SkRRect::kLowerLeft_Corner].fX, B);
1018 this->conicTo(L, B, L, B - rrect.fRadii[SkRRect::kLowerLeft_Corner].fY, W);
1019 } else {
1020 this->moveTo(L, T + rrect.fRadii[SkRRect::kUpperLeft_Corner].fY);
1021
1022 this->lineTo(L, B - rrect.fRadii[SkRRect::kLowerLeft_Corner].fY);
1023 this->conicTo(L, B, L + rrect.fRadii[SkRRect::kLowerLeft_Corner].fX, B, W);
1024
1025 this->lineTo(R - rrect.fRadii[SkRRect::kLowerRight_Corner].fX, B);
1026 this->conicTo(R, B, R, B - rrect.fRadii[SkRRect::kLowerRight_Corner].fY, W);
1027
1028 this->lineTo(R, T + rrect.fRadii[SkRRect::kUpperRight_Corner].fY);
1029 this->conicTo(R, T, R - rrect.fRadii[SkRRect::kUpperRight_Corner].fX, T, W);
1030
1031 this->lineTo(L + rrect.fRadii[SkRRect::kUpperLeft_Corner].fX, T);
1032 this->conicTo(L, T, L, T + rrect.fRadii[SkRRect::kUpperLeft_Corner].fY, W);
1033 }
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001034 this->close();
reed@google.com4ed0fb72012-12-12 20:48:18 +00001035 }
reed5bcbe912014-12-15 12:28:33 -08001036 SkDEBUGCODE(fPathRef->validate();)
reed@google.com4ed0fb72012-12-12 20:48:18 +00001037}
1038
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001039bool SkPath::hasOnlyMoveTos() const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001040 int count = fPathRef->countVerbs();
1041 const uint8_t* verbs = const_cast<const SkPathRef*>(fPathRef.get())->verbsMemBegin();
1042 for (int i = 0; i < count; ++i) {
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001043 if (*verbs == kLine_Verb ||
1044 *verbs == kQuad_Verb ||
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001045 *verbs == kConic_Verb ||
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001046 *verbs == kCubic_Verb) {
1047 return false;
1048 }
1049 ++verbs;
1050 }
1051 return true;
1052}
1053
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001054void SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
1055 Direction dir) {
1056 assert_known_direction(dir);
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001057
humper@google.com75e3ca12013-04-08 21:44:11 +00001058 if (rx < 0 || ry < 0) {
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001059 SkErrorInternals::SetError( kInvalidArgument_SkError,
humper@google.com75e3ca12013-04-08 21:44:11 +00001060 "I got %f and %f as radii to SkPath::AddRoundRect, "
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001061 "but negative radii are not allowed.",
humper@google.com75e3ca12013-04-08 21:44:11 +00001062 SkScalarToDouble(rx), SkScalarToDouble(ry) );
1063 return;
1064 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001065
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001066 SkRRect rrect;
1067 rrect.setRectXY(rect, rx, ry);
1068 this->addRRect(rrect, dir);
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001069}
1070
reed@android.com8a1c16f2008-12-17 15:59:43 +00001071void SkPath::addOval(const SkRect& oval, Direction dir) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +00001072 assert_known_direction(dir);
1073
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001074 /* If addOval() is called after previous moveTo(),
1075 this path is still marked as an oval. This is used to
1076 fit into WebKit's calling sequences.
1077 We can't simply check isEmpty() in this case, as additional
1078 moveTo() would mark the path non empty.
1079 */
robertphillips@google.com466310d2013-12-03 16:43:54 +00001080 bool isOval = hasOnlyMoveTos();
1081 if (isOval) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001082 fDirection = dir;
1083 } else {
1084 fDirection = kUnknown_Direction;
1085 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001086
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001087 SkAutoDisableDirectionCheck addc(this);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001088
reed@android.com8a1c16f2008-12-17 15:59:43 +00001089 SkAutoPathBoundsUpdate apbu(this, oval);
1090
reed220f9262014-12-17 08:21:04 -08001091 const SkScalar L = oval.fLeft;
1092 const SkScalar T = oval.fTop;
1093 const SkScalar R = oval.fRight;
1094 const SkScalar B = oval.fBottom;
1095 const SkScalar cx = oval.centerX();
1096 const SkScalar cy = oval.centerY();
1097 const SkScalar weight = SK_ScalarRoot2Over2;
1098
1099 this->incReserve(9); // move + 4 conics
1100 this->moveTo(R, cy);
1101 if (dir == kCCW_Direction) {
1102 this->conicTo(R, T, cx, T, weight);
1103 this->conicTo(L, T, L, cy, weight);
1104 this->conicTo(L, B, cx, B, weight);
1105 this->conicTo(R, B, R, cy, weight);
1106 } else {
1107 this->conicTo(R, B, cx, B, weight);
1108 this->conicTo(L, B, L, cy, weight);
1109 this->conicTo(L, T, cx, T, weight);
1110 this->conicTo(R, T, R, cy, weight);
1111 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001112 this->close();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001113
robertphillips@google.com466310d2013-12-03 16:43:54 +00001114 SkPathRef::Editor ed(&fPathRef);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001115
robertphillips@google.com466310d2013-12-03 16:43:54 +00001116 ed.setIsOval(isOval);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001117}
1118
reed@android.com8a1c16f2008-12-17 15:59:43 +00001119void SkPath::addCircle(SkScalar x, SkScalar y, SkScalar r, Direction dir) {
1120 if (r > 0) {
1121 SkRect rect;
1122 rect.set(x - r, y - r, x + r, y + r);
1123 this->addOval(rect, dir);
1124 }
1125}
1126
reed@android.com8a1c16f2008-12-17 15:59:43 +00001127void SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
1128 bool forceMoveTo) {
1129 if (oval.width() < 0 || oval.height() < 0) {
1130 return;
1131 }
1132
reedf90ea012015-01-29 12:03:58 -08001133 if (fPathRef->countVerbs() == 0) {
1134 forceMoveTo = true;
1135 }
1136
1137 SkPoint lonePt;
1138 if (arc_is_lone_point(oval, startAngle, sweepAngle, &lonePt)) {
1139 forceMoveTo ? this->moveTo(lonePt) : this->lineTo(lonePt);
1140 return;
1141 }
1142
reedd5d27d92015-02-09 13:54:43 -08001143 SkVector startV, stopV;
1144 SkRotationDirection dir;
1145 angles_to_unit_vectors(startAngle, sweepAngle, &startV, &stopV, &dir);
1146
reed9e779d42015-02-17 11:43:14 -08001147 SkPoint singlePt;
reedd5d27d92015-02-09 13:54:43 -08001148 SkConic conics[SkConic::kMaxConicsForArc];
reed9e779d42015-02-17 11:43:14 -08001149 int count = build_arc_conics(oval, startV, stopV, dir, conics, &singlePt);
reedd5d27d92015-02-09 13:54:43 -08001150 if (count) {
1151 this->incReserve(count * 2 + 1);
1152 const SkPoint& pt = conics[0].fPts[0];
1153 forceMoveTo ? this->moveTo(pt) : this->lineTo(pt);
1154 for (int i = 0; i < count; ++i) {
1155 this->conicTo(conics[i].fPts[1], conics[i].fPts[2], conics[i].fW);
1156 }
reed9e779d42015-02-17 11:43:14 -08001157 } else {
1158 forceMoveTo ? this->moveTo(singlePt) : this->lineTo(singlePt);
reedd5d27d92015-02-09 13:54:43 -08001159 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001160}
1161
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001162void SkPath::addArc(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001163 if (oval.isEmpty() || 0 == sweepAngle) {
1164 return;
1165 }
1166
1167 const SkScalar kFullCircleAngle = SkIntToScalar(360);
1168
1169 if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
1170 this->addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction);
reedc7789042015-01-29 12:59:11 -08001171 } else {
1172 this->arcTo(oval, startAngle, sweepAngle, true);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001173 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001174}
1175
1176/*
1177 Need to handle the case when the angle is sharp, and our computed end-points
1178 for the arc go behind pt1 and/or p2...
1179*/
reedc7789042015-01-29 12:59:11 -08001180void SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2, SkScalar radius) {
reeda8b326c2014-12-09 11:50:32 -08001181 if (radius == 0) {
1182 this->lineTo(x1, y1);
1183 return;
1184 }
1185
1186 SkVector before, after;
reed@google.comabf15c12011-01-18 20:35:51 +00001187
reed@android.com8a1c16f2008-12-17 15:59:43 +00001188 // need to know our prev pt so we can construct tangent vectors
1189 {
1190 SkPoint start;
1191 this->getLastPt(&start);
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001192 // Handle degenerate cases by adding a line to the first point and
1193 // bailing out.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001194 before.setNormalize(x1 - start.fX, y1 - start.fY);
1195 after.setNormalize(x2 - x1, y2 - y1);
1196 }
reed@google.comabf15c12011-01-18 20:35:51 +00001197
reed@android.com8a1c16f2008-12-17 15:59:43 +00001198 SkScalar cosh = SkPoint::DotProduct(before, after);
1199 SkScalar sinh = SkPoint::CrossProduct(before, after);
1200
1201 if (SkScalarNearlyZero(sinh)) { // angle is too tight
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001202 this->lineTo(x1, y1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001203 return;
1204 }
reed@google.comabf15c12011-01-18 20:35:51 +00001205
reed@android.com8a1c16f2008-12-17 15:59:43 +00001206 SkScalar dist = SkScalarMulDiv(radius, SK_Scalar1 - cosh, sinh);
1207 if (dist < 0) {
1208 dist = -dist;
1209 }
1210
1211 SkScalar xx = x1 - SkScalarMul(dist, before.fX);
1212 SkScalar yy = y1 - SkScalarMul(dist, before.fY);
1213 SkRotationDirection arcDir;
1214
1215 // now turn before/after into normals
1216 if (sinh > 0) {
1217 before.rotateCCW();
1218 after.rotateCCW();
1219 arcDir = kCW_SkRotationDirection;
1220 } else {
1221 before.rotateCW();
1222 after.rotateCW();
1223 arcDir = kCCW_SkRotationDirection;
1224 }
1225
1226 SkMatrix matrix;
1227 SkPoint pts[kSkBuildQuadArcStorage];
reed@google.comabf15c12011-01-18 20:35:51 +00001228
reed@android.com8a1c16f2008-12-17 15:59:43 +00001229 matrix.setScale(radius, radius);
1230 matrix.postTranslate(xx - SkScalarMul(radius, before.fX),
1231 yy - SkScalarMul(radius, before.fY));
reed@google.comabf15c12011-01-18 20:35:51 +00001232
reed@android.com8a1c16f2008-12-17 15:59:43 +00001233 int count = SkBuildQuadArc(before, after, arcDir, &matrix, pts);
reed@google.comabf15c12011-01-18 20:35:51 +00001234
reed@android.com8a1c16f2008-12-17 15:59:43 +00001235 this->incReserve(count);
1236 // [xx,yy] == pts[0]
1237 this->lineTo(xx, yy);
1238 for (int i = 1; i < count; i += 2) {
1239 this->quadTo(pts[i], pts[i+1]);
1240 }
1241}
1242
1243///////////////////////////////////////////////////////////////////////////////
1244
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001245void SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy, AddPathMode mode) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001246 SkMatrix matrix;
1247
1248 matrix.setTranslate(dx, dy);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001249 this->addPath(path, matrix, mode);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001250}
1251
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001252void SkPath::addPath(const SkPath& path, const SkMatrix& matrix, AddPathMode mode) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001253 SkPathRef::Editor(&fPathRef, path.countVerbs(), path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001254
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001255 RawIter iter(path);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001256 SkPoint pts[4];
1257 Verb verb;
1258
1259 SkMatrix::MapPtsProc proc = matrix.getMapPtsProc();
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001260 bool firstVerb = true;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001261 while ((verb = iter.next(pts)) != kDone_Verb) {
1262 switch (verb) {
1263 case kMove_Verb:
1264 proc(matrix, &pts[0], &pts[0], 1);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001265 if (firstVerb && mode == kExtend_AddPathMode && !isEmpty()) {
1266 injectMoveToIfNeeded(); // In case last contour is closed
1267 this->lineTo(pts[0]);
1268 } else {
1269 this->moveTo(pts[0]);
1270 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001271 break;
1272 case kLine_Verb:
1273 proc(matrix, &pts[1], &pts[1], 1);
1274 this->lineTo(pts[1]);
1275 break;
1276 case kQuad_Verb:
1277 proc(matrix, &pts[1], &pts[1], 2);
1278 this->quadTo(pts[1], pts[2]);
1279 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001280 case kConic_Verb:
1281 proc(matrix, &pts[1], &pts[1], 2);
1282 this->conicTo(pts[1], pts[2], iter.conicWeight());
1283 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001284 case kCubic_Verb:
1285 proc(matrix, &pts[1], &pts[1], 3);
1286 this->cubicTo(pts[1], pts[2], pts[3]);
1287 break;
1288 case kClose_Verb:
1289 this->close();
1290 break;
1291 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001292 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001293 }
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001294 firstVerb = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001295 }
1296}
1297
1298///////////////////////////////////////////////////////////////////////////////
1299
reed@google.com277c3f82013-05-31 15:17:50 +00001300static int pts_in_verb(unsigned verb) {
1301 static const uint8_t gPtsInVerb[] = {
1302 1, // kMove
1303 1, // kLine
1304 2, // kQuad
1305 2, // kConic
1306 3, // kCubic
1307 0, // kClose
1308 0 // kDone
1309 };
1310
1311 SkASSERT(verb < SK_ARRAY_COUNT(gPtsInVerb));
1312 return gPtsInVerb[verb];
1313}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001314
reed@android.com8a1c16f2008-12-17 15:59:43 +00001315// ignore the last point of the 1st contour
1316void SkPath::reversePathTo(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001317 int i, vcount = path.fPathRef->countVerbs();
1318 // exit early if the path is empty, or just has a moveTo.
1319 if (vcount < 2) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001320 return;
1321 }
1322
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001323 SkPathRef::Editor(&fPathRef, vcount, path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001324
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001325 const uint8_t* verbs = path.fPathRef->verbs();
1326 const SkPoint* pts = path.fPathRef->points();
reed@google.com277c3f82013-05-31 15:17:50 +00001327 const SkScalar* conicWeights = path.fPathRef->conicWeights();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001328
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001329 SkASSERT(verbs[~0] == kMove_Verb);
1330 for (i = 1; i < vcount; ++i) {
reed@google.com277c3f82013-05-31 15:17:50 +00001331 unsigned v = verbs[~i];
1332 int n = pts_in_verb(v);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001333 if (n == 0) {
1334 break;
1335 }
1336 pts += n;
reed@google.com277c3f82013-05-31 15:17:50 +00001337 conicWeights += (SkPath::kConic_Verb == v);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001338 }
1339
1340 while (--i > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001341 switch (verbs[~i]) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001342 case kLine_Verb:
1343 this->lineTo(pts[-1].fX, pts[-1].fY);
1344 break;
1345 case kQuad_Verb:
1346 this->quadTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY);
1347 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001348 case kConic_Verb:
1349 this->conicTo(pts[-1], pts[-2], *--conicWeights);
1350 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001351 case kCubic_Verb:
1352 this->cubicTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY,
1353 pts[-3].fX, pts[-3].fY);
1354 break;
1355 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001356 SkDEBUGFAIL("bad verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001357 break;
1358 }
reed@google.com277c3f82013-05-31 15:17:50 +00001359 pts -= pts_in_verb(verbs[~i]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001360 }
1361}
1362
reed@google.com63d73742012-01-10 15:33:12 +00001363void SkPath::reverseAddPath(const SkPath& src) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001364 SkPathRef::Editor ed(&fPathRef, src.fPathRef->countPoints(), src.fPathRef->countVerbs());
reed@google.com63d73742012-01-10 15:33:12 +00001365
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001366 const SkPoint* pts = src.fPathRef->pointsEnd();
1367 // we will iterator through src's verbs backwards
1368 const uint8_t* verbs = src.fPathRef->verbsMemBegin(); // points at the last verb
1369 const uint8_t* verbsEnd = src.fPathRef->verbs(); // points just past the first verb
reed@google.com277c3f82013-05-31 15:17:50 +00001370 const SkScalar* conicWeights = src.fPathRef->conicWeightsEnd();
reed@google.com63d73742012-01-10 15:33:12 +00001371
1372 bool needMove = true;
1373 bool needClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001374 while (verbs < verbsEnd) {
1375 uint8_t v = *(verbs++);
reed@google.com277c3f82013-05-31 15:17:50 +00001376 int n = pts_in_verb(v);
reed@google.com63d73742012-01-10 15:33:12 +00001377
1378 if (needMove) {
1379 --pts;
1380 this->moveTo(pts->fX, pts->fY);
1381 needMove = false;
1382 }
1383 pts -= n;
1384 switch (v) {
1385 case kMove_Verb:
1386 if (needClose) {
1387 this->close();
1388 needClose = false;
1389 }
1390 needMove = true;
1391 pts += 1; // so we see the point in "if (needMove)" above
1392 break;
1393 case kLine_Verb:
1394 this->lineTo(pts[0]);
1395 break;
1396 case kQuad_Verb:
1397 this->quadTo(pts[1], pts[0]);
1398 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001399 case kConic_Verb:
1400 this->conicTo(pts[1], pts[0], *--conicWeights);
1401 break;
reed@google.com63d73742012-01-10 15:33:12 +00001402 case kCubic_Verb:
1403 this->cubicTo(pts[2], pts[1], pts[0]);
1404 break;
1405 case kClose_Verb:
1406 needClose = true;
1407 break;
1408 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00001409 SkDEBUGFAIL("unexpected verb");
reed@google.com63d73742012-01-10 15:33:12 +00001410 }
1411 }
1412}
1413
reed@android.com8a1c16f2008-12-17 15:59:43 +00001414///////////////////////////////////////////////////////////////////////////////
1415
1416void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
1417 SkMatrix matrix;
1418
1419 matrix.setTranslate(dx, dy);
1420 this->transform(matrix, dst);
1421}
1422
reed@android.com8a1c16f2008-12-17 15:59:43 +00001423static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
1424 int level = 2) {
1425 if (--level >= 0) {
1426 SkPoint tmp[7];
1427
1428 SkChopCubicAtHalf(pts, tmp);
1429 subdivide_cubic_to(path, &tmp[0], level);
1430 subdivide_cubic_to(path, &tmp[3], level);
1431 } else {
1432 path->cubicTo(pts[1], pts[2], pts[3]);
1433 }
1434}
1435
1436void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
1437 SkDEBUGCODE(this->validate();)
1438 if (dst == NULL) {
1439 dst = (SkPath*)this;
1440 }
1441
tomhudson@google.com8d430182011-06-06 19:11:19 +00001442 if (matrix.hasPerspective()) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001443 SkPath tmp;
1444 tmp.fFillType = fFillType;
1445
1446 SkPath::Iter iter(*this, false);
1447 SkPoint pts[4];
1448 SkPath::Verb verb;
1449
reed@google.com4a3b7142012-05-16 17:16:46 +00001450 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001451 switch (verb) {
1452 case kMove_Verb:
1453 tmp.moveTo(pts[0]);
1454 break;
1455 case kLine_Verb:
1456 tmp.lineTo(pts[1]);
1457 break;
1458 case kQuad_Verb:
reed220f9262014-12-17 08:21:04 -08001459 // promote the quad to a conic
1460 tmp.conicTo(pts[1], pts[2],
1461 SkConic::TransformW(pts, SK_Scalar1, matrix));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001462 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001463 case kConic_Verb:
reed220f9262014-12-17 08:21:04 -08001464 tmp.conicTo(pts[1], pts[2],
1465 SkConic::TransformW(pts, iter.conicWeight(), matrix));
reed@google.com277c3f82013-05-31 15:17:50 +00001466 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001467 case kCubic_Verb:
1468 subdivide_cubic_to(&tmp, pts);
1469 break;
1470 case kClose_Verb:
1471 tmp.close();
1472 break;
1473 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001474 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001475 break;
1476 }
1477 }
1478
1479 dst->swap(tmp);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001480 SkPathRef::Editor ed(&dst->fPathRef);
1481 matrix.mapPoints(ed.points(), ed.pathRef()->countPoints());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001482 dst->fDirection = kUnknown_Direction;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001483 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001484 SkPathRef::CreateTransformedCopy(&dst->fPathRef, *fPathRef.get(), matrix);
1485
reed@android.com8a1c16f2008-12-17 15:59:43 +00001486 if (this != dst) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001487 dst->fFillType = fFillType;
reed@google.com2a6f8ab2011-10-25 18:41:23 +00001488 dst->fConvexity = fConvexity;
jvanverthb3eb6872014-10-24 07:12:51 -07001489 dst->fIsVolatile = fIsVolatile;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001490 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001491
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001492 if (kUnknown_Direction == fDirection) {
1493 dst->fDirection = kUnknown_Direction;
1494 } else {
1495 SkScalar det2x2 =
1496 SkScalarMul(matrix.get(SkMatrix::kMScaleX), matrix.get(SkMatrix::kMScaleY)) -
1497 SkScalarMul(matrix.get(SkMatrix::kMSkewX), matrix.get(SkMatrix::kMSkewY));
1498 if (det2x2 < 0) {
1499 dst->fDirection = SkPath::OppositeDirection(static_cast<Direction>(fDirection));
1500 } else if (det2x2 > 0) {
1501 dst->fDirection = fDirection;
1502 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001503 dst->fConvexity = kUnknown_Convexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001504 dst->fDirection = kUnknown_Direction;
1505 }
1506 }
1507
reed@android.com8a1c16f2008-12-17 15:59:43 +00001508 SkDEBUGCODE(dst->validate();)
1509 }
1510}
1511
reed@android.com8a1c16f2008-12-17 15:59:43 +00001512///////////////////////////////////////////////////////////////////////////////
1513///////////////////////////////////////////////////////////////////////////////
1514
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001515enum SegmentState {
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001516 kEmptyContour_SegmentState, // The current contour is empty. We may be
1517 // starting processing or we may have just
1518 // closed a contour.
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001519 kAfterMove_SegmentState, // We have seen a move, but nothing else.
1520 kAfterPrimitive_SegmentState // We have seen a primitive but not yet
1521 // closed the path. Also the initial state.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001522};
1523
1524SkPath::Iter::Iter() {
1525#ifdef SK_DEBUG
1526 fPts = NULL;
reed@google.com277c3f82013-05-31 15:17:50 +00001527 fConicWeights = NULL;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001528 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001529 fForceClose = fCloseLine = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001530 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001531#endif
1532 // need to init enough to make next() harmlessly return kDone_Verb
1533 fVerbs = NULL;
1534 fVerbStop = NULL;
1535 fNeedClose = false;
1536}
1537
1538SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
1539 this->setPath(path, forceClose);
1540}
1541
1542void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001543 fPts = path.fPathRef->points();
1544 fVerbs = path.fPathRef->verbs();
1545 fVerbStop = path.fPathRef->verbsMemBegin();
reed@google.com277c3f82013-05-31 15:17:50 +00001546 fConicWeights = path.fPathRef->conicWeights() - 1; // begin one behind
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001547 fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org72785c42011-12-29 21:03:28 +00001548 fMoveTo.fX = fMoveTo.fY = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001549 fForceClose = SkToU8(forceClose);
1550 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001551 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001552}
1553
1554bool SkPath::Iter::isClosedContour() const {
1555 if (fVerbs == NULL || fVerbs == fVerbStop) {
1556 return false;
1557 }
1558 if (fForceClose) {
1559 return true;
1560 }
1561
1562 const uint8_t* verbs = fVerbs;
1563 const uint8_t* stop = fVerbStop;
reed@google.comabf15c12011-01-18 20:35:51 +00001564
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001565 if (kMove_Verb == *(verbs - 1)) {
1566 verbs -= 1; // skip the initial moveto
reed@android.com8a1c16f2008-12-17 15:59:43 +00001567 }
1568
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001569 while (verbs > stop) {
1570 // verbs points one beyond the current verb, decrement first.
1571 unsigned v = *(--verbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001572 if (kMove_Verb == v) {
1573 break;
1574 }
1575 if (kClose_Verb == v) {
1576 return true;
1577 }
1578 }
1579 return false;
1580}
1581
1582SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001583 SkASSERT(pts);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001584 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001585 // A special case: if both points are NaN, SkPoint::operation== returns
1586 // false, but the iterator expects that they are treated as the same.
1587 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001588 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1589 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001590 return kClose_Verb;
1591 }
1592
reed@google.com9e25dbf2012-05-15 17:05:38 +00001593 pts[0] = fLastPt;
1594 pts[1] = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001595 fLastPt = fMoveTo;
1596 fCloseLine = true;
1597 return kLine_Verb;
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +00001598 } else {
1599 pts[0] = fMoveTo;
1600 return kClose_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001601 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001602}
1603
reed@google.com9e25dbf2012-05-15 17:05:38 +00001604const SkPoint& SkPath::Iter::cons_moveTo() {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001605 if (fSegmentState == kAfterMove_SegmentState) {
1606 // Set the first return pt to the move pt
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001607 fSegmentState = kAfterPrimitive_SegmentState;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001608 return fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001609 } else {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001610 SkASSERT(fSegmentState == kAfterPrimitive_SegmentState);
1611 // Set the first return pt to the last pt of the previous primitive.
reed@google.com9e25dbf2012-05-15 17:05:38 +00001612 return fPts[-1];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001613 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001614}
1615
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001616void SkPath::Iter::consumeDegenerateSegments() {
1617 // We need to step over anything that will not move the current draw point
1618 // forward before the next move is seen
1619 const uint8_t* lastMoveVerb = 0;
1620 const SkPoint* lastMovePt = 0;
robertphillipsb8de1f42015-02-23 11:17:01 -08001621 const SkScalar* lastMoveWeight = NULL;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001622 SkPoint lastPt = fLastPt;
1623 while (fVerbs != fVerbStop) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001624 unsigned verb = *(fVerbs - 1); // fVerbs is one beyond the current verb
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001625 switch (verb) {
1626 case kMove_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001627 // Keep a record of this most recent move
1628 lastMoveVerb = fVerbs;
1629 lastMovePt = fPts;
robertphillipsb8de1f42015-02-23 11:17:01 -08001630 lastMoveWeight = fConicWeights;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001631 lastPt = fPts[0];
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001632 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001633 fPts++;
1634 break;
1635
1636 case kClose_Verb:
schenney@chromium.org7e963602012-06-13 17:05:43 +00001637 // A close when we are in a segment is always valid except when it
1638 // follows a move which follows a segment.
1639 if (fSegmentState == kAfterPrimitive_SegmentState && !lastMoveVerb) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001640 return;
1641 }
1642 // A close at any other time must be ignored
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001643 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001644 break;
1645
1646 case kLine_Verb:
1647 if (!IsLineDegenerate(lastPt, fPts[0])) {
1648 if (lastMoveVerb) {
1649 fVerbs = lastMoveVerb;
1650 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001651 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001652 return;
1653 }
1654 return;
1655 }
1656 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001657 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001658 fPts++;
1659 break;
1660
reed@google.com277c3f82013-05-31 15:17:50 +00001661 case kConic_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001662 case kQuad_Verb:
1663 if (!IsQuadDegenerate(lastPt, fPts[0], fPts[1])) {
1664 if (lastMoveVerb) {
1665 fVerbs = lastMoveVerb;
1666 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001667 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001668 return;
1669 }
1670 return;
1671 }
1672 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001673 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001674 fPts += 2;
reed@google.com277c3f82013-05-31 15:17:50 +00001675 fConicWeights += (kConic_Verb == verb);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001676 break;
1677
1678 case kCubic_Verb:
1679 if (!IsCubicDegenerate(lastPt, fPts[0], fPts[1], fPts[2])) {
1680 if (lastMoveVerb) {
1681 fVerbs = lastMoveVerb;
1682 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001683 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001684 return;
1685 }
1686 return;
1687 }
1688 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001689 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001690 fPts += 3;
1691 break;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001692
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001693 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001694 SkDEBUGFAIL("Should never see kDone_Verb");
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001695 }
1696 }
1697}
1698
reed@google.com4a3b7142012-05-16 17:16:46 +00001699SkPath::Verb SkPath::Iter::doNext(SkPoint ptsParam[4]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001700 SkASSERT(ptsParam);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001701
reed@android.com8a1c16f2008-12-17 15:59:43 +00001702 if (fVerbs == fVerbStop) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001703 // Close the curve if requested and if there is some curve to close
1704 if (fNeedClose && fSegmentState == kAfterPrimitive_SegmentState) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001705 if (kLine_Verb == this->autoClose(ptsParam)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001706 return kLine_Verb;
1707 }
1708 fNeedClose = false;
1709 return kClose_Verb;
1710 }
1711 return kDone_Verb;
1712 }
1713
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001714 // fVerbs is one beyond the current verb, decrement first
1715 unsigned verb = *(--fVerbs);
reed@google.com9e25dbf2012-05-15 17:05:38 +00001716 const SkPoint* SK_RESTRICT srcPts = fPts;
1717 SkPoint* SK_RESTRICT pts = ptsParam;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001718
1719 switch (verb) {
1720 case kMove_Verb:
1721 if (fNeedClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001722 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001723 verb = this->autoClose(pts);
1724 if (verb == kClose_Verb) {
1725 fNeedClose = false;
1726 }
1727 return (Verb)verb;
1728 }
1729 if (fVerbs == fVerbStop) { // might be a trailing moveto
1730 return kDone_Verb;
1731 }
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001732 fMoveTo = *srcPts;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001733 pts[0] = *srcPts;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001734 srcPts += 1;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001735 fSegmentState = kAfterMove_SegmentState;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001736 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001737 fNeedClose = fForceClose;
1738 break;
1739 case kLine_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001740 pts[0] = this->cons_moveTo();
1741 pts[1] = srcPts[0];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001742 fLastPt = srcPts[0];
1743 fCloseLine = false;
1744 srcPts += 1;
1745 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001746 case kConic_Verb:
1747 fConicWeights += 1;
1748 // fall-through
reed@android.com8a1c16f2008-12-17 15:59:43 +00001749 case kQuad_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001750 pts[0] = this->cons_moveTo();
1751 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001752 fLastPt = srcPts[1];
1753 srcPts += 2;
1754 break;
1755 case kCubic_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001756 pts[0] = this->cons_moveTo();
1757 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001758 fLastPt = srcPts[2];
1759 srcPts += 3;
1760 break;
1761 case kClose_Verb:
1762 verb = this->autoClose(pts);
1763 if (verb == kLine_Verb) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001764 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001765 } else {
1766 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001767 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001768 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001769 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001770 break;
1771 }
1772 fPts = srcPts;
1773 return (Verb)verb;
1774}
1775
1776///////////////////////////////////////////////////////////////////////////////
1777
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001778SkPath::RawIter::RawIter() {
1779#ifdef SK_DEBUG
1780 fPts = NULL;
reed@google.com277c3f82013-05-31 15:17:50 +00001781 fConicWeights = NULL;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001782 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
1783#endif
1784 // need to init enough to make next() harmlessly return kDone_Verb
1785 fVerbs = NULL;
1786 fVerbStop = NULL;
1787}
1788
1789SkPath::RawIter::RawIter(const SkPath& path) {
1790 this->setPath(path);
1791}
1792
1793void SkPath::RawIter::setPath(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001794 fPts = path.fPathRef->points();
1795 fVerbs = path.fPathRef->verbs();
1796 fVerbStop = path.fPathRef->verbsMemBegin();
reed@google.com277c3f82013-05-31 15:17:50 +00001797 fConicWeights = path.fPathRef->conicWeights() - 1; // begin one behind
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001798 fMoveTo.fX = fMoveTo.fY = 0;
1799 fLastPt.fX = fLastPt.fY = 0;
1800}
1801
1802SkPath::Verb SkPath::RawIter::next(SkPoint pts[4]) {
bsalomon49f085d2014-09-05 13:34:00 -07001803 SkASSERT(pts);
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001804 if (fVerbs == fVerbStop) {
1805 return kDone_Verb;
1806 }
1807
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001808 // fVerbs points one beyond next verb so decrement first.
1809 unsigned verb = *(--fVerbs);
1810 const SkPoint* srcPts = fPts;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001811
1812 switch (verb) {
1813 case kMove_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001814 pts[0] = *srcPts;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001815 fMoveTo = srcPts[0];
1816 fLastPt = fMoveTo;
1817 srcPts += 1;
1818 break;
1819 case kLine_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001820 pts[0] = fLastPt;
1821 pts[1] = srcPts[0];
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001822 fLastPt = srcPts[0];
1823 srcPts += 1;
1824 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001825 case kConic_Verb:
1826 fConicWeights += 1;
1827 // fall-through
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001828 case kQuad_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001829 pts[0] = fLastPt;
1830 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001831 fLastPt = srcPts[1];
1832 srcPts += 2;
1833 break;
1834 case kCubic_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001835 pts[0] = fLastPt;
1836 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001837 fLastPt = srcPts[2];
1838 srcPts += 3;
1839 break;
1840 case kClose_Verb:
1841 fLastPt = fMoveTo;
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001842 pts[0] = fMoveTo;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001843 break;
1844 }
1845 fPts = srcPts;
1846 return (Verb)verb;
1847}
1848
1849///////////////////////////////////////////////////////////////////////////////
1850
reed@android.com8a1c16f2008-12-17 15:59:43 +00001851/*
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001852 Format in compressed buffer: [ptCount, verbCount, pts[], verbs[]]
reed@android.com8a1c16f2008-12-17 15:59:43 +00001853*/
1854
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00001855size_t SkPath::writeToMemory(void* storage) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001856 SkDEBUGCODE(this->validate();)
1857
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001858 if (NULL == storage) {
robertphillips@google.comca0c8382013-09-26 12:18:23 +00001859 const int byteCount = sizeof(int32_t) + fPathRef->writeSize();
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001860 return SkAlign4(byteCount);
1861 }
1862
1863 SkWBuffer buffer(storage);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001864
robertphillips@google.com466310d2013-12-03 16:43:54 +00001865 int32_t packed = (fConvexity << kConvexity_SerializationShift) |
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001866 (fFillType << kFillType_SerializationShift) |
jvanverthb3eb6872014-10-24 07:12:51 -07001867 (fDirection << kDirection_SerializationShift) |
1868 (fIsVolatile << kIsVolatile_SerializationShift);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001869
robertphillips@google.com2972bb52012-08-07 17:32:51 +00001870 buffer.write32(packed);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001871
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001872 fPathRef->writeToBuffer(&buffer);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001873
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001874 buffer.padToAlign4();
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00001875 return buffer.pos();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001876}
1877
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00001878size_t SkPath::readFromMemory(const void* storage, size_t length) {
1879 SkRBufferWithSizeCheck buffer(storage, length);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001880
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00001881 int32_t packed;
1882 if (!buffer.readS32(&packed)) {
1883 return 0;
1884 }
1885
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001886 fConvexity = (packed >> kConvexity_SerializationShift) & 0xFF;
1887 fFillType = (packed >> kFillType_SerializationShift) & 0xFF;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001888 fDirection = (packed >> kDirection_SerializationShift) & 0x3;
jvanverthb3eb6872014-10-24 07:12:51 -07001889 fIsVolatile = (packed >> kIsVolatile_SerializationShift) & 0x1;
commit-bot@chromium.orgfed2ab62014-01-23 15:16:05 +00001890 SkPathRef* pathRef = SkPathRef::CreateFromBuffer(&buffer);
reed@google.comabf15c12011-01-18 20:35:51 +00001891
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00001892 size_t sizeRead = 0;
1893 if (buffer.isValid()) {
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00001894 fPathRef.reset(pathRef);
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00001895 SkDEBUGCODE(this->validate();)
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00001896 buffer.skipToAlign4();
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00001897 sizeRead = buffer.pos();
bsalomon49f085d2014-09-05 13:34:00 -07001898 } else if (pathRef) {
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00001899 // If the buffer is not valid, pathRef should be NULL
1900 sk_throw();
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00001901 }
1902 return sizeRead;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001903}
1904
1905///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00001906
reede05fed02014-12-15 07:59:53 -08001907#include "SkStringUtils.h"
caryclark66a5d8b2014-06-24 08:30:15 -07001908#include "SkStream.h"
reed@google.com51bbe752013-01-17 22:07:50 +00001909
reed@google.com51bbe752013-01-17 22:07:50 +00001910static void append_params(SkString* str, const char label[], const SkPoint pts[],
reede05fed02014-12-15 07:59:53 -08001911 int count, SkScalarAsStringType strType, SkScalar conicWeight = -1) {
reed@google.com51bbe752013-01-17 22:07:50 +00001912 str->append(label);
1913 str->append("(");
skia.committer@gmail.com15dd3002013-01-18 07:07:28 +00001914
reed@google.com51bbe752013-01-17 22:07:50 +00001915 const SkScalar* values = &pts[0].fX;
1916 count *= 2;
1917
1918 for (int i = 0; i < count; ++i) {
reede05fed02014-12-15 07:59:53 -08001919 SkAppendScalar(str, values[i], strType);
reed@google.com51bbe752013-01-17 22:07:50 +00001920 if (i < count - 1) {
1921 str->append(", ");
1922 }
1923 }
reed@google.com277c3f82013-05-31 15:17:50 +00001924 if (conicWeight >= 0) {
1925 str->append(", ");
reede05fed02014-12-15 07:59:53 -08001926 SkAppendScalar(str, conicWeight, strType);
reed@google.com277c3f82013-05-31 15:17:50 +00001927 }
caryclark08fa28c2014-10-23 13:08:56 -07001928 str->append(");");
reede05fed02014-12-15 07:59:53 -08001929 if (kHex_SkScalarAsStringType == strType) {
caryclark08fa28c2014-10-23 13:08:56 -07001930 str->append(" // ");
1931 for (int i = 0; i < count; ++i) {
reede05fed02014-12-15 07:59:53 -08001932 SkAppendScalarDec(str, values[i]);
caryclark08fa28c2014-10-23 13:08:56 -07001933 if (i < count - 1) {
1934 str->append(", ");
1935 }
1936 }
1937 if (conicWeight >= 0) {
1938 str->append(", ");
reede05fed02014-12-15 07:59:53 -08001939 SkAppendScalarDec(str, conicWeight);
caryclark08fa28c2014-10-23 13:08:56 -07001940 }
1941 }
1942 str->append("\n");
reed@google.com51bbe752013-01-17 22:07:50 +00001943}
1944
caryclarke9562592014-09-15 09:26:09 -07001945void SkPath::dump(SkWStream* wStream, bool forceClose, bool dumpAsHex) const {
reede05fed02014-12-15 07:59:53 -08001946 SkScalarAsStringType asType = dumpAsHex ? kHex_SkScalarAsStringType : kDec_SkScalarAsStringType;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001947 Iter iter(*this, forceClose);
1948 SkPoint pts[4];
1949 Verb verb;
1950
caryclark66a5d8b2014-06-24 08:30:15 -07001951 if (!wStream) {
1952 SkDebugf("path: forceClose=%s\n", forceClose ? "true" : "false");
1953 }
reed@google.com51bbe752013-01-17 22:07:50 +00001954 SkString builder;
1955
reed@google.com4a3b7142012-05-16 17:16:46 +00001956 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001957 switch (verb) {
1958 case kMove_Verb:
reede05fed02014-12-15 07:59:53 -08001959 append_params(&builder, "path.moveTo", &pts[0], 1, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001960 break;
1961 case kLine_Verb:
reede05fed02014-12-15 07:59:53 -08001962 append_params(&builder, "path.lineTo", &pts[1], 1, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001963 break;
1964 case kQuad_Verb:
reede05fed02014-12-15 07:59:53 -08001965 append_params(&builder, "path.quadTo", &pts[1], 2, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001966 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001967 case kConic_Verb:
reede05fed02014-12-15 07:59:53 -08001968 append_params(&builder, "path.conicTo", &pts[1], 2, asType, iter.conicWeight());
reed@google.com277c3f82013-05-31 15:17:50 +00001969 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001970 case kCubic_Verb:
reede05fed02014-12-15 07:59:53 -08001971 append_params(&builder, "path.cubicTo", &pts[1], 3, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001972 break;
1973 case kClose_Verb:
caryclark66a5d8b2014-06-24 08:30:15 -07001974 builder.append("path.close();\n");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001975 break;
1976 default:
1977 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
1978 verb = kDone_Verb; // stop the loop
1979 break;
1980 }
caryclark1049f122015-04-20 08:31:59 -07001981 if (!wStream && builder.size()) {
1982 SkDebugf("%s", builder.c_str());
1983 builder.reset();
1984 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001985 }
caryclark66a5d8b2014-06-24 08:30:15 -07001986 if (wStream) {
1987 wStream->writeText(builder.c_str());
caryclark66a5d8b2014-06-24 08:30:15 -07001988 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001989}
1990
reed@android.come522ca52009-11-23 20:10:41 +00001991void SkPath::dump() const {
caryclarke9562592014-09-15 09:26:09 -07001992 this->dump(NULL, false, false);
1993}
1994
1995void SkPath::dumpHex() const {
1996 this->dump(NULL, false, true);
reed@android.come522ca52009-11-23 20:10:41 +00001997}
1998
1999#ifdef SK_DEBUG
2000void SkPath::validate() const {
2001 SkASSERT(this != NULL);
2002 SkASSERT((fFillType & ~3) == 0);
reed@google.comabf15c12011-01-18 20:35:51 +00002003
djsollen@google.com077348c2012-10-22 20:23:32 +00002004#ifdef SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002005 if (!fBoundsIsDirty) {
2006 SkRect bounds;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002007
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002008 bool isFinite = compute_pt_bounds(&bounds, *fPathRef.get());
robertphillips@google.com5d8d1862012-08-15 14:36:41 +00002009 SkASSERT(SkToBool(fIsFinite) == isFinite);
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002010
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002011 if (fPathRef->countPoints() <= 1) {
reed@android.come522ca52009-11-23 20:10:41 +00002012 // if we're empty, fBounds may be empty but translated, so we can't
2013 // necessarily compare to bounds directly
2014 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
2015 // be [2, 2, 2, 2]
2016 SkASSERT(bounds.isEmpty());
2017 SkASSERT(fBounds.isEmpty());
2018 } else {
reed@google.comeac52bd2011-11-14 18:13:59 +00002019 if (bounds.isEmpty()) {
2020 SkASSERT(fBounds.isEmpty());
2021 } else {
reed@google.com3563c9e2011-11-14 19:34:57 +00002022 if (!fBounds.isEmpty()) {
2023 SkASSERT(fBounds.contains(bounds));
2024 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002025 }
reed@android.come522ca52009-11-23 20:10:41 +00002026 }
2027 }
djsollen@google.com077348c2012-10-22 20:23:32 +00002028#endif // SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002029}
djsollen@google.com077348c2012-10-22 20:23:32 +00002030#endif // SK_DEBUG
reed@android.come522ca52009-11-23 20:10:41 +00002031
reed@google.com04863fa2011-05-15 04:08:24 +00002032///////////////////////////////////////////////////////////////////////////////
2033
reed@google.com0b7b9822011-05-16 12:29:27 +00002034static int sign(SkScalar x) { return x < 0; }
2035#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00002036
robertphillipsc506e302014-09-16 09:43:31 -07002037enum DirChange {
2038 kLeft_DirChange,
2039 kRight_DirChange,
2040 kStraight_DirChange,
2041 kBackwards_DirChange,
2042
2043 kInvalid_DirChange
2044};
2045
2046
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002047static bool almost_equal(SkScalar compA, SkScalar compB) {
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002048 // The error epsilon was empirically derived; worse case round rects
2049 // with a mid point outset by 2x float epsilon in tests had an error
2050 // of 12.
2051 const int epsilon = 16;
2052 if (!SkScalarIsFinite(compA) || !SkScalarIsFinite(compB)) {
2053 return false;
2054 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002055 // no need to check for small numbers because SkPath::Iter has removed degenerate values
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002056 int aBits = SkFloatAs2sCompliment(compA);
2057 int bBits = SkFloatAs2sCompliment(compB);
2058 return aBits < bBits + epsilon && bBits < aBits + epsilon;
reed@google.com04863fa2011-05-15 04:08:24 +00002059}
2060
caryclarkb4216502015-03-02 13:02:34 -08002061static bool approximately_zero_when_compared_to(double x, double y) {
2062 return x == 0 || fabs(x) < fabs(y * FLT_EPSILON);
robertphillipsc506e302014-09-16 09:43:31 -07002063}
2064
caryclarkb4216502015-03-02 13:02:34 -08002065
reed@google.com04863fa2011-05-15 04:08:24 +00002066// only valid for a single contour
2067struct Convexicator {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002068 Convexicator()
2069 : fPtCount(0)
2070 , fConvexity(SkPath::kConvex_Convexity)
caryclarkd3d1a982014-12-08 04:57:38 -08002071 , fDirection(SkPath::kUnknown_Direction)
caryclark5ccef572015-03-02 10:07:56 -08002072 , fIsFinite(true)
2073 , fIsCurve(false) {
robertphillipsc506e302014-09-16 09:43:31 -07002074 fExpectedDir = kInvalid_DirChange;
reed@google.com04863fa2011-05-15 04:08:24 +00002075 // warnings
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002076 fLastPt.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002077 fCurrPt.set(0, 0);
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002078 fLastVec.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002079 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00002080
2081 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00002082 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00002083 }
2084
2085 SkPath::Convexity getConvexity() const { return fConvexity; }
2086
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002087 /** The direction returned is only valid if the path is determined convex */
2088 SkPath::Direction getDirection() const { return fDirection; }
2089
reed@google.com04863fa2011-05-15 04:08:24 +00002090 void addPt(const SkPoint& pt) {
caryclarkd3d1a982014-12-08 04:57:38 -08002091 if (SkPath::kConcave_Convexity == fConvexity || !fIsFinite) {
reed@google.com04863fa2011-05-15 04:08:24 +00002092 return;
2093 }
2094
2095 if (0 == fPtCount) {
2096 fCurrPt = pt;
2097 ++fPtCount;
2098 } else {
2099 SkVector vec = pt - fCurrPt;
caryclarkd3d1a982014-12-08 04:57:38 -08002100 SkScalar lengthSqd = vec.lengthSqd();
2101 if (!SkScalarIsFinite(lengthSqd)) {
2102 fIsFinite = false;
2103 } else if (!SkScalarNearlyZero(lengthSqd, SK_ScalarNearlyZero*SK_ScalarNearlyZero)) {
caryclarkb4216502015-03-02 13:02:34 -08002104 fPriorPt = fLastPt;
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002105 fLastPt = fCurrPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002106 fCurrPt = pt;
2107 if (++fPtCount == 2) {
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002108 fFirstVec = fLastVec = vec;
reed@google.com04863fa2011-05-15 04:08:24 +00002109 } else {
2110 SkASSERT(fPtCount > 2);
2111 this->addVec(vec);
2112 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002113
reed@google.com85b6e392011-05-15 20:25:17 +00002114 int sx = sign(vec.fX);
2115 int sy = sign(vec.fY);
2116 fDx += (sx != fSx);
2117 fDy += (sy != fSy);
2118 fSx = sx;
2119 fSy = sy;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002120
reed@google.com85b6e392011-05-15 20:25:17 +00002121 if (fDx > 3 || fDy > 3) {
2122 fConvexity = SkPath::kConcave_Convexity;
2123 }
reed@google.com04863fa2011-05-15 04:08:24 +00002124 }
2125 }
2126 }
2127
2128 void close() {
2129 if (fPtCount > 2) {
2130 this->addVec(fFirstVec);
2131 }
2132 }
2133
caryclarkb4216502015-03-02 13:02:34 -08002134 DirChange directionChange(const SkVector& curVec) {
2135 SkScalar cross = SkPoint::CrossProduct(fLastVec, curVec);
2136
2137 SkScalar smallest = SkTMin(fCurrPt.fX, SkTMin(fCurrPt.fY, SkTMin(fLastPt.fX, fLastPt.fY)));
2138 SkScalar largest = SkTMax(fCurrPt.fX, SkTMax(fCurrPt.fY, SkTMax(fLastPt.fX, fLastPt.fY)));
2139 largest = SkTMax(largest, -smallest);
2140
2141 if (!almost_equal(largest, largest + cross)) {
2142 int sign = SkScalarSignAsInt(cross);
2143 if (sign) {
2144 return (1 == sign) ? kRight_DirChange : kLeft_DirChange;
2145 }
2146 }
2147
2148 if (cross) {
2149 double dLastVecX = SkScalarToDouble(fLastPt.fX) - SkScalarToDouble(fPriorPt.fX);
2150 double dLastVecY = SkScalarToDouble(fLastPt.fY) - SkScalarToDouble(fPriorPt.fY);
2151 double dCurrVecX = SkScalarToDouble(fCurrPt.fX) - SkScalarToDouble(fLastPt.fX);
2152 double dCurrVecY = SkScalarToDouble(fCurrPt.fY) - SkScalarToDouble(fLastPt.fY);
2153 double dCross = dLastVecX * dCurrVecY - dLastVecY * dCurrVecX;
2154 if (!approximately_zero_when_compared_to(dCross, SkScalarToDouble(largest))) {
2155 int sign = SkScalarSignAsInt(SkDoubleToScalar(dCross));
2156 if (sign) {
2157 return (1 == sign) ? kRight_DirChange : kLeft_DirChange;
2158 }
2159 }
2160 }
2161
2162 if (!SkScalarNearlyZero(fLastVec.lengthSqd(), SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
2163 !SkScalarNearlyZero(curVec.lengthSqd(), SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
2164 fLastVec.dot(curVec) < 0.0f) {
2165 return kBackwards_DirChange;
2166 }
2167
2168 return kStraight_DirChange;
2169 }
2170
2171
caryclarkd3d1a982014-12-08 04:57:38 -08002172 bool isFinite() const {
2173 return fIsFinite;
2174 }
2175
caryclark5ccef572015-03-02 10:07:56 -08002176 void setCurve(bool isCurve) {
2177 fIsCurve = isCurve;
2178 }
2179
reed@google.com04863fa2011-05-15 04:08:24 +00002180private:
2181 void addVec(const SkVector& vec) {
2182 SkASSERT(vec.fX || vec.fY);
caryclarkb4216502015-03-02 13:02:34 -08002183 DirChange dir = this->directionChange(vec);
robertphillipsc506e302014-09-16 09:43:31 -07002184 switch (dir) {
2185 case kLeft_DirChange: // fall through
2186 case kRight_DirChange:
2187 if (kInvalid_DirChange == fExpectedDir) {
2188 fExpectedDir = dir;
2189 fDirection = (kRight_DirChange == dir) ? SkPath::kCW_Direction
2190 : SkPath::kCCW_Direction;
2191 } else if (dir != fExpectedDir) {
2192 fConvexity = SkPath::kConcave_Convexity;
2193 fDirection = SkPath::kUnknown_Direction;
2194 }
2195 fLastVec = vec;
2196 break;
2197 case kStraight_DirChange:
2198 break;
2199 case kBackwards_DirChange:
caryclark5ccef572015-03-02 10:07:56 -08002200 if (fIsCurve) {
2201 fConvexity = SkPath::kConcave_Convexity;
2202 fDirection = SkPath::kUnknown_Direction;
2203 }
robertphillipsc506e302014-09-16 09:43:31 -07002204 fLastVec = vec;
2205 break;
2206 case kInvalid_DirChange:
2207 SkFAIL("Use of invalid direction change flag");
2208 break;
reed@google.com04863fa2011-05-15 04:08:24 +00002209 }
2210 }
2211
caryclarkb4216502015-03-02 13:02:34 -08002212 SkPoint fPriorPt;
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002213 SkPoint fLastPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002214 SkPoint fCurrPt;
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002215 // fLastVec does not necessarily start at fLastPt. We only advance it when the cross product
2216 // value with the current vec is deemed to be of a significant value.
2217 SkVector fLastVec, fFirstVec;
reed@google.com04863fa2011-05-15 04:08:24 +00002218 int fPtCount; // non-degenerate points
robertphillipsc506e302014-09-16 09:43:31 -07002219 DirChange fExpectedDir;
reed@google.com04863fa2011-05-15 04:08:24 +00002220 SkPath::Convexity fConvexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002221 SkPath::Direction fDirection;
reed@google.com0b7b9822011-05-16 12:29:27 +00002222 int fDx, fDy, fSx, fSy;
caryclarkd3d1a982014-12-08 04:57:38 -08002223 bool fIsFinite;
caryclark5ccef572015-03-02 10:07:56 -08002224 bool fIsCurve;
reed@google.com04863fa2011-05-15 04:08:24 +00002225};
2226
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002227SkPath::Convexity SkPath::internalGetConvexity() const {
2228 SkASSERT(kUnknown_Convexity == fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002229 SkPoint pts[4];
2230 SkPath::Verb verb;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002231 SkPath::Iter iter(*this, true);
reed@google.com04863fa2011-05-15 04:08:24 +00002232
2233 int contourCount = 0;
2234 int count;
2235 Convexicator state;
2236
caryclarkd3d1a982014-12-08 04:57:38 -08002237 if (!isFinite()) {
2238 return kUnknown_Convexity;
2239 }
reed@google.com04863fa2011-05-15 04:08:24 +00002240 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
2241 switch (verb) {
2242 case kMove_Verb:
2243 if (++contourCount > 1) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002244 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002245 return kConcave_Convexity;
2246 }
2247 pts[1] = pts[0];
caryclark5ccef572015-03-02 10:07:56 -08002248 // fall through
2249 case kLine_Verb:
reed@google.com04863fa2011-05-15 04:08:24 +00002250 count = 1;
caryclark5ccef572015-03-02 10:07:56 -08002251 state.setCurve(false);
reed@google.com04863fa2011-05-15 04:08:24 +00002252 break;
caryclark5ccef572015-03-02 10:07:56 -08002253 case kQuad_Verb:
2254 // fall through
2255 case kConic_Verb:
2256 // fall through
2257 case kCubic_Verb:
2258 count = 2 + (kCubic_Verb == verb);
2259 // As an additional enhancement, this could set curve true only
2260 // if the curve is nonlinear
2261 state.setCurve(true);
2262 break;
reed@google.com04863fa2011-05-15 04:08:24 +00002263 case kClose_Verb:
caryclark5ccef572015-03-02 10:07:56 -08002264 state.setCurve(false);
reed@google.com04863fa2011-05-15 04:08:24 +00002265 state.close();
2266 count = 0;
2267 break;
2268 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00002269 SkDEBUGFAIL("bad verb");
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
2274 for (int i = 1; i <= count; i++) {
2275 state.addPt(pts[i]);
2276 }
2277 // early exit
caryclarkd3d1a982014-12-08 04:57:38 -08002278 if (!state.isFinite()) {
2279 return kUnknown_Convexity;
2280 }
reed@google.com04863fa2011-05-15 04:08:24 +00002281 if (kConcave_Convexity == state.getConvexity()) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002282 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002283 return kConcave_Convexity;
2284 }
2285 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002286 fConvexity = state.getConvexity();
2287 if (kConvex_Convexity == fConvexity && kUnknown_Direction == fDirection) {
2288 fDirection = state.getDirection();
2289 }
2290 return static_cast<Convexity>(fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002291}
reed@google.com69a99432012-01-10 18:00:10 +00002292
2293///////////////////////////////////////////////////////////////////////////////
2294
2295class ContourIter {
2296public:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002297 ContourIter(const SkPathRef& pathRef);
reed@google.com69a99432012-01-10 18:00:10 +00002298
2299 bool done() const { return fDone; }
2300 // if !done() then these may be called
2301 int count() const { return fCurrPtCount; }
2302 const SkPoint* pts() const { return fCurrPt; }
2303 void next();
2304
2305private:
2306 int fCurrPtCount;
2307 const SkPoint* fCurrPt;
2308 const uint8_t* fCurrVerb;
2309 const uint8_t* fStopVerbs;
reed@google.com277c3f82013-05-31 15:17:50 +00002310 const SkScalar* fCurrConicWeight;
reed@google.com69a99432012-01-10 18:00:10 +00002311 bool fDone;
reed@google.comd1ab9322012-01-10 18:40:03 +00002312 SkDEBUGCODE(int fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002313};
2314
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002315ContourIter::ContourIter(const SkPathRef& pathRef) {
2316 fStopVerbs = pathRef.verbsMemBegin();
reed@google.com69a99432012-01-10 18:00:10 +00002317 fDone = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002318 fCurrPt = pathRef.points();
2319 fCurrVerb = pathRef.verbs();
reed@google.com277c3f82013-05-31 15:17:50 +00002320 fCurrConicWeight = pathRef.conicWeights();
reed@google.com69a99432012-01-10 18:00:10 +00002321 fCurrPtCount = 0;
reed@google.comd1ab9322012-01-10 18:40:03 +00002322 SkDEBUGCODE(fContourCounter = 0;)
reed@google.com69a99432012-01-10 18:00:10 +00002323 this->next();
2324}
2325
2326void ContourIter::next() {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002327 if (fCurrVerb <= fStopVerbs) {
reed@google.com69a99432012-01-10 18:00:10 +00002328 fDone = true;
2329 }
2330 if (fDone) {
2331 return;
2332 }
2333
2334 // skip pts of prev contour
2335 fCurrPt += fCurrPtCount;
2336
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002337 SkASSERT(SkPath::kMove_Verb == fCurrVerb[~0]);
reed@google.com69a99432012-01-10 18:00:10 +00002338 int ptCount = 1; // moveTo
2339 const uint8_t* verbs = fCurrVerb;
2340
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002341 for (--verbs; verbs > fStopVerbs; --verbs) {
2342 switch (verbs[~0]) {
reed@google.com69a99432012-01-10 18:00:10 +00002343 case SkPath::kMove_Verb:
reed@google.com69a99432012-01-10 18:00:10 +00002344 goto CONTOUR_END;
2345 case SkPath::kLine_Verb:
2346 ptCount += 1;
2347 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002348 case SkPath::kConic_Verb:
2349 fCurrConicWeight += 1;
2350 // fall-through
reed@google.com69a99432012-01-10 18:00:10 +00002351 case SkPath::kQuad_Verb:
2352 ptCount += 2;
2353 break;
2354 case SkPath::kCubic_Verb:
2355 ptCount += 3;
2356 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002357 case SkPath::kClose_Verb:
2358 break;
2359 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00002360 SkDEBUGFAIL("unexpected verb");
reed@google.com69a99432012-01-10 18:00:10 +00002361 break;
2362 }
2363 }
2364CONTOUR_END:
2365 fCurrPtCount = ptCount;
2366 fCurrVerb = verbs;
reed@google.comd1ab9322012-01-10 18:40:03 +00002367 SkDEBUGCODE(++fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002368}
2369
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002370// returns cross product of (p1 - p0) and (p2 - p0)
reed@google.com69a99432012-01-10 18:00:10 +00002371static SkScalar cross_prod(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002372 SkScalar cross = SkPoint::CrossProduct(p1 - p0, p2 - p0);
2373 // We may get 0 when the above subtracts underflow. We expect this to be
2374 // very rare and lazily promote to double.
2375 if (0 == cross) {
2376 double p0x = SkScalarToDouble(p0.fX);
2377 double p0y = SkScalarToDouble(p0.fY);
2378
2379 double p1x = SkScalarToDouble(p1.fX);
2380 double p1y = SkScalarToDouble(p1.fY);
2381
2382 double p2x = SkScalarToDouble(p2.fX);
2383 double p2y = SkScalarToDouble(p2.fY);
2384
2385 cross = SkDoubleToScalar((p1x - p0x) * (p2y - p0y) -
2386 (p1y - p0y) * (p2x - p0x));
2387
2388 }
2389 return cross;
reed@google.com69a99432012-01-10 18:00:10 +00002390}
2391
reed@google.comc1ea60a2012-01-31 15:15:36 +00002392// Returns the first pt with the maximum Y coordinate
reed@google.com69a99432012-01-10 18:00:10 +00002393static int find_max_y(const SkPoint pts[], int count) {
2394 SkASSERT(count > 0);
2395 SkScalar max = pts[0].fY;
reed@google.comc1ea60a2012-01-31 15:15:36 +00002396 int firstIndex = 0;
reed@google.com69a99432012-01-10 18:00:10 +00002397 for (int i = 1; i < count; ++i) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002398 SkScalar y = pts[i].fY;
2399 if (y > max) {
2400 max = y;
2401 firstIndex = i;
reed@google.com69a99432012-01-10 18:00:10 +00002402 }
2403 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002404 return firstIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002405}
2406
reed@google.comcabaf1d2012-01-11 21:03:05 +00002407static int find_diff_pt(const SkPoint pts[], int index, int n, int inc) {
2408 int i = index;
2409 for (;;) {
2410 i = (i + inc) % n;
2411 if (i == index) { // we wrapped around, so abort
2412 break;
2413 }
2414 if (pts[index] != pts[i]) { // found a different point, success!
2415 break;
2416 }
2417 }
2418 return i;
2419}
2420
reed@google.comc1ea60a2012-01-31 15:15:36 +00002421/**
2422 * Starting at index, and moving forward (incrementing), find the xmin and
2423 * xmax of the contiguous points that have the same Y.
2424 */
2425static int find_min_max_x_at_y(const SkPoint pts[], int index, int n,
2426 int* maxIndexPtr) {
2427 const SkScalar y = pts[index].fY;
2428 SkScalar min = pts[index].fX;
2429 SkScalar max = min;
2430 int minIndex = index;
2431 int maxIndex = index;
2432 for (int i = index + 1; i < n; ++i) {
2433 if (pts[i].fY != y) {
2434 break;
2435 }
2436 SkScalar x = pts[i].fX;
2437 if (x < min) {
2438 min = x;
2439 minIndex = i;
2440 } else if (x > max) {
2441 max = x;
2442 maxIndex = i;
2443 }
2444 }
2445 *maxIndexPtr = maxIndex;
2446 return minIndex;
2447}
2448
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002449static void crossToDir(SkScalar cross, SkPath::Direction* dir) {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002450 *dir = cross > 0 ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002451}
2452
reed@google.comac8543f2012-01-30 20:51:25 +00002453/*
2454 * We loop through all contours, and keep the computed cross-product of the
2455 * contour that contained the global y-max. If we just look at the first
2456 * contour, we may find one that is wound the opposite way (correctly) since
2457 * it is the interior of a hole (e.g. 'o'). Thus we must find the contour
2458 * that is outer most (or at least has the global y-max) before we can consider
2459 * its cross product.
2460 */
reed@google.com69a99432012-01-10 18:00:10 +00002461bool SkPath::cheapComputeDirection(Direction* dir) const {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002462 if (kUnknown_Direction != fDirection) {
2463 *dir = static_cast<Direction>(fDirection);
2464 return true;
2465 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002466
2467 // don't want to pay the cost for computing this if it
2468 // is unknown, so we don't call isConvex()
2469 if (kConvex_Convexity == this->getConvexityOrUnknown()) {
2470 SkASSERT(kUnknown_Direction == fDirection);
2471 *dir = static_cast<Direction>(fDirection);
2472 return false;
2473 }
reed@google.com69a99432012-01-10 18:00:10 +00002474
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002475 ContourIter iter(*fPathRef.get());
reed@google.com69a99432012-01-10 18:00:10 +00002476
reed@google.comac8543f2012-01-30 20:51:25 +00002477 // initialize with our logical y-min
2478 SkScalar ymax = this->getBounds().fTop;
2479 SkScalar ymaxCross = 0;
2480
reed@google.com69a99432012-01-10 18:00:10 +00002481 for (; !iter.done(); iter.next()) {
2482 int n = iter.count();
reed@google.comcabaf1d2012-01-11 21:03:05 +00002483 if (n < 3) {
2484 continue;
2485 }
djsollen@google.come63793a2012-03-21 15:39:03 +00002486
reed@google.comcabaf1d2012-01-11 21:03:05 +00002487 const SkPoint* pts = iter.pts();
reed@google.com69a99432012-01-10 18:00:10 +00002488 SkScalar cross = 0;
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002489 int index = find_max_y(pts, n);
2490 if (pts[index].fY < ymax) {
2491 continue;
2492 }
2493
2494 // If there is more than 1 distinct point at the y-max, we take the
2495 // x-min and x-max of them and just subtract to compute the dir.
2496 if (pts[(index + 1) % n].fY == pts[index].fY) {
2497 int maxIndex;
2498 int minIndex = find_min_max_x_at_y(pts, index, n, &maxIndex);
2499 if (minIndex == maxIndex) {
2500 goto TRY_CROSSPROD;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002501 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002502 SkASSERT(pts[minIndex].fY == pts[index].fY);
2503 SkASSERT(pts[maxIndex].fY == pts[index].fY);
2504 SkASSERT(pts[minIndex].fX <= pts[maxIndex].fX);
2505 // we just subtract the indices, and let that auto-convert to
2506 // SkScalar, since we just want - or + to signal the direction.
2507 cross = minIndex - maxIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002508 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002509 TRY_CROSSPROD:
2510 // Find a next and prev index to use for the cross-product test,
2511 // but we try to find pts that form non-zero vectors from pts[index]
2512 //
2513 // Its possible that we can't find two non-degenerate vectors, so
2514 // we have to guard our search (e.g. all the pts could be in the
2515 // same place).
2516
2517 // we pass n - 1 instead of -1 so we don't foul up % operator by
2518 // passing it a negative LH argument.
2519 int prev = find_diff_pt(pts, index, n, n - 1);
2520 if (prev == index) {
2521 // completely degenerate, skip to next contour
reed@google.comac8543f2012-01-30 20:51:25 +00002522 continue;
2523 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002524 int next = find_diff_pt(pts, index, n, 1);
2525 SkASSERT(next != index);
2526 cross = cross_prod(pts[prev], pts[index], pts[next]);
2527 // if we get a zero and the points are horizontal, then we look at the spread in
2528 // x-direction. We really should continue to walk away from the degeneracy until
2529 // there is a divergence.
2530 if (0 == cross && pts[prev].fY == pts[index].fY && pts[next].fY == pts[index].fY) {
2531 // construct the subtract so we get the correct Direction below
2532 cross = pts[index].fX - pts[next].fX;
reed@google.com188bfcf2012-01-17 18:26:38 +00002533 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002534 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002535
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002536 if (cross) {
2537 // record our best guess so far
2538 ymax = pts[index].fY;
2539 ymaxCross = cross;
reed@google.com69a99432012-01-10 18:00:10 +00002540 }
2541 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002542 if (ymaxCross) {
2543 crossToDir(ymaxCross, dir);
2544 fDirection = *dir;
2545 return true;
2546 } else {
2547 return false;
2548 }
reed@google.comac8543f2012-01-30 20:51:25 +00002549}
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002550
2551///////////////////////////////////////////////////////////////////////////////
2552
2553static SkScalar eval_cubic_coeff(SkScalar A, SkScalar B, SkScalar C,
2554 SkScalar D, SkScalar t) {
2555 return SkScalarMulAdd(SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C), t, D);
2556}
2557
2558static SkScalar eval_cubic_pts(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2559 SkScalar t) {
2560 SkScalar A = c3 + 3*(c1 - c2) - c0;
2561 SkScalar B = 3*(c2 - c1 - c1 + c0);
2562 SkScalar C = 3*(c1 - c0);
2563 SkScalar D = c0;
2564 return eval_cubic_coeff(A, B, C, D, t);
2565}
2566
2567/* Given 4 cubic points (either Xs or Ys), and a target X or Y, compute the
2568 t value such that cubic(t) = target
2569 */
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002570static void chopMonoCubicAt(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002571 SkScalar target, SkScalar* t) {
2572 // SkASSERT(c0 <= c1 && c1 <= c2 && c2 <= c3);
2573 SkASSERT(c0 < target && target < c3);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002574
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002575 SkScalar D = c0 - target;
2576 SkScalar A = c3 + 3*(c1 - c2) - c0;
2577 SkScalar B = 3*(c2 - c1 - c1 + c0);
2578 SkScalar C = 3*(c1 - c0);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002579
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002580 const SkScalar TOLERANCE = SK_Scalar1 / 4096;
2581 SkScalar minT = 0;
2582 SkScalar maxT = SK_Scalar1;
2583 SkScalar mid;
2584 int i;
2585 for (i = 0; i < 16; i++) {
2586 mid = SkScalarAve(minT, maxT);
2587 SkScalar delta = eval_cubic_coeff(A, B, C, D, mid);
2588 if (delta < 0) {
2589 minT = mid;
2590 delta = -delta;
2591 } else {
2592 maxT = mid;
2593 }
2594 if (delta < TOLERANCE) {
2595 break;
2596 }
2597 }
2598 *t = mid;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002599}
2600
2601template <size_t N> static void find_minmax(const SkPoint pts[],
2602 SkScalar* minPtr, SkScalar* maxPtr) {
2603 SkScalar min, max;
2604 min = max = pts[0].fX;
2605 for (size_t i = 1; i < N; ++i) {
2606 min = SkMinScalar(min, pts[i].fX);
2607 max = SkMaxScalar(max, pts[i].fX);
2608 }
2609 *minPtr = min;
2610 *maxPtr = max;
2611}
2612
2613static int winding_mono_cubic(const SkPoint pts[], SkScalar x, SkScalar y) {
2614 SkPoint storage[4];
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002615
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002616 int dir = 1;
2617 if (pts[0].fY > pts[3].fY) {
2618 storage[0] = pts[3];
2619 storage[1] = pts[2];
2620 storage[2] = pts[1];
2621 storage[3] = pts[0];
2622 pts = storage;
2623 dir = -1;
2624 }
2625 if (y < pts[0].fY || y >= pts[3].fY) {
2626 return 0;
2627 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002628
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002629 // quickreject or quickaccept
2630 SkScalar min, max;
2631 find_minmax<4>(pts, &min, &max);
2632 if (x < min) {
2633 return 0;
2634 }
2635 if (x > max) {
2636 return dir;
2637 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002638
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002639 // compute the actual x(t) value
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002640 SkScalar t;
2641 chopMonoCubicAt(pts[0].fY, pts[1].fY, pts[2].fY, pts[3].fY, y, &t);
2642 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 +00002643 return xt < x ? dir : 0;
2644}
2645
2646static int winding_cubic(const SkPoint pts[], SkScalar x, SkScalar y) {
2647 SkPoint dst[10];
2648 int n = SkChopCubicAtYExtrema(pts, dst);
2649 int w = 0;
2650 for (int i = 0; i <= n; ++i) {
2651 w += winding_mono_cubic(&dst[i * 3], x, y);
2652 }
2653 return w;
2654}
2655
2656static int winding_mono_quad(const SkPoint pts[], SkScalar x, SkScalar y) {
2657 SkScalar y0 = pts[0].fY;
2658 SkScalar y2 = pts[2].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002659
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002660 int dir = 1;
2661 if (y0 > y2) {
2662 SkTSwap(y0, y2);
2663 dir = -1;
2664 }
2665 if (y < y0 || y >= y2) {
2666 return 0;
2667 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002668
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002669 // bounds check on X (not required. is it faster?)
2670#if 0
2671 if (pts[0].fX > x && pts[1].fX > x && pts[2].fX > x) {
2672 return 0;
2673 }
2674#endif
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002675
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002676 SkScalar roots[2];
2677 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
2678 2 * (pts[1].fY - pts[0].fY),
2679 pts[0].fY - y,
2680 roots);
2681 SkASSERT(n <= 1);
2682 SkScalar xt;
2683 if (0 == n) {
2684 SkScalar mid = SkScalarAve(y0, y2);
2685 // Need [0] and [2] if dir == 1
2686 // and [2] and [0] if dir == -1
2687 xt = y < mid ? pts[1 - dir].fX : pts[dir - 1].fX;
2688 } else {
2689 SkScalar t = roots[0];
2690 SkScalar C = pts[0].fX;
2691 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
2692 SkScalar B = 2 * (pts[1].fX - C);
2693 xt = SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C);
2694 }
2695 return xt < x ? dir : 0;
2696}
2697
2698static bool is_mono_quad(SkScalar y0, SkScalar y1, SkScalar y2) {
2699 // return SkScalarSignAsInt(y0 - y1) + SkScalarSignAsInt(y1 - y2) != 0;
2700 if (y0 == y1) {
2701 return true;
2702 }
2703 if (y0 < y1) {
2704 return y1 <= y2;
2705 } else {
2706 return y1 >= y2;
2707 }
2708}
2709
2710static int winding_quad(const SkPoint pts[], SkScalar x, SkScalar y) {
2711 SkPoint dst[5];
2712 int n = 0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002713
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002714 if (!is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY)) {
2715 n = SkChopQuadAtYExtrema(pts, dst);
2716 pts = dst;
2717 }
2718 int w = winding_mono_quad(pts, x, y);
2719 if (n > 0) {
2720 w += winding_mono_quad(&pts[2], x, y);
2721 }
2722 return w;
2723}
2724
2725static int winding_line(const SkPoint pts[], SkScalar x, SkScalar y) {
2726 SkScalar x0 = pts[0].fX;
2727 SkScalar y0 = pts[0].fY;
2728 SkScalar x1 = pts[1].fX;
2729 SkScalar y1 = pts[1].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002730
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002731 SkScalar dy = y1 - y0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002732
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002733 int dir = 1;
2734 if (y0 > y1) {
2735 SkTSwap(y0, y1);
2736 dir = -1;
2737 }
2738 if (y < y0 || y >= y1) {
2739 return 0;
2740 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002741
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002742 SkScalar cross = SkScalarMul(x1 - x0, y - pts[0].fY) -
2743 SkScalarMul(dy, x - pts[0].fX);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002744
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002745 if (SkScalarSignAsInt(cross) == dir) {
2746 dir = 0;
2747 }
2748 return dir;
2749}
2750
reed@google.com4db592c2013-10-30 17:39:43 +00002751static bool contains_inclusive(const SkRect& r, SkScalar x, SkScalar y) {
2752 return r.fLeft <= x && x <= r.fRight && r.fTop <= y && y <= r.fBottom;
2753}
2754
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002755bool SkPath::contains(SkScalar x, SkScalar y) const {
2756 bool isInverse = this->isInverseFillType();
2757 if (this->isEmpty()) {
2758 return isInverse;
2759 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002760
reed@google.com4db592c2013-10-30 17:39:43 +00002761 if (!contains_inclusive(this->getBounds(), x, y)) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002762 return isInverse;
2763 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002764
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002765 SkPath::Iter iter(*this, true);
2766 bool done = false;
2767 int w = 0;
2768 do {
2769 SkPoint pts[4];
2770 switch (iter.next(pts, false)) {
2771 case SkPath::kMove_Verb:
2772 case SkPath::kClose_Verb:
2773 break;
2774 case SkPath::kLine_Verb:
2775 w += winding_line(pts, x, y);
2776 break;
2777 case SkPath::kQuad_Verb:
2778 w += winding_quad(pts, x, y);
2779 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002780 case SkPath::kConic_Verb:
2781 SkASSERT(0);
2782 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002783 case SkPath::kCubic_Verb:
2784 w += winding_cubic(pts, x, y);
2785 break;
2786 case SkPath::kDone_Verb:
2787 done = true;
2788 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002789 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002790 } while (!done);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002791
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002792 switch (this->getFillType()) {
2793 case SkPath::kEvenOdd_FillType:
2794 case SkPath::kInverseEvenOdd_FillType:
2795 w &= 1;
2796 break;
reed@google.come9bb6232012-07-11 18:56:10 +00002797 default:
2798 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002799 }
2800 return SkToBool(w);
2801}