blob: 195424e7c0b79c9ba94a7aa1a5b415635f37e30c [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()
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000128 : fPathRef(SkPathRef::CreateEmpty())
bungeman@google.coma5809a32013-06-21 15:13:34 +0000129#ifdef SK_BUILD_FOR_ANDROID
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000130 , fSourcePath(NULL)
bungeman@google.coma5809a32013-06-21 15:13:34 +0000131#endif
132{
133 this->resetFields();
jvanverthb3eb6872014-10-24 07:12:51 -0700134 fIsVolatile = false;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000135}
136
137void SkPath::resetFields() {
138 //fPathRef is assumed to have been emptied by the caller.
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000139 fLastMoveToIndex = INITIAL_LASTMOVETOINDEX_VALUE;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000140 fFillType = kWinding_FillType;
reed@google.com04863fa2011-05-15 04:08:24 +0000141 fConvexity = kUnknown_Convexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000142 fDirection = kUnknown_Direction;
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000143
144 // We don't touch Android's fSourcePath. It's used to track texture garbage collection, so we
145 // don't want to muck with it if it's been set to something non-NULL.
reed@android.com6b82d1a2009-06-03 02:35:01 +0000146}
reed@android.com8a1c16f2008-12-17 15:59:43 +0000147
bungeman@google.coma5809a32013-06-21 15:13:34 +0000148SkPath::SkPath(const SkPath& that)
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000149 : fPathRef(SkRef(that.fPathRef.get())) {
bungeman@google.coma5809a32013-06-21 15:13:34 +0000150 this->copyFields(that);
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000151#ifdef SK_BUILD_FOR_ANDROID
mtklein@google.comcb8b0ee2013-08-15 21:14:51 +0000152 fSourcePath = that.fSourcePath;
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000153#endif
bungeman@google.coma5809a32013-06-21 15:13:34 +0000154 SkDEBUGCODE(that.validate();)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000155}
156
157SkPath::~SkPath() {
158 SkDEBUGCODE(this->validate();)
159}
160
bungeman@google.coma5809a32013-06-21 15:13:34 +0000161SkPath& SkPath::operator=(const SkPath& that) {
162 SkDEBUGCODE(that.validate();)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000163
bungeman@google.coma5809a32013-06-21 15:13:34 +0000164 if (this != &that) {
165 fPathRef.reset(SkRef(that.fPathRef.get()));
166 this->copyFields(that);
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000167#ifdef SK_BUILD_FOR_ANDROID
mtklein@google.comcb8b0ee2013-08-15 21:14:51 +0000168 fSourcePath = that.fSourcePath;
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000169#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000170 }
171 SkDEBUGCODE(this->validate();)
172 return *this;
173}
174
bungeman@google.coma5809a32013-06-21 15:13:34 +0000175void SkPath::copyFields(const SkPath& that) {
176 //fPathRef is assumed to have been set by the caller.
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000177 fLastMoveToIndex = that.fLastMoveToIndex;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000178 fFillType = that.fFillType;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000179 fConvexity = that.fConvexity;
180 fDirection = that.fDirection;
jvanverthb3eb6872014-10-24 07:12:51 -0700181 fIsVolatile = that.fIsVolatile;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000182}
183
djsollen@google.com9c1a9672013-08-09 13:49:13 +0000184bool operator==(const SkPath& a, const SkPath& b) {
reed@android.com6b82d1a2009-06-03 02:35:01 +0000185 // note: don't need to look at isConvex or bounds, since just comparing the
186 // raw data is sufficient.
reed@android.com3abec1d2009-03-02 05:36:20 +0000187 return &a == &b ||
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000188 (a.fFillType == b.fFillType && *a.fPathRef.get() == *b.fPathRef.get());
reed@android.com3abec1d2009-03-02 05:36:20 +0000189}
190
bungeman@google.coma5809a32013-06-21 15:13:34 +0000191void SkPath::swap(SkPath& that) {
192 SkASSERT(&that != NULL);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000193
bungeman@google.coma5809a32013-06-21 15:13:34 +0000194 if (this != &that) {
195 fPathRef.swap(&that.fPathRef);
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000196 SkTSwap<int>(fLastMoveToIndex, that.fLastMoveToIndex);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000197 SkTSwap<uint8_t>(fFillType, that.fFillType);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000198 SkTSwap<uint8_t>(fConvexity, that.fConvexity);
199 SkTSwap<uint8_t>(fDirection, that.fDirection);
jvanverthb3eb6872014-10-24 07:12:51 -0700200 SkTSwap<SkBool8>(fIsVolatile, that.fIsVolatile);
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000201#ifdef SK_BUILD_FOR_ANDROID
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000202 SkTSwap<const SkPath*>(fSourcePath, that.fSourcePath);
203#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000204 }
205}
206
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000207static inline bool check_edge_against_rect(const SkPoint& p0,
208 const SkPoint& p1,
209 const SkRect& rect,
210 SkPath::Direction dir) {
211 const SkPoint* edgeBegin;
212 SkVector v;
213 if (SkPath::kCW_Direction == dir) {
214 v = p1 - p0;
215 edgeBegin = &p0;
216 } else {
217 v = p0 - p1;
218 edgeBegin = &p1;
219 }
220 if (v.fX || v.fY) {
221 // check the cross product of v with the vec from edgeBegin to each rect corner
222 SkScalar yL = SkScalarMul(v.fY, rect.fLeft - edgeBegin->fX);
223 SkScalar xT = SkScalarMul(v.fX, rect.fTop - edgeBegin->fY);
224 SkScalar yR = SkScalarMul(v.fY, rect.fRight - edgeBegin->fX);
225 SkScalar xB = SkScalarMul(v.fX, rect.fBottom - edgeBegin->fY);
226 if ((xT < yL) || (xT < yR) || (xB < yL) || (xB < yR)) {
227 return false;
228 }
229 }
230 return true;
231}
232
233bool SkPath::conservativelyContainsRect(const SkRect& rect) const {
234 // This only handles non-degenerate convex paths currently.
235 if (kConvex_Convexity != this->getConvexity()) {
236 return false;
237 }
skia.committer@gmail.comcec8de62012-11-14 02:01:22 +0000238
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000239 Direction direction;
240 if (!this->cheapComputeDirection(&direction)) {
241 return false;
242 }
243
244 SkPoint firstPt;
245 SkPoint prevPt;
246 RawIter iter(*this);
247 SkPath::Verb verb;
248 SkPoint pts[4];
249 SkDEBUGCODE(int moveCnt = 0;)
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000250 SkDEBUGCODE(int segmentCount = 0;)
251 SkDEBUGCODE(int closeCount = 0;)
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000252
253 while ((verb = iter.next(pts)) != kDone_Verb) {
254 int nextPt = -1;
255 switch (verb) {
256 case kMove_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000257 SkASSERT(!segmentCount && !closeCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000258 SkDEBUGCODE(++moveCnt);
259 firstPt = prevPt = pts[0];
260 break;
261 case kLine_Verb:
262 nextPt = 1;
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000263 SkASSERT(moveCnt && !closeCount);
264 SkDEBUGCODE(++segmentCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000265 break;
266 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000267 case kConic_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000268 SkASSERT(moveCnt && !closeCount);
269 SkDEBUGCODE(++segmentCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000270 nextPt = 2;
271 break;
272 case kCubic_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000273 SkASSERT(moveCnt && !closeCount);
274 SkDEBUGCODE(++segmentCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000275 nextPt = 3;
276 break;
277 case kClose_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000278 SkDEBUGCODE(++closeCount;)
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000279 break;
280 default:
281 SkDEBUGFAIL("unknown verb");
282 }
283 if (-1 != nextPt) {
reed220f9262014-12-17 08:21:04 -0800284 if (SkPath::kConic_Verb == verb) {
285 SkConic orig;
286 orig.set(pts, iter.conicWeight());
287 SkPoint quadPts[5];
288 int count = orig.chopIntoQuadsPOW2(quadPts, 1);
289 SK_ALWAYSBREAK(2 == count);
290
291 if (!check_edge_against_rect(quadPts[0], quadPts[2], rect, direction)) {
292 return false;
293 }
294 if (!check_edge_against_rect(quadPts[2], quadPts[4], rect, direction)) {
295 return false;
296 }
297 } else {
298 if (!check_edge_against_rect(prevPt, pts[nextPt], rect, direction)) {
299 return false;
300 }
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000301 }
302 prevPt = pts[nextPt];
303 }
304 }
305
306 return check_edge_against_rect(prevPt, firstPt, rect, direction);
307}
308
robertphillips@google.com7101abe2013-10-29 22:45:37 +0000309uint32_t SkPath::getGenerationID() const {
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000310 uint32_t genID = fPathRef->genID();
311#ifdef SK_BUILD_FOR_ANDROID
312 SkASSERT((unsigned)fFillType < (1 << (32 - kPathRefGenIDBitCnt)));
313 genID |= static_cast<uint32_t>(fFillType) << kPathRefGenIDBitCnt;
314#endif
315 return genID;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000316}
djsollen@google.come63793a2012-03-21 15:39:03 +0000317
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000318#ifdef SK_BUILD_FOR_ANDROID
djsollen@google.come63793a2012-03-21 15:39:03 +0000319const SkPath* SkPath::getSourcePath() const {
320 return fSourcePath;
321}
322
323void SkPath::setSourcePath(const SkPath* path) {
324 fSourcePath = path;
325}
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000326#endif
327
reed@android.com8a1c16f2008-12-17 15:59:43 +0000328void SkPath::reset() {
329 SkDEBUGCODE(this->validate();)
330
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000331 fPathRef.reset(SkPathRef::CreateEmpty());
bungeman@google.coma5809a32013-06-21 15:13:34 +0000332 this->resetFields();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000333}
334
335void SkPath::rewind() {
336 SkDEBUGCODE(this->validate();)
337
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000338 SkPathRef::Rewind(&fPathRef);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000339 this->resetFields();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000340}
341
reed@google.com7e6c4d12012-05-10 14:05:43 +0000342bool SkPath::isLine(SkPoint line[2]) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000343 int verbCount = fPathRef->countVerbs();
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000344
commit-bot@chromium.orga62efcc2013-08-05 13:23:13 +0000345 if (2 == verbCount) {
346 SkASSERT(kMove_Verb == fPathRef->atVerb(0));
347 if (kLine_Verb == fPathRef->atVerb(1)) {
348 SkASSERT(2 == fPathRef->countPoints());
reed@google.com7e6c4d12012-05-10 14:05:43 +0000349 if (line) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000350 const SkPoint* pts = fPathRef->points();
reed@google.com7e6c4d12012-05-10 14:05:43 +0000351 line[0] = pts[0];
352 line[1] = pts[1];
353 }
354 return true;
355 }
356 }
357 return false;
358}
359
caryclark@google.comf1316942011-07-26 19:54:45 +0000360/*
361 Determines if path is a rect by keeping track of changes in direction
362 and looking for a loop either clockwise or counterclockwise.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000363
caryclark@google.comf1316942011-07-26 19:54:45 +0000364 The direction is computed such that:
365 0: vertical up
caryclark@google.comf68154a2012-11-21 15:18:06 +0000366 1: horizontal left
caryclark@google.comf1316942011-07-26 19:54:45 +0000367 2: vertical down
caryclark@google.comf68154a2012-11-21 15:18:06 +0000368 3: horizontal right
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000369
caryclark@google.comf1316942011-07-26 19:54:45 +0000370A rectangle cycles up/right/down/left or up/left/down/right.
371
372The test fails if:
373 The path is closed, and followed by a line.
374 A second move creates a new endpoint.
375 A diagonal line is parsed.
376 There's more than four changes of direction.
377 There's a discontinuity on the line (e.g., a move in the middle)
378 The line reverses direction.
caryclark@google.comf1316942011-07-26 19:54:45 +0000379 The path contains a quadratic or cubic.
380 The path contains fewer than four points.
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000381 *The rectangle doesn't complete a cycle.
382 *The final point isn't equal to the first point.
383
384 *These last two conditions we relax if we have a 3-edge path that would
385 form a rectangle if it were closed (as we do when we fill a path)
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000386
caryclark@google.comf1316942011-07-26 19:54:45 +0000387It's OK if the path has:
388 Several colinear line segments composing a rectangle side.
389 Single points on the rectangle side.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000390
caryclark@google.comf1316942011-07-26 19:54:45 +0000391The direction takes advantage of the corners found since opposite sides
392must travel in opposite directions.
393
394FIXME: Allow colinear quads and cubics to be treated like lines.
395FIXME: If the API passes fill-only, return true if the filled stroke
396 is a rectangle, though the caller failed to close the path.
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000397
398 first,last,next direction state-machine:
399 0x1 is set if the segment is horizontal
400 0x2 is set if the segment is moving to the right or down
401 thus:
402 two directions are opposites iff (dirA ^ dirB) == 0x2
403 two directions are perpendicular iff (dirA ^ dirB) == 0x1
skia.committer@gmail.comf5e67c12014-01-16 07:01:48 +0000404
caryclark@google.comf1316942011-07-26 19:54:45 +0000405 */
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000406static int rect_make_dir(SkScalar dx, SkScalar dy) {
407 return ((0 != dx) << 0) | ((dx > 0 || dy > 0) << 1);
408}
caryclark@google.comf68154a2012-11-21 15:18:06 +0000409bool SkPath::isRectContour(bool allowPartial, int* currVerb, const SkPoint** ptsPtr,
410 bool* isClosed, Direction* direction) const {
caryclark@google.comf1316942011-07-26 19:54:45 +0000411 int corners = 0;
412 SkPoint first, last;
caryclark@google.com56f233a2012-11-19 13:06:06 +0000413 const SkPoint* pts = *ptsPtr;
caryclark@google.combfe90372012-11-21 13:56:20 +0000414 const SkPoint* savePts = NULL;
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000415 first.set(0, 0);
416 last.set(0, 0);
417 int firstDirection = 0;
418 int lastDirection = 0;
419 int nextDirection = 0;
420 bool closedOrMoved = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000421 bool autoClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000422 int verbCnt = fPathRef->countVerbs();
caryclark@google.com56f233a2012-11-19 13:06:06 +0000423 while (*currVerb < verbCnt && (!allowPartial || !autoClose)) {
424 switch (fPathRef->atVerb(*currVerb)) {
caryclark@google.comf1316942011-07-26 19:54:45 +0000425 case kClose_Verb:
caryclark@google.com56f233a2012-11-19 13:06:06 +0000426 savePts = pts;
427 pts = *ptsPtr;
caryclark@google.comf1316942011-07-26 19:54:45 +0000428 autoClose = true;
429 case kLine_Verb: {
430 SkScalar left = last.fX;
431 SkScalar top = last.fY;
432 SkScalar right = pts->fX;
433 SkScalar bottom = pts->fY;
434 ++pts;
435 if (left != right && top != bottom) {
436 return false; // diagonal
437 }
438 if (left == right && top == bottom) {
439 break; // single point on side OK
440 }
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000441 nextDirection = rect_make_dir(right - left, bottom - top);
caryclark@google.comf1316942011-07-26 19:54:45 +0000442 if (0 == corners) {
443 firstDirection = nextDirection;
444 first = last;
445 last = pts[-1];
446 corners = 1;
447 closedOrMoved = false;
448 break;
449 }
450 if (closedOrMoved) {
451 return false; // closed followed by a line
452 }
caryclark@google.combfe90372012-11-21 13:56:20 +0000453 if (autoClose && nextDirection == firstDirection) {
454 break; // colinear with first
455 }
caryclark@google.comf1316942011-07-26 19:54:45 +0000456 closedOrMoved = autoClose;
457 if (lastDirection != nextDirection) {
458 if (++corners > 4) {
459 return false; // too many direction changes
460 }
461 }
462 last = pts[-1];
463 if (lastDirection == nextDirection) {
464 break; // colinear segment
465 }
466 // Possible values for corners are 2, 3, and 4.
467 // When corners == 3, nextDirection opposes firstDirection.
468 // Otherwise, nextDirection at corner 2 opposes corner 4.
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000469 int turn = firstDirection ^ (corners - 1);
caryclark@google.comf1316942011-07-26 19:54:45 +0000470 int directionCycle = 3 == corners ? 0 : nextDirection ^ turn;
471 if ((directionCycle ^ turn) != nextDirection) {
472 return false; // direction didn't follow cycle
473 }
474 break;
475 }
476 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000477 case kConic_Verb:
caryclark@google.comf1316942011-07-26 19:54:45 +0000478 case kCubic_Verb:
479 return false; // quadratic, cubic not allowed
480 case kMove_Verb:
481 last = *pts++;
482 closedOrMoved = true;
483 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000484 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000485 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000486 break;
caryclark@google.comf1316942011-07-26 19:54:45 +0000487 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000488 *currVerb += 1;
caryclark@google.comf1316942011-07-26 19:54:45 +0000489 lastDirection = nextDirection;
490 }
491 // Success if 4 corners and first point equals last
caryclark@google.combfe90372012-11-21 13:56:20 +0000492 bool result = 4 == corners && (first == last || autoClose);
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000493 if (!result) {
494 // check if we are just an incomplete rectangle, in which case we can
495 // return true, but not claim to be closed.
496 // e.g.
497 // 3 sided rectangle
498 // 4 sided but the last edge is not long enough to reach the start
499 //
500 SkScalar closeX = first.x() - last.x();
501 SkScalar closeY = first.y() - last.y();
502 if (closeX && closeY) {
503 return false; // we're diagonal, abort (can we ever reach this?)
504 }
505 int closeDirection = rect_make_dir(closeX, closeY);
506 // make sure the close-segment doesn't double-back on itself
507 if (3 == corners || (4 == corners && closeDirection == lastDirection)) {
508 result = true;
509 autoClose = false; // we are not closed
510 }
511 }
caryclark@google.combfe90372012-11-21 13:56:20 +0000512 if (savePts) {
513 *ptsPtr = savePts;
514 }
caryclark@google.comf68154a2012-11-21 15:18:06 +0000515 if (result && isClosed) {
516 *isClosed = autoClose;
517 }
518 if (result && direction) {
sugoi@google.com12b4e272012-12-06 20:13:11 +0000519 *direction = firstDirection == ((lastDirection + 1) & 3) ? kCCW_Direction : kCW_Direction;
caryclark@google.comf68154a2012-11-21 15:18:06 +0000520 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000521 return result;
522}
523
robertphillips4f662e62014-12-29 14:06:51 -0800524bool SkPath::isRect(SkRect* rect, bool* isClosed, Direction* direction) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000525 SkDEBUGCODE(this->validate();)
526 int currVerb = 0;
527 const SkPoint* pts = fPathRef->points();
robertphillipsfe7c4272014-12-29 11:36:39 -0800528 const SkPoint* first = pts;
robertphillips4f662e62014-12-29 14:06:51 -0800529 if (!this->isRectContour(false, &currVerb, &pts, isClosed, direction)) {
robertphillipsfe7c4272014-12-29 11:36:39 -0800530 return false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000531 }
robertphillipsfe7c4272014-12-29 11:36:39 -0800532 if (rect) {
robertphillips4f662e62014-12-29 14:06:51 -0800533 int32_t num = SkToS32(pts - first);
534 if (num) {
535 rect->set(first, num);
robertphillipsfe7c4272014-12-29 11:36:39 -0800536 } else {
537 // 'pts' isn't updated for open rects
538 *rect = this->getBounds();
539 }
540 }
541 return true;
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000542}
skia.committer@gmail.com020b25b2013-06-22 07:00:58 +0000543
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000544bool SkPath::isNestedRects(SkRect rects[2], Direction dirs[2]) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000545 SkDEBUGCODE(this->validate();)
546 int currVerb = 0;
547 const SkPoint* pts = fPathRef->points();
548 const SkPoint* first = pts;
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000549 Direction testDirs[2];
550 if (!isRectContour(true, &currVerb, &pts, NULL, &testDirs[0])) {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000551 return false;
552 }
553 const SkPoint* last = pts;
554 SkRect testRects[2];
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000555 if (isRectContour(false, &currVerb, &pts, NULL, &testDirs[1])) {
scroggo@google.com614f9e32013-05-09 18:05:32 +0000556 testRects[0].set(first, SkToS32(last - first));
557 testRects[1].set(last, SkToS32(pts - last));
caryclark@google.com56f233a2012-11-19 13:06:06 +0000558 if (testRects[0].contains(testRects[1])) {
559 if (rects) {
560 rects[0] = testRects[0];
561 rects[1] = testRects[1];
562 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000563 if (dirs) {
564 dirs[0] = testDirs[0];
565 dirs[1] = testDirs[1];
566 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000567 return true;
568 }
569 if (testRects[1].contains(testRects[0])) {
570 if (rects) {
571 rects[0] = testRects[1];
572 rects[1] = testRects[0];
573 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000574 if (dirs) {
575 dirs[0] = testDirs[1];
576 dirs[1] = testDirs[0];
577 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000578 return true;
579 }
580 }
581 return false;
582}
583
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000584int SkPath::countPoints() const {
585 return fPathRef->countPoints();
586}
587
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000588int SkPath::getPoints(SkPoint dst[], int max) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000589 SkDEBUGCODE(this->validate();)
590
591 SkASSERT(max >= 0);
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000592 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000593 int count = SkMin32(max, fPathRef->countPoints());
594 memcpy(dst, fPathRef->points(), count * sizeof(SkPoint));
595 return fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000596}
597
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000598SkPoint SkPath::getPoint(int index) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000599 if ((unsigned)index < (unsigned)fPathRef->countPoints()) {
600 return fPathRef->atPoint(index);
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000601 }
602 return SkPoint::Make(0, 0);
603}
604
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000605int SkPath::countVerbs() const {
606 return fPathRef->countVerbs();
607}
608
609static inline void copy_verbs_reverse(uint8_t* inorderDst,
610 const uint8_t* reversedSrc,
611 int count) {
612 for (int i = 0; i < count; ++i) {
613 inorderDst[i] = reversedSrc[~i];
614 }
615}
616
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000617int SkPath::getVerbs(uint8_t dst[], int max) const {
618 SkDEBUGCODE(this->validate();)
619
620 SkASSERT(max >= 0);
621 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000622 int count = SkMin32(max, fPathRef->countVerbs());
623 copy_verbs_reverse(dst, fPathRef->verbs(), count);
624 return fPathRef->countVerbs();
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000625}
626
reed@google.com294dd7b2011-10-11 11:58:32 +0000627bool SkPath::getLastPt(SkPoint* lastPt) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000628 SkDEBUGCODE(this->validate();)
629
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000630 int count = fPathRef->countPoints();
reed@google.com294dd7b2011-10-11 11:58:32 +0000631 if (count > 0) {
632 if (lastPt) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000633 *lastPt = fPathRef->atPoint(count - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000634 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000635 return true;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000636 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000637 if (lastPt) {
638 lastPt->set(0, 0);
639 }
640 return false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000641}
642
643void SkPath::setLastPt(SkScalar x, SkScalar y) {
644 SkDEBUGCODE(this->validate();)
645
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000646 int count = fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000647 if (count == 0) {
648 this->moveTo(x, y);
649 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000650 SkPathRef::Editor ed(&fPathRef);
651 ed.atPoint(count-1)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000652 }
653}
654
reed@google.com04863fa2011-05-15 04:08:24 +0000655void SkPath::setConvexity(Convexity c) {
656 if (fConvexity != c) {
657 fConvexity = c;
reed@google.com04863fa2011-05-15 04:08:24 +0000658 }
659}
660
reed@android.com8a1c16f2008-12-17 15:59:43 +0000661//////////////////////////////////////////////////////////////////////////////
662// Construction methods
663
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000664#define DIRTY_AFTER_EDIT \
665 do { \
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000666 fConvexity = kUnknown_Convexity; \
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000667 fDirection = kUnknown_Direction; \
reed@google.comb54455e2011-05-16 14:16:04 +0000668 } while (0)
669
reed@android.com8a1c16f2008-12-17 15:59:43 +0000670void SkPath::incReserve(U16CPU inc) {
671 SkDEBUGCODE(this->validate();)
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000672 SkPathRef::Editor(&fPathRef, inc, inc);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000673 SkDEBUGCODE(this->validate();)
674}
675
676void SkPath::moveTo(SkScalar x, SkScalar y) {
677 SkDEBUGCODE(this->validate();)
678
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000679 SkPathRef::Editor ed(&fPathRef);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000680
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000681 // remember our index
682 fLastMoveToIndex = fPathRef->countPoints();
683
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000684 ed.growForVerb(kMove_Verb)->set(x, y);
bsalomonb17c1292014-08-28 14:04:55 -0700685
686 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000687}
688
689void SkPath::rMoveTo(SkScalar x, SkScalar y) {
690 SkPoint pt;
691 this->getLastPt(&pt);
692 this->moveTo(pt.fX + x, pt.fY + y);
693}
694
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000695void SkPath::injectMoveToIfNeeded() {
696 if (fLastMoveToIndex < 0) {
697 SkScalar x, y;
698 if (fPathRef->countVerbs() == 0) {
699 x = y = 0;
700 } else {
701 const SkPoint& pt = fPathRef->atPoint(~fLastMoveToIndex);
702 x = pt.fX;
703 y = pt.fY;
704 }
705 this->moveTo(x, y);
706 }
707}
708
reed@android.com8a1c16f2008-12-17 15:59:43 +0000709void SkPath::lineTo(SkScalar x, SkScalar y) {
710 SkDEBUGCODE(this->validate();)
711
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000712 this->injectMoveToIfNeeded();
713
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000714 SkPathRef::Editor ed(&fPathRef);
715 ed.growForVerb(kLine_Verb)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000716
reed@google.comb54455e2011-05-16 14:16:04 +0000717 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000718}
719
720void SkPath::rLineTo(SkScalar x, SkScalar y) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000721 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000722 SkPoint pt;
723 this->getLastPt(&pt);
724 this->lineTo(pt.fX + x, pt.fY + y);
725}
726
727void SkPath::quadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
728 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000729
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000730 this->injectMoveToIfNeeded();
731
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000732 SkPathRef::Editor ed(&fPathRef);
733 SkPoint* pts = ed.growForVerb(kQuad_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000734 pts[0].set(x1, y1);
735 pts[1].set(x2, y2);
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000736
reed@google.comb54455e2011-05-16 14:16:04 +0000737 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000738}
739
740void SkPath::rQuadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000741 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000742 SkPoint pt;
743 this->getLastPt(&pt);
744 this->quadTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2);
745}
746
reed@google.com277c3f82013-05-31 15:17:50 +0000747void SkPath::conicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
748 SkScalar w) {
749 // check for <= 0 or NaN with this test
750 if (!(w > 0)) {
751 this->lineTo(x2, y2);
752 } else if (!SkScalarIsFinite(w)) {
753 this->lineTo(x1, y1);
754 this->lineTo(x2, y2);
755 } else if (SK_Scalar1 == w) {
756 this->quadTo(x1, y1, x2, y2);
757 } else {
758 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000759
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000760 this->injectMoveToIfNeeded();
761
reed@google.com277c3f82013-05-31 15:17:50 +0000762 SkPathRef::Editor ed(&fPathRef);
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000763 SkPoint* pts = ed.growForVerb(kConic_Verb, w);
reed@google.com277c3f82013-05-31 15:17:50 +0000764 pts[0].set(x1, y1);
765 pts[1].set(x2, y2);
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000766
reed@google.com277c3f82013-05-31 15:17:50 +0000767 DIRTY_AFTER_EDIT;
768 }
769}
770
771void SkPath::rConicTo(SkScalar dx1, SkScalar dy1, SkScalar dx2, SkScalar dy2,
772 SkScalar w) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000773 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@google.com277c3f82013-05-31 15:17:50 +0000774 SkPoint pt;
775 this->getLastPt(&pt);
776 this->conicTo(pt.fX + dx1, pt.fY + dy1, pt.fX + dx2, pt.fY + dy2, w);
777}
778
reed@android.com8a1c16f2008-12-17 15:59:43 +0000779void SkPath::cubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
780 SkScalar x3, SkScalar y3) {
781 SkDEBUGCODE(this->validate();)
782
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000783 this->injectMoveToIfNeeded();
784
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000785 SkPathRef::Editor ed(&fPathRef);
786 SkPoint* pts = ed.growForVerb(kCubic_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000787 pts[0].set(x1, y1);
788 pts[1].set(x2, y2);
789 pts[2].set(x3, y3);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000790
reed@google.comb54455e2011-05-16 14:16:04 +0000791 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000792}
793
794void SkPath::rCubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
795 SkScalar x3, SkScalar y3) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000796 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000797 SkPoint pt;
798 this->getLastPt(&pt);
799 this->cubicTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2,
800 pt.fX + x3, pt.fY + y3);
801}
802
803void SkPath::close() {
804 SkDEBUGCODE(this->validate();)
805
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000806 int count = fPathRef->countVerbs();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000807 if (count > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000808 switch (fPathRef->atVerb(count - 1)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000809 case kLine_Verb:
810 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000811 case kConic_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +0000812 case kCubic_Verb:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000813 case kMove_Verb: {
814 SkPathRef::Editor ed(&fPathRef);
815 ed.growForVerb(kClose_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000816 break;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000817 }
reed@google.com277c3f82013-05-31 15:17:50 +0000818 case kClose_Verb:
reed@google.comfa2f2a42013-05-30 15:29:48 +0000819 // don't add a close if it's the first verb or a repeat
reed@google.com7950a9e2013-05-30 14:57:55 +0000820 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000821 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000822 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000823 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000824 }
825 }
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000826
827 // signal that we need a moveTo to follow us (unless we're done)
828#if 0
829 if (fLastMoveToIndex >= 0) {
830 fLastMoveToIndex = ~fLastMoveToIndex;
831 }
832#else
833 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
834#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000835}
836
837///////////////////////////////////////////////////////////////////////////////
reed@google.comabf15c12011-01-18 20:35:51 +0000838
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000839static void assert_known_direction(int dir) {
840 SkASSERT(SkPath::kCW_Direction == dir || SkPath::kCCW_Direction == dir);
841}
842
reed@android.com8a1c16f2008-12-17 15:59:43 +0000843void SkPath::addRect(const SkRect& rect, Direction dir) {
844 this->addRect(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, dir);
845}
846
847void SkPath::addRect(SkScalar left, SkScalar top, SkScalar right,
848 SkScalar bottom, Direction dir) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000849 assert_known_direction(dir);
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000850 fDirection = this->hasOnlyMoveTos() ? dir : kUnknown_Direction;
851 SkAutoDisableDirectionCheck addc(this);
852
reed@android.com8a1c16f2008-12-17 15:59:43 +0000853 SkAutoPathBoundsUpdate apbu(this, left, top, right, bottom);
854
855 this->incReserve(5);
856
857 this->moveTo(left, top);
858 if (dir == kCCW_Direction) {
859 this->lineTo(left, bottom);
860 this->lineTo(right, bottom);
861 this->lineTo(right, top);
862 } else {
863 this->lineTo(right, top);
864 this->lineTo(right, bottom);
865 this->lineTo(left, bottom);
866 }
867 this->close();
868}
869
reed@google.com744faba2012-05-29 19:54:52 +0000870void SkPath::addPoly(const SkPoint pts[], int count, bool close) {
871 SkDEBUGCODE(this->validate();)
872 if (count <= 0) {
873 return;
874 }
875
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000876 fLastMoveToIndex = fPathRef->countPoints();
877
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000878 // +close makes room for the extra kClose_Verb
879 SkPathRef::Editor ed(&fPathRef, count+close, count);
880
881 ed.growForVerb(kMove_Verb)->set(pts[0].fX, pts[0].fY);
reed@google.com744faba2012-05-29 19:54:52 +0000882 if (count > 1) {
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000883 SkPoint* p = ed.growForRepeatedVerb(kLine_Verb, count - 1);
884 memcpy(p, &pts[1], (count-1) * sizeof(SkPoint));
reed@google.com744faba2012-05-29 19:54:52 +0000885 }
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000886
reed@google.com744faba2012-05-29 19:54:52 +0000887 if (close) {
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000888 ed.growForVerb(kClose_Verb);
reed@google.com744faba2012-05-29 19:54:52 +0000889 }
890
reed@google.com744faba2012-05-29 19:54:52 +0000891 DIRTY_AFTER_EDIT;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000892 SkDEBUGCODE(this->validate();)
reed@google.com744faba2012-05-29 19:54:52 +0000893}
894
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000895#include "SkGeometry.h"
896
reedf90ea012015-01-29 12:03:58 -0800897static bool arc_is_lone_point(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
898 SkPoint* pt) {
899 if (0 == sweepAngle && (0 == startAngle || SkIntToScalar(360) == startAngle)) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000900 // Chrome uses this path to move into and out of ovals. If not
901 // treated as a special case the moves can distort the oval's
902 // bounding box (and break the circle special case).
reedf90ea012015-01-29 12:03:58 -0800903 pt->set(oval.fRight, oval.centerY());
904 return true;
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000905 } else if (0 == oval.width() && 0 == oval.height()) {
906 // Chrome will sometimes create 0 radius round rects. Having degenerate
907 // quad segments in the path prevents the path from being recognized as
908 // a rect.
909 // TODO: optimizing the case where only one of width or height is zero
910 // should also be considered. This case, however, doesn't seem to be
911 // as common as the single point case.
reedf90ea012015-01-29 12:03:58 -0800912 pt->set(oval.fRight, oval.fTop);
913 return true;
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000914 }
reedf90ea012015-01-29 12:03:58 -0800915 return false;
916}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000917
reedf90ea012015-01-29 12:03:58 -0800918static int build_arc_points(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
919 SkPoint pts[kSkBuildQuadArcStorage]) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000920 SkVector start, stop;
921
922 start.fY = SkScalarSinCos(SkDegreesToRadians(startAngle), &start.fX);
923 stop.fY = SkScalarSinCos(SkDegreesToRadians(startAngle + sweepAngle),
924 &stop.fX);
925
926 /* If the sweep angle is nearly (but less than) 360, then due to precision
927 loss in radians-conversion and/or sin/cos, we may end up with coincident
928 vectors, which will fool SkBuildQuadArc into doing nothing (bad) instead
929 of drawing a nearly complete circle (good).
930 e.g. canvas.drawArc(0, 359.99, ...)
931 -vs- canvas.drawArc(0, 359.9, ...)
932 We try to detect this edge case, and tweak the stop vector
933 */
934 if (start == stop) {
935 SkScalar sw = SkScalarAbs(sweepAngle);
936 if (sw < SkIntToScalar(360) && sw > SkIntToScalar(359)) {
937 SkScalar stopRad = SkDegreesToRadians(startAngle + sweepAngle);
938 // make a guess at a tiny angle (in radians) to tweak by
939 SkScalar deltaRad = SkScalarCopySign(SK_Scalar1/512, sweepAngle);
940 // not sure how much will be enough, so we use a loop
941 do {
942 stopRad -= deltaRad;
943 stop.fY = SkScalarSinCos(stopRad, &stop.fX);
944 } while (start == stop);
945 }
946 }
947
948 SkMatrix matrix;
949
950 matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height()));
951 matrix.postTranslate(oval.centerX(), oval.centerY());
952
953 return SkBuildQuadArc(start, stop,
954 sweepAngle > 0 ? kCW_SkRotationDirection :
955 kCCW_SkRotationDirection,
956 &matrix, pts);
957}
958
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +0000959void SkPath::addRoundRect(const SkRect& rect, const SkScalar radii[],
reed@android.com8a1c16f2008-12-17 15:59:43 +0000960 Direction dir) {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +0000961 SkRRect rrect;
962 rrect.setRectRadii(rect, (const SkVector*) radii);
963 this->addRRect(rrect, dir);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000964}
965
reed1b28a3a2014-12-17 14:42:09 -0800966#ifdef SK_SUPPORT_LEGACY_ADDRRECT
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +0000967/* The inline clockwise and counterclockwise round rect quad approximations
968 make it easier to see the symmetry patterns used by add corner quads.
969Clockwise corner value
970 path->lineTo(rect.fLeft, rect.fTop + ry); 0 upper left
971 path->quadTo(rect.fLeft, rect.fTop + offPtY,
972 rect.fLeft + midPtX, rect.fTop + midPtY);
973 path->quadTo(rect.fLeft + offPtX, rect.fTop,
974 rect.fLeft + rx, rect.fTop);
975
976 path->lineTo(rect.fRight - rx, rect.fTop); 1 upper right
977 path->quadTo(rect.fRight - offPtX, rect.fTop,
978 rect.fRight - midPtX, rect.fTop + midPtY);
979 path->quadTo(rect.fRight, rect.fTop + offPtY,
980 rect.fRight, rect.fTop + ry);
981
982 path->lineTo(rect.fRight, rect.fBottom - ry); 2 lower right
983 path->quadTo(rect.fRight, rect.fBottom - offPtY,
984 rect.fRight - midPtX, rect.fBottom - midPtY);
985 path->quadTo(rect.fRight - offPtX, rect.fBottom,
986 rect.fRight - rx, rect.fBottom);
987
988 path->lineTo(rect.fLeft + rx, rect.fBottom); 3 lower left
989 path->quadTo(rect.fLeft + offPtX, rect.fBottom,
990 rect.fLeft + midPtX, rect.fBottom - midPtY);
991 path->quadTo(rect.fLeft, rect.fBottom - offPtY,
992 rect.fLeft, rect.fBottom - ry);
993
994Counterclockwise
995 path->lineTo(rect.fLeft, rect.fBottom - ry); 3 lower left
996 path->quadTo(rect.fLeft, rect.fBottom - offPtY,
997 rect.fLeft + midPtX, rect.fBottom - midPtY);
998 path->quadTo(rect.fLeft + offPtX, rect.fBottom,
999 rect.fLeft + rx, rect.fBottom);
1000
1001 path->lineTo(rect.fRight - rx, rect.fBottom); 2 lower right
1002 path->quadTo(rect.fRight - offPtX, rect.fBottom,
1003 rect.fRight - midPtX, rect.fBottom - midPtY);
1004 path->quadTo(rect.fRight, rect.fBottom - offPtY,
1005 rect.fRight, rect.fBottom - ry);
1006
1007 path->lineTo(rect.fRight, rect.fTop + ry); 1 upper right
1008 path->quadTo(rect.fRight, rect.fTop + offPtY,
1009 rect.fRight - midPtX, rect.fTop + midPtY);
1010 path->quadTo(rect.fRight - offPtX, rect.fTop,
1011 rect.fRight - rx, rect.fTop);
1012
1013 path->lineTo(rect.fLeft + rx, rect.fTop); 0 upper left
1014 path->quadTo(rect.fLeft + offPtX, rect.fTop,
1015 rect.fLeft + midPtX, rect.fTop + midPtY);
1016 path->quadTo(rect.fLeft, rect.fTop + offPtY,
1017 rect.fLeft, rect.fTop + ry);
1018*/
1019static void add_corner_quads(SkPath* path, const SkRRect& rrect,
1020 SkRRect::Corner corner, SkPath::Direction dir) {
1021 const SkRect& rect = rrect.rect();
1022 const SkVector& radii = rrect.radii(corner);
1023 SkScalar rx = radii.fX;
1024 SkScalar ry = radii.fY;
1025 // The mid point of the quadratic arc approximation is half way between the two
1026 // control points.
caryclark@google.com2e1b99e2013-11-08 18:05:02 +00001027 const SkScalar mid = 1 - (SK_Scalar1 + SK_ScalarTanPIOver8) / 2;
1028 SkScalar midPtX = rx * mid;
1029 SkScalar midPtY = ry * mid;
1030 const SkScalar control = 1 - SK_ScalarTanPIOver8;
1031 SkScalar offPtX = rx * control;
1032 SkScalar offPtY = ry * control;
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001033 static const int kCornerPts = 5;
1034 SkScalar xOff[kCornerPts];
1035 SkScalar yOff[kCornerPts];
1036
1037 if ((corner & 1) == (dir == SkPath::kCCW_Direction)) { // corners always alternate direction
1038 SkASSERT(dir == SkPath::kCCW_Direction
1039 ? corner == SkRRect::kLowerLeft_Corner || corner == SkRRect::kUpperRight_Corner
1040 : corner == SkRRect::kUpperLeft_Corner || corner == SkRRect::kLowerRight_Corner);
1041 xOff[0] = xOff[1] = 0;
1042 xOff[2] = midPtX;
1043 xOff[3] = offPtX;
1044 xOff[4] = rx;
1045 yOff[0] = ry;
1046 yOff[1] = offPtY;
1047 yOff[2] = midPtY;
1048 yOff[3] = yOff[4] = 0;
1049 } else {
1050 xOff[0] = rx;
1051 xOff[1] = offPtX;
1052 xOff[2] = midPtX;
1053 xOff[3] = xOff[4] = 0;
1054 yOff[0] = yOff[1] = 0;
1055 yOff[2] = midPtY;
1056 yOff[3] = offPtY;
1057 yOff[4] = ry;
1058 }
1059 if ((corner - 1) & 2) {
1060 SkASSERT(corner == SkRRect::kLowerLeft_Corner || corner == SkRRect::kUpperLeft_Corner);
1061 for (int i = 0; i < kCornerPts; ++i) {
1062 xOff[i] = rect.fLeft + xOff[i];
1063 }
1064 } else {
1065 SkASSERT(corner == SkRRect::kLowerRight_Corner || corner == SkRRect::kUpperRight_Corner);
1066 for (int i = 0; i < kCornerPts; ++i) {
1067 xOff[i] = rect.fRight - xOff[i];
1068 }
1069 }
1070 if (corner < SkRRect::kLowerRight_Corner) {
1071 for (int i = 0; i < kCornerPts; ++i) {
1072 yOff[i] = rect.fTop + yOff[i];
1073 }
1074 } else {
1075 for (int i = 0; i < kCornerPts; ++i) {
1076 yOff[i] = rect.fBottom - yOff[i];
1077 }
1078 }
1079
1080 SkPoint lastPt;
1081 SkAssertResult(path->getLastPt(&lastPt));
1082 if (lastPt.fX != xOff[0] || lastPt.fY != yOff[0]) {
1083 path->lineTo(xOff[0], yOff[0]);
1084 }
1085 if (rx || ry) {
1086 path->quadTo(xOff[1], yOff[1], xOff[2], yOff[2]);
1087 path->quadTo(xOff[3], yOff[3], xOff[4], yOff[4]);
1088 } else {
1089 path->lineTo(xOff[2], yOff[2]);
1090 path->lineTo(xOff[4], yOff[4]);
1091 }
1092}
reed1b28a3a2014-12-17 14:42:09 -08001093#endif
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001094
reed@google.com4ed0fb72012-12-12 20:48:18 +00001095void SkPath::addRRect(const SkRRect& rrect, Direction dir) {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001096 assert_known_direction(dir);
1097
1098 if (rrect.isEmpty()) {
1099 return;
1100 }
1101
reed@google.com4ed0fb72012-12-12 20:48:18 +00001102 const SkRect& bounds = rrect.getBounds();
1103
1104 if (rrect.isRect()) {
1105 this->addRect(bounds, dir);
1106 } else if (rrect.isOval()) {
1107 this->addOval(bounds, dir);
reed@google.com4ed0fb72012-12-12 20:48:18 +00001108 } else {
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001109 fDirection = this->hasOnlyMoveTos() ? dir : kUnknown_Direction;
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001110
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001111 SkAutoPathBoundsUpdate apbu(this, bounds);
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001112 SkAutoDisableDirectionCheck addc(this);
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001113
reed1b28a3a2014-12-17 14:42:09 -08001114#ifdef SK_SUPPORT_LEGACY_ADDRRECT
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001115 this->incReserve(21);
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001116 if (kCW_Direction == dir) {
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001117 this->moveTo(bounds.fLeft,
1118 bounds.fBottom - rrect.fRadii[SkRRect::kLowerLeft_Corner].fY);
1119 add_corner_quads(this, rrect, SkRRect::kUpperLeft_Corner, dir);
1120 add_corner_quads(this, rrect, SkRRect::kUpperRight_Corner, dir);
1121 add_corner_quads(this, rrect, SkRRect::kLowerRight_Corner, dir);
1122 add_corner_quads(this, rrect, SkRRect::kLowerLeft_Corner, dir);
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001123 } else {
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001124 this->moveTo(bounds.fLeft,
1125 bounds.fTop + rrect.fRadii[SkRRect::kUpperLeft_Corner].fY);
1126 add_corner_quads(this, rrect, SkRRect::kLowerLeft_Corner, dir);
1127 add_corner_quads(this, rrect, SkRRect::kLowerRight_Corner, dir);
1128 add_corner_quads(this, rrect, SkRRect::kUpperRight_Corner, dir);
1129 add_corner_quads(this, rrect, SkRRect::kUpperLeft_Corner, dir);
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001130 }
reed1b28a3a2014-12-17 14:42:09 -08001131#else
1132 const SkScalar L = bounds.fLeft;
1133 const SkScalar T = bounds.fTop;
1134 const SkScalar R = bounds.fRight;
1135 const SkScalar B = bounds.fBottom;
1136 const SkScalar W = SK_ScalarRoot2Over2;
1137
1138 this->incReserve(13);
1139 if (kCW_Direction == dir) {
1140 this->moveTo(L, B - rrect.fRadii[SkRRect::kLowerLeft_Corner].fY);
1141
1142 this->lineTo(L, T + rrect.fRadii[SkRRect::kUpperLeft_Corner].fY);
1143 this->conicTo(L, T, L + rrect.fRadii[SkRRect::kUpperLeft_Corner].fX, T, W);
1144
1145 this->lineTo(R - rrect.fRadii[SkRRect::kUpperRight_Corner].fX, T);
1146 this->conicTo(R, T, R, T + rrect.fRadii[SkRRect::kUpperRight_Corner].fY, W);
1147
1148 this->lineTo(R, B - rrect.fRadii[SkRRect::kLowerRight_Corner].fY);
1149 this->conicTo(R, B, R - rrect.fRadii[SkRRect::kLowerRight_Corner].fX, B, W);
1150
1151 this->lineTo(L + rrect.fRadii[SkRRect::kLowerLeft_Corner].fX, B);
1152 this->conicTo(L, B, L, B - rrect.fRadii[SkRRect::kLowerLeft_Corner].fY, W);
1153 } else {
1154 this->moveTo(L, T + rrect.fRadii[SkRRect::kUpperLeft_Corner].fY);
1155
1156 this->lineTo(L, B - rrect.fRadii[SkRRect::kLowerLeft_Corner].fY);
1157 this->conicTo(L, B, L + rrect.fRadii[SkRRect::kLowerLeft_Corner].fX, B, W);
1158
1159 this->lineTo(R - rrect.fRadii[SkRRect::kLowerRight_Corner].fX, B);
1160 this->conicTo(R, B, R, B - rrect.fRadii[SkRRect::kLowerRight_Corner].fY, W);
1161
1162 this->lineTo(R, T + rrect.fRadii[SkRRect::kUpperRight_Corner].fY);
1163 this->conicTo(R, T, R - rrect.fRadii[SkRRect::kUpperRight_Corner].fX, T, W);
1164
1165 this->lineTo(L + rrect.fRadii[SkRRect::kUpperLeft_Corner].fX, T);
1166 this->conicTo(L, T, L, T + rrect.fRadii[SkRRect::kUpperLeft_Corner].fY, W);
1167 }
1168#endif
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001169 this->close();
reed@google.com4ed0fb72012-12-12 20:48:18 +00001170 }
reed5bcbe912014-12-15 12:28:33 -08001171 SkDEBUGCODE(fPathRef->validate();)
reed@google.com4ed0fb72012-12-12 20:48:18 +00001172}
1173
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001174bool SkPath::hasOnlyMoveTos() const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001175 int count = fPathRef->countVerbs();
1176 const uint8_t* verbs = const_cast<const SkPathRef*>(fPathRef.get())->verbsMemBegin();
1177 for (int i = 0; i < count; ++i) {
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001178 if (*verbs == kLine_Verb ||
1179 *verbs == kQuad_Verb ||
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001180 *verbs == kConic_Verb ||
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001181 *verbs == kCubic_Verb) {
1182 return false;
1183 }
1184 ++verbs;
1185 }
1186 return true;
1187}
1188
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001189void SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
1190 Direction dir) {
1191 assert_known_direction(dir);
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001192
humper@google.com75e3ca12013-04-08 21:44:11 +00001193 if (rx < 0 || ry < 0) {
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001194 SkErrorInternals::SetError( kInvalidArgument_SkError,
humper@google.com75e3ca12013-04-08 21:44:11 +00001195 "I got %f and %f as radii to SkPath::AddRoundRect, "
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001196 "but negative radii are not allowed.",
humper@google.com75e3ca12013-04-08 21:44:11 +00001197 SkScalarToDouble(rx), SkScalarToDouble(ry) );
1198 return;
1199 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001200
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001201 SkRRect rrect;
1202 rrect.setRectXY(rect, rx, ry);
1203 this->addRRect(rrect, dir);
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001204}
1205
reed@android.com8a1c16f2008-12-17 15:59:43 +00001206void SkPath::addOval(const SkRect& oval, Direction dir) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +00001207 assert_known_direction(dir);
1208
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001209 /* If addOval() is called after previous moveTo(),
1210 this path is still marked as an oval. This is used to
1211 fit into WebKit's calling sequences.
1212 We can't simply check isEmpty() in this case, as additional
1213 moveTo() would mark the path non empty.
1214 */
robertphillips@google.com466310d2013-12-03 16:43:54 +00001215 bool isOval = hasOnlyMoveTos();
1216 if (isOval) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001217 fDirection = dir;
1218 } else {
1219 fDirection = kUnknown_Direction;
1220 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001221
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001222 SkAutoDisableDirectionCheck addc(this);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001223
reed@android.com8a1c16f2008-12-17 15:59:43 +00001224 SkAutoPathBoundsUpdate apbu(this, oval);
1225
reed220f9262014-12-17 08:21:04 -08001226#ifdef SK_SUPPORT_LEGACY_ADDOVAL
reed@android.com8a1c16f2008-12-17 15:59:43 +00001227 SkScalar cx = oval.centerX();
1228 SkScalar cy = oval.centerY();
1229 SkScalar rx = SkScalarHalf(oval.width());
1230 SkScalar ry = SkScalarHalf(oval.height());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001231
reed@android.com8a1c16f2008-12-17 15:59:43 +00001232 SkScalar sx = SkScalarMul(rx, SK_ScalarTanPIOver8);
1233 SkScalar sy = SkScalarMul(ry, SK_ScalarTanPIOver8);
1234 SkScalar mx = SkScalarMul(rx, SK_ScalarRoot2Over2);
1235 SkScalar my = SkScalarMul(ry, SK_ScalarRoot2Over2);
1236
1237 /*
reed220f9262014-12-17 08:21:04 -08001238 To handle imprecision in computing the center and radii, we revert to
1239 the provided bounds when we can (i.e. use oval.fLeft instead of cx-rx)
1240 to ensure that we don't exceed the oval's bounds *ever*, since we want
1241 to use oval for our fast-bounds, rather than have to recompute it.
1242 */
reed@android.com8a1c16f2008-12-17 15:59:43 +00001243 const SkScalar L = oval.fLeft; // cx - rx
1244 const SkScalar T = oval.fTop; // cy - ry
1245 const SkScalar R = oval.fRight; // cx + rx
1246 const SkScalar B = oval.fBottom; // cy + ry
1247
1248 this->incReserve(17); // 8 quads + close
1249 this->moveTo(R, cy);
1250 if (dir == kCCW_Direction) {
1251 this->quadTo( R, cy - sy, cx + mx, cy - my);
1252 this->quadTo(cx + sx, T, cx , T);
1253 this->quadTo(cx - sx, T, cx - mx, cy - my);
1254 this->quadTo( L, cy - sy, L, cy );
1255 this->quadTo( L, cy + sy, cx - mx, cy + my);
1256 this->quadTo(cx - sx, B, cx , B);
1257 this->quadTo(cx + sx, B, cx + mx, cy + my);
1258 this->quadTo( R, cy + sy, R, cy );
1259 } else {
1260 this->quadTo( R, cy + sy, cx + mx, cy + my);
1261 this->quadTo(cx + sx, B, cx , B);
1262 this->quadTo(cx - sx, B, cx - mx, cy + my);
1263 this->quadTo( L, cy + sy, L, cy );
1264 this->quadTo( L, cy - sy, cx - mx, cy - my);
1265 this->quadTo(cx - sx, T, cx , T);
1266 this->quadTo(cx + sx, T, cx + mx, cy - my);
1267 this->quadTo( R, cy - sy, R, cy );
1268 }
reed220f9262014-12-17 08:21:04 -08001269#else
1270 const SkScalar L = oval.fLeft;
1271 const SkScalar T = oval.fTop;
1272 const SkScalar R = oval.fRight;
1273 const SkScalar B = oval.fBottom;
1274 const SkScalar cx = oval.centerX();
1275 const SkScalar cy = oval.centerY();
1276 const SkScalar weight = SK_ScalarRoot2Over2;
1277
1278 this->incReserve(9); // move + 4 conics
1279 this->moveTo(R, cy);
1280 if (dir == kCCW_Direction) {
1281 this->conicTo(R, T, cx, T, weight);
1282 this->conicTo(L, T, L, cy, weight);
1283 this->conicTo(L, B, cx, B, weight);
1284 this->conicTo(R, B, R, cy, weight);
1285 } else {
1286 this->conicTo(R, B, cx, B, weight);
1287 this->conicTo(L, B, L, cy, weight);
1288 this->conicTo(L, T, cx, T, weight);
1289 this->conicTo(R, T, R, cy, weight);
1290 }
1291#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +00001292 this->close();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001293
robertphillips@google.com466310d2013-12-03 16:43:54 +00001294 SkPathRef::Editor ed(&fPathRef);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001295
robertphillips@google.com466310d2013-12-03 16:43:54 +00001296 ed.setIsOval(isOval);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001297}
1298
reed@android.com8a1c16f2008-12-17 15:59:43 +00001299void SkPath::addCircle(SkScalar x, SkScalar y, SkScalar r, Direction dir) {
1300 if (r > 0) {
1301 SkRect rect;
1302 rect.set(x - r, y - r, x + r, y + r);
1303 this->addOval(rect, dir);
1304 }
1305}
1306
reed@android.com8a1c16f2008-12-17 15:59:43 +00001307void SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
1308 bool forceMoveTo) {
1309 if (oval.width() < 0 || oval.height() < 0) {
1310 return;
1311 }
1312
reedf90ea012015-01-29 12:03:58 -08001313 if (fPathRef->countVerbs() == 0) {
1314 forceMoveTo = true;
1315 }
1316
1317 SkPoint lonePt;
1318 if (arc_is_lone_point(oval, startAngle, sweepAngle, &lonePt)) {
1319 forceMoveTo ? this->moveTo(lonePt) : this->lineTo(lonePt);
1320 return;
1321 }
1322
reed@android.com8a1c16f2008-12-17 15:59:43 +00001323 SkPoint pts[kSkBuildQuadArcStorage];
1324 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
1325 SkASSERT((count & 1) == 1);
1326
reed@android.com8a1c16f2008-12-17 15:59:43 +00001327 this->incReserve(count);
1328 forceMoveTo ? this->moveTo(pts[0]) : this->lineTo(pts[0]);
1329 for (int i = 1; i < count; i += 2) {
1330 this->quadTo(pts[i], pts[i+1]);
1331 }
1332}
1333
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001334void SkPath::addArc(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001335 if (oval.isEmpty() || 0 == sweepAngle) {
1336 return;
1337 }
1338
1339 const SkScalar kFullCircleAngle = SkIntToScalar(360);
1340
1341 if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
1342 this->addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction);
reedc7789042015-01-29 12:59:11 -08001343 } else {
1344 this->arcTo(oval, startAngle, sweepAngle, true);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001345 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001346}
1347
1348/*
1349 Need to handle the case when the angle is sharp, and our computed end-points
1350 for the arc go behind pt1 and/or p2...
1351*/
reedc7789042015-01-29 12:59:11 -08001352void SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2, SkScalar radius) {
reeda8b326c2014-12-09 11:50:32 -08001353 if (radius == 0) {
1354 this->lineTo(x1, y1);
1355 return;
1356 }
1357
1358 SkVector before, after;
reed@google.comabf15c12011-01-18 20:35:51 +00001359
reed@android.com8a1c16f2008-12-17 15:59:43 +00001360 // need to know our prev pt so we can construct tangent vectors
1361 {
1362 SkPoint start;
1363 this->getLastPt(&start);
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001364 // Handle degenerate cases by adding a line to the first point and
1365 // bailing out.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001366 before.setNormalize(x1 - start.fX, y1 - start.fY);
1367 after.setNormalize(x2 - x1, y2 - y1);
1368 }
reed@google.comabf15c12011-01-18 20:35:51 +00001369
reed@android.com8a1c16f2008-12-17 15:59:43 +00001370 SkScalar cosh = SkPoint::DotProduct(before, after);
1371 SkScalar sinh = SkPoint::CrossProduct(before, after);
1372
1373 if (SkScalarNearlyZero(sinh)) { // angle is too tight
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001374 this->lineTo(x1, y1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001375 return;
1376 }
reed@google.comabf15c12011-01-18 20:35:51 +00001377
reed@android.com8a1c16f2008-12-17 15:59:43 +00001378 SkScalar dist = SkScalarMulDiv(radius, SK_Scalar1 - cosh, sinh);
1379 if (dist < 0) {
1380 dist = -dist;
1381 }
1382
1383 SkScalar xx = x1 - SkScalarMul(dist, before.fX);
1384 SkScalar yy = y1 - SkScalarMul(dist, before.fY);
1385 SkRotationDirection arcDir;
1386
1387 // now turn before/after into normals
1388 if (sinh > 0) {
1389 before.rotateCCW();
1390 after.rotateCCW();
1391 arcDir = kCW_SkRotationDirection;
1392 } else {
1393 before.rotateCW();
1394 after.rotateCW();
1395 arcDir = kCCW_SkRotationDirection;
1396 }
1397
1398 SkMatrix matrix;
1399 SkPoint pts[kSkBuildQuadArcStorage];
reed@google.comabf15c12011-01-18 20:35:51 +00001400
reed@android.com8a1c16f2008-12-17 15:59:43 +00001401 matrix.setScale(radius, radius);
1402 matrix.postTranslate(xx - SkScalarMul(radius, before.fX),
1403 yy - SkScalarMul(radius, before.fY));
reed@google.comabf15c12011-01-18 20:35:51 +00001404
reed@android.com8a1c16f2008-12-17 15:59:43 +00001405 int count = SkBuildQuadArc(before, after, arcDir, &matrix, pts);
reed@google.comabf15c12011-01-18 20:35:51 +00001406
reed@android.com8a1c16f2008-12-17 15:59:43 +00001407 this->incReserve(count);
1408 // [xx,yy] == pts[0]
1409 this->lineTo(xx, yy);
1410 for (int i = 1; i < count; i += 2) {
1411 this->quadTo(pts[i], pts[i+1]);
1412 }
1413}
1414
1415///////////////////////////////////////////////////////////////////////////////
1416
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001417void SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy, AddPathMode mode) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001418 SkMatrix matrix;
1419
1420 matrix.setTranslate(dx, dy);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001421 this->addPath(path, matrix, mode);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001422}
1423
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001424void SkPath::addPath(const SkPath& path, const SkMatrix& matrix, AddPathMode mode) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001425 SkPathRef::Editor(&fPathRef, path.countVerbs(), path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001426
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001427 RawIter iter(path);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001428 SkPoint pts[4];
1429 Verb verb;
1430
1431 SkMatrix::MapPtsProc proc = matrix.getMapPtsProc();
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001432 bool firstVerb = true;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001433 while ((verb = iter.next(pts)) != kDone_Verb) {
1434 switch (verb) {
1435 case kMove_Verb:
1436 proc(matrix, &pts[0], &pts[0], 1);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001437 if (firstVerb && mode == kExtend_AddPathMode && !isEmpty()) {
1438 injectMoveToIfNeeded(); // In case last contour is closed
1439 this->lineTo(pts[0]);
1440 } else {
1441 this->moveTo(pts[0]);
1442 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001443 break;
1444 case kLine_Verb:
1445 proc(matrix, &pts[1], &pts[1], 1);
1446 this->lineTo(pts[1]);
1447 break;
1448 case kQuad_Verb:
1449 proc(matrix, &pts[1], &pts[1], 2);
1450 this->quadTo(pts[1], pts[2]);
1451 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001452 case kConic_Verb:
1453 proc(matrix, &pts[1], &pts[1], 2);
1454 this->conicTo(pts[1], pts[2], iter.conicWeight());
1455 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001456 case kCubic_Verb:
1457 proc(matrix, &pts[1], &pts[1], 3);
1458 this->cubicTo(pts[1], pts[2], pts[3]);
1459 break;
1460 case kClose_Verb:
1461 this->close();
1462 break;
1463 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001464 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001465 }
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001466 firstVerb = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001467 }
1468}
1469
1470///////////////////////////////////////////////////////////////////////////////
1471
reed@google.com277c3f82013-05-31 15:17:50 +00001472static int pts_in_verb(unsigned verb) {
1473 static const uint8_t gPtsInVerb[] = {
1474 1, // kMove
1475 1, // kLine
1476 2, // kQuad
1477 2, // kConic
1478 3, // kCubic
1479 0, // kClose
1480 0 // kDone
1481 };
1482
1483 SkASSERT(verb < SK_ARRAY_COUNT(gPtsInVerb));
1484 return gPtsInVerb[verb];
1485}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001486
reed@android.com8a1c16f2008-12-17 15:59:43 +00001487// ignore the last point of the 1st contour
1488void SkPath::reversePathTo(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001489 int i, vcount = path.fPathRef->countVerbs();
1490 // exit early if the path is empty, or just has a moveTo.
1491 if (vcount < 2) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001492 return;
1493 }
1494
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001495 SkPathRef::Editor(&fPathRef, vcount, path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001496
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001497 const uint8_t* verbs = path.fPathRef->verbs();
1498 const SkPoint* pts = path.fPathRef->points();
reed@google.com277c3f82013-05-31 15:17:50 +00001499 const SkScalar* conicWeights = path.fPathRef->conicWeights();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001500
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001501 SkASSERT(verbs[~0] == kMove_Verb);
1502 for (i = 1; i < vcount; ++i) {
reed@google.com277c3f82013-05-31 15:17:50 +00001503 unsigned v = verbs[~i];
1504 int n = pts_in_verb(v);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001505 if (n == 0) {
1506 break;
1507 }
1508 pts += n;
reed@google.com277c3f82013-05-31 15:17:50 +00001509 conicWeights += (SkPath::kConic_Verb == v);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001510 }
1511
1512 while (--i > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001513 switch (verbs[~i]) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001514 case kLine_Verb:
1515 this->lineTo(pts[-1].fX, pts[-1].fY);
1516 break;
1517 case kQuad_Verb:
1518 this->quadTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY);
1519 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001520 case kConic_Verb:
1521 this->conicTo(pts[-1], pts[-2], *--conicWeights);
1522 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001523 case kCubic_Verb:
1524 this->cubicTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY,
1525 pts[-3].fX, pts[-3].fY);
1526 break;
1527 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001528 SkDEBUGFAIL("bad verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001529 break;
1530 }
reed@google.com277c3f82013-05-31 15:17:50 +00001531 pts -= pts_in_verb(verbs[~i]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001532 }
1533}
1534
reed@google.com63d73742012-01-10 15:33:12 +00001535void SkPath::reverseAddPath(const SkPath& src) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001536 SkPathRef::Editor ed(&fPathRef, src.fPathRef->countPoints(), src.fPathRef->countVerbs());
reed@google.com63d73742012-01-10 15:33:12 +00001537
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001538 const SkPoint* pts = src.fPathRef->pointsEnd();
1539 // we will iterator through src's verbs backwards
1540 const uint8_t* verbs = src.fPathRef->verbsMemBegin(); // points at the last verb
1541 const uint8_t* verbsEnd = src.fPathRef->verbs(); // points just past the first verb
reed@google.com277c3f82013-05-31 15:17:50 +00001542 const SkScalar* conicWeights = src.fPathRef->conicWeightsEnd();
reed@google.com63d73742012-01-10 15:33:12 +00001543
1544 bool needMove = true;
1545 bool needClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001546 while (verbs < verbsEnd) {
1547 uint8_t v = *(verbs++);
reed@google.com277c3f82013-05-31 15:17:50 +00001548 int n = pts_in_verb(v);
reed@google.com63d73742012-01-10 15:33:12 +00001549
1550 if (needMove) {
1551 --pts;
1552 this->moveTo(pts->fX, pts->fY);
1553 needMove = false;
1554 }
1555 pts -= n;
1556 switch (v) {
1557 case kMove_Verb:
1558 if (needClose) {
1559 this->close();
1560 needClose = false;
1561 }
1562 needMove = true;
1563 pts += 1; // so we see the point in "if (needMove)" above
1564 break;
1565 case kLine_Verb:
1566 this->lineTo(pts[0]);
1567 break;
1568 case kQuad_Verb:
1569 this->quadTo(pts[1], pts[0]);
1570 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001571 case kConic_Verb:
1572 this->conicTo(pts[1], pts[0], *--conicWeights);
1573 break;
reed@google.com63d73742012-01-10 15:33:12 +00001574 case kCubic_Verb:
1575 this->cubicTo(pts[2], pts[1], pts[0]);
1576 break;
1577 case kClose_Verb:
1578 needClose = true;
1579 break;
1580 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00001581 SkDEBUGFAIL("unexpected verb");
reed@google.com63d73742012-01-10 15:33:12 +00001582 }
1583 }
1584}
1585
reed@android.com8a1c16f2008-12-17 15:59:43 +00001586///////////////////////////////////////////////////////////////////////////////
1587
1588void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
1589 SkMatrix matrix;
1590
1591 matrix.setTranslate(dx, dy);
1592 this->transform(matrix, dst);
1593}
1594
reed@android.com8a1c16f2008-12-17 15:59:43 +00001595static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
1596 int level = 2) {
1597 if (--level >= 0) {
1598 SkPoint tmp[7];
1599
1600 SkChopCubicAtHalf(pts, tmp);
1601 subdivide_cubic_to(path, &tmp[0], level);
1602 subdivide_cubic_to(path, &tmp[3], level);
1603 } else {
1604 path->cubicTo(pts[1], pts[2], pts[3]);
1605 }
1606}
1607
1608void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
1609 SkDEBUGCODE(this->validate();)
1610 if (dst == NULL) {
1611 dst = (SkPath*)this;
1612 }
1613
tomhudson@google.com8d430182011-06-06 19:11:19 +00001614 if (matrix.hasPerspective()) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001615 SkPath tmp;
1616 tmp.fFillType = fFillType;
1617
1618 SkPath::Iter iter(*this, false);
1619 SkPoint pts[4];
1620 SkPath::Verb verb;
1621
reed@google.com4a3b7142012-05-16 17:16:46 +00001622 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001623 switch (verb) {
1624 case kMove_Verb:
1625 tmp.moveTo(pts[0]);
1626 break;
1627 case kLine_Verb:
1628 tmp.lineTo(pts[1]);
1629 break;
1630 case kQuad_Verb:
reed220f9262014-12-17 08:21:04 -08001631 // promote the quad to a conic
1632 tmp.conicTo(pts[1], pts[2],
1633 SkConic::TransformW(pts, SK_Scalar1, matrix));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001634 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001635 case kConic_Verb:
reed220f9262014-12-17 08:21:04 -08001636 tmp.conicTo(pts[1], pts[2],
1637 SkConic::TransformW(pts, iter.conicWeight(), matrix));
reed@google.com277c3f82013-05-31 15:17:50 +00001638 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001639 case kCubic_Verb:
1640 subdivide_cubic_to(&tmp, pts);
1641 break;
1642 case kClose_Verb:
1643 tmp.close();
1644 break;
1645 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001646 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001647 break;
1648 }
1649 }
1650
1651 dst->swap(tmp);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001652 SkPathRef::Editor ed(&dst->fPathRef);
1653 matrix.mapPoints(ed.points(), ed.pathRef()->countPoints());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001654 dst->fDirection = kUnknown_Direction;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001655 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001656 SkPathRef::CreateTransformedCopy(&dst->fPathRef, *fPathRef.get(), matrix);
1657
reed@android.com8a1c16f2008-12-17 15:59:43 +00001658 if (this != dst) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001659 dst->fFillType = fFillType;
reed@google.com2a6f8ab2011-10-25 18:41:23 +00001660 dst->fConvexity = fConvexity;
jvanverthb3eb6872014-10-24 07:12:51 -07001661 dst->fIsVolatile = fIsVolatile;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001662 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001663
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001664 if (kUnknown_Direction == fDirection) {
1665 dst->fDirection = kUnknown_Direction;
1666 } else {
1667 SkScalar det2x2 =
1668 SkScalarMul(matrix.get(SkMatrix::kMScaleX), matrix.get(SkMatrix::kMScaleY)) -
1669 SkScalarMul(matrix.get(SkMatrix::kMSkewX), matrix.get(SkMatrix::kMSkewY));
1670 if (det2x2 < 0) {
1671 dst->fDirection = SkPath::OppositeDirection(static_cast<Direction>(fDirection));
1672 } else if (det2x2 > 0) {
1673 dst->fDirection = fDirection;
1674 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001675 dst->fConvexity = kUnknown_Convexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001676 dst->fDirection = kUnknown_Direction;
1677 }
1678 }
1679
reed@android.com8a1c16f2008-12-17 15:59:43 +00001680 SkDEBUGCODE(dst->validate();)
1681 }
1682}
1683
reed@android.com8a1c16f2008-12-17 15:59:43 +00001684///////////////////////////////////////////////////////////////////////////////
1685///////////////////////////////////////////////////////////////////////////////
1686
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001687enum SegmentState {
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001688 kEmptyContour_SegmentState, // The current contour is empty. We may be
1689 // starting processing or we may have just
1690 // closed a contour.
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001691 kAfterMove_SegmentState, // We have seen a move, but nothing else.
1692 kAfterPrimitive_SegmentState // We have seen a primitive but not yet
1693 // closed the path. Also the initial state.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001694};
1695
1696SkPath::Iter::Iter() {
1697#ifdef SK_DEBUG
1698 fPts = NULL;
reed@google.com277c3f82013-05-31 15:17:50 +00001699 fConicWeights = NULL;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001700 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001701 fForceClose = fCloseLine = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001702 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001703#endif
1704 // need to init enough to make next() harmlessly return kDone_Verb
1705 fVerbs = NULL;
1706 fVerbStop = NULL;
1707 fNeedClose = false;
1708}
1709
1710SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
1711 this->setPath(path, forceClose);
1712}
1713
1714void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001715 fPts = path.fPathRef->points();
1716 fVerbs = path.fPathRef->verbs();
1717 fVerbStop = path.fPathRef->verbsMemBegin();
reed@google.com277c3f82013-05-31 15:17:50 +00001718 fConicWeights = path.fPathRef->conicWeights() - 1; // begin one behind
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001719 fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org72785c42011-12-29 21:03:28 +00001720 fMoveTo.fX = fMoveTo.fY = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001721 fForceClose = SkToU8(forceClose);
1722 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001723 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001724}
1725
1726bool SkPath::Iter::isClosedContour() const {
1727 if (fVerbs == NULL || fVerbs == fVerbStop) {
1728 return false;
1729 }
1730 if (fForceClose) {
1731 return true;
1732 }
1733
1734 const uint8_t* verbs = fVerbs;
1735 const uint8_t* stop = fVerbStop;
reed@google.comabf15c12011-01-18 20:35:51 +00001736
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001737 if (kMove_Verb == *(verbs - 1)) {
1738 verbs -= 1; // skip the initial moveto
reed@android.com8a1c16f2008-12-17 15:59:43 +00001739 }
1740
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001741 while (verbs > stop) {
1742 // verbs points one beyond the current verb, decrement first.
1743 unsigned v = *(--verbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001744 if (kMove_Verb == v) {
1745 break;
1746 }
1747 if (kClose_Verb == v) {
1748 return true;
1749 }
1750 }
1751 return false;
1752}
1753
1754SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001755 SkASSERT(pts);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001756 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001757 // A special case: if both points are NaN, SkPoint::operation== returns
1758 // false, but the iterator expects that they are treated as the same.
1759 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001760 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1761 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001762 return kClose_Verb;
1763 }
1764
reed@google.com9e25dbf2012-05-15 17:05:38 +00001765 pts[0] = fLastPt;
1766 pts[1] = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001767 fLastPt = fMoveTo;
1768 fCloseLine = true;
1769 return kLine_Verb;
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +00001770 } else {
1771 pts[0] = fMoveTo;
1772 return kClose_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001773 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001774}
1775
reed@google.com9e25dbf2012-05-15 17:05:38 +00001776const SkPoint& SkPath::Iter::cons_moveTo() {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001777 if (fSegmentState == kAfterMove_SegmentState) {
1778 // Set the first return pt to the move pt
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001779 fSegmentState = kAfterPrimitive_SegmentState;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001780 return fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001781 } else {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001782 SkASSERT(fSegmentState == kAfterPrimitive_SegmentState);
1783 // Set the first return pt to the last pt of the previous primitive.
reed@google.com9e25dbf2012-05-15 17:05:38 +00001784 return fPts[-1];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001785 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001786}
1787
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001788void SkPath::Iter::consumeDegenerateSegments() {
1789 // We need to step over anything that will not move the current draw point
1790 // forward before the next move is seen
1791 const uint8_t* lastMoveVerb = 0;
1792 const SkPoint* lastMovePt = 0;
1793 SkPoint lastPt = fLastPt;
1794 while (fVerbs != fVerbStop) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001795 unsigned verb = *(fVerbs - 1); // fVerbs is one beyond the current verb
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001796 switch (verb) {
1797 case kMove_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001798 // Keep a record of this most recent move
1799 lastMoveVerb = fVerbs;
1800 lastMovePt = fPts;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001801 lastPt = fPts[0];
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001802 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001803 fPts++;
1804 break;
1805
1806 case kClose_Verb:
schenney@chromium.org7e963602012-06-13 17:05:43 +00001807 // A close when we are in a segment is always valid except when it
1808 // follows a move which follows a segment.
1809 if (fSegmentState == kAfterPrimitive_SegmentState && !lastMoveVerb) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001810 return;
1811 }
1812 // A close at any other time must be ignored
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001813 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001814 break;
1815
1816 case kLine_Verb:
1817 if (!IsLineDegenerate(lastPt, fPts[0])) {
1818 if (lastMoveVerb) {
1819 fVerbs = lastMoveVerb;
1820 fPts = lastMovePt;
1821 return;
1822 }
1823 return;
1824 }
1825 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001826 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001827 fPts++;
1828 break;
1829
reed@google.com277c3f82013-05-31 15:17:50 +00001830 case kConic_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001831 case kQuad_Verb:
1832 if (!IsQuadDegenerate(lastPt, fPts[0], fPts[1])) {
1833 if (lastMoveVerb) {
1834 fVerbs = lastMoveVerb;
1835 fPts = lastMovePt;
1836 return;
1837 }
1838 return;
1839 }
1840 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001841 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001842 fPts += 2;
reed@google.com277c3f82013-05-31 15:17:50 +00001843 fConicWeights += (kConic_Verb == verb);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001844 break;
1845
1846 case kCubic_Verb:
1847 if (!IsCubicDegenerate(lastPt, fPts[0], fPts[1], fPts[2])) {
1848 if (lastMoveVerb) {
1849 fVerbs = lastMoveVerb;
1850 fPts = lastMovePt;
1851 return;
1852 }
1853 return;
1854 }
1855 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001856 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001857 fPts += 3;
1858 break;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001859
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001860 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001861 SkDEBUGFAIL("Should never see kDone_Verb");
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001862 }
1863 }
1864}
1865
reed@google.com4a3b7142012-05-16 17:16:46 +00001866SkPath::Verb SkPath::Iter::doNext(SkPoint ptsParam[4]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001867 SkASSERT(ptsParam);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001868
reed@android.com8a1c16f2008-12-17 15:59:43 +00001869 if (fVerbs == fVerbStop) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001870 // Close the curve if requested and if there is some curve to close
1871 if (fNeedClose && fSegmentState == kAfterPrimitive_SegmentState) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001872 if (kLine_Verb == this->autoClose(ptsParam)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001873 return kLine_Verb;
1874 }
1875 fNeedClose = false;
1876 return kClose_Verb;
1877 }
1878 return kDone_Verb;
1879 }
1880
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001881 // fVerbs is one beyond the current verb, decrement first
1882 unsigned verb = *(--fVerbs);
reed@google.com9e25dbf2012-05-15 17:05:38 +00001883 const SkPoint* SK_RESTRICT srcPts = fPts;
1884 SkPoint* SK_RESTRICT pts = ptsParam;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001885
1886 switch (verb) {
1887 case kMove_Verb:
1888 if (fNeedClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001889 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001890 verb = this->autoClose(pts);
1891 if (verb == kClose_Verb) {
1892 fNeedClose = false;
1893 }
1894 return (Verb)verb;
1895 }
1896 if (fVerbs == fVerbStop) { // might be a trailing moveto
1897 return kDone_Verb;
1898 }
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001899 fMoveTo = *srcPts;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001900 pts[0] = *srcPts;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001901 srcPts += 1;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001902 fSegmentState = kAfterMove_SegmentState;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001903 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001904 fNeedClose = fForceClose;
1905 break;
1906 case kLine_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001907 pts[0] = this->cons_moveTo();
1908 pts[1] = srcPts[0];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001909 fLastPt = srcPts[0];
1910 fCloseLine = false;
1911 srcPts += 1;
1912 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001913 case kConic_Verb:
1914 fConicWeights += 1;
1915 // fall-through
reed@android.com8a1c16f2008-12-17 15:59:43 +00001916 case kQuad_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001917 pts[0] = this->cons_moveTo();
1918 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001919 fLastPt = srcPts[1];
1920 srcPts += 2;
1921 break;
1922 case kCubic_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001923 pts[0] = this->cons_moveTo();
1924 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001925 fLastPt = srcPts[2];
1926 srcPts += 3;
1927 break;
1928 case kClose_Verb:
1929 verb = this->autoClose(pts);
1930 if (verb == kLine_Verb) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001931 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001932 } else {
1933 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001934 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001935 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001936 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001937 break;
1938 }
1939 fPts = srcPts;
1940 return (Verb)verb;
1941}
1942
1943///////////////////////////////////////////////////////////////////////////////
1944
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001945SkPath::RawIter::RawIter() {
1946#ifdef SK_DEBUG
1947 fPts = NULL;
reed@google.com277c3f82013-05-31 15:17:50 +00001948 fConicWeights = NULL;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001949 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
1950#endif
1951 // need to init enough to make next() harmlessly return kDone_Verb
1952 fVerbs = NULL;
1953 fVerbStop = NULL;
1954}
1955
1956SkPath::RawIter::RawIter(const SkPath& path) {
1957 this->setPath(path);
1958}
1959
1960void SkPath::RawIter::setPath(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001961 fPts = path.fPathRef->points();
1962 fVerbs = path.fPathRef->verbs();
1963 fVerbStop = path.fPathRef->verbsMemBegin();
reed@google.com277c3f82013-05-31 15:17:50 +00001964 fConicWeights = path.fPathRef->conicWeights() - 1; // begin one behind
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001965 fMoveTo.fX = fMoveTo.fY = 0;
1966 fLastPt.fX = fLastPt.fY = 0;
1967}
1968
1969SkPath::Verb SkPath::RawIter::next(SkPoint pts[4]) {
bsalomon49f085d2014-09-05 13:34:00 -07001970 SkASSERT(pts);
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001971 if (fVerbs == fVerbStop) {
1972 return kDone_Verb;
1973 }
1974
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001975 // fVerbs points one beyond next verb so decrement first.
1976 unsigned verb = *(--fVerbs);
1977 const SkPoint* srcPts = fPts;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001978
1979 switch (verb) {
1980 case kMove_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001981 pts[0] = *srcPts;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001982 fMoveTo = srcPts[0];
1983 fLastPt = fMoveTo;
1984 srcPts += 1;
1985 break;
1986 case kLine_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001987 pts[0] = fLastPt;
1988 pts[1] = srcPts[0];
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001989 fLastPt = srcPts[0];
1990 srcPts += 1;
1991 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001992 case kConic_Verb:
1993 fConicWeights += 1;
1994 // fall-through
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001995 case kQuad_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001996 pts[0] = fLastPt;
1997 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001998 fLastPt = srcPts[1];
1999 srcPts += 2;
2000 break;
2001 case kCubic_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002002 pts[0] = fLastPt;
2003 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002004 fLastPt = srcPts[2];
2005 srcPts += 3;
2006 break;
2007 case kClose_Verb:
2008 fLastPt = fMoveTo;
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002009 pts[0] = fMoveTo;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002010 break;
2011 }
2012 fPts = srcPts;
2013 return (Verb)verb;
2014}
2015
2016///////////////////////////////////////////////////////////////////////////////
2017
reed@android.com8a1c16f2008-12-17 15:59:43 +00002018/*
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002019 Format in compressed buffer: [ptCount, verbCount, pts[], verbs[]]
reed@android.com8a1c16f2008-12-17 15:59:43 +00002020*/
2021
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002022size_t SkPath::writeToMemory(void* storage) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002023 SkDEBUGCODE(this->validate();)
2024
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002025 if (NULL == storage) {
robertphillips@google.comca0c8382013-09-26 12:18:23 +00002026 const int byteCount = sizeof(int32_t) + fPathRef->writeSize();
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002027 return SkAlign4(byteCount);
2028 }
2029
2030 SkWBuffer buffer(storage);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002031
robertphillips@google.com466310d2013-12-03 16:43:54 +00002032 int32_t packed = (fConvexity << kConvexity_SerializationShift) |
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002033 (fFillType << kFillType_SerializationShift) |
jvanverthb3eb6872014-10-24 07:12:51 -07002034 (fDirection << kDirection_SerializationShift) |
2035 (fIsVolatile << kIsVolatile_SerializationShift);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002036
robertphillips@google.com2972bb52012-08-07 17:32:51 +00002037 buffer.write32(packed);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002038
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002039 fPathRef->writeToBuffer(&buffer);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002040
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002041 buffer.padToAlign4();
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002042 return buffer.pos();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002043}
2044
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002045size_t SkPath::readFromMemory(const void* storage, size_t length) {
2046 SkRBufferWithSizeCheck buffer(storage, length);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002047
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00002048 int32_t packed;
2049 if (!buffer.readS32(&packed)) {
2050 return 0;
2051 }
2052
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002053 fConvexity = (packed >> kConvexity_SerializationShift) & 0xFF;
2054 fFillType = (packed >> kFillType_SerializationShift) & 0xFF;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002055 fDirection = (packed >> kDirection_SerializationShift) & 0x3;
jvanverthb3eb6872014-10-24 07:12:51 -07002056 fIsVolatile = (packed >> kIsVolatile_SerializationShift) & 0x1;
commit-bot@chromium.orgfed2ab62014-01-23 15:16:05 +00002057 SkPathRef* pathRef = SkPathRef::CreateFromBuffer(&buffer);
reed@google.comabf15c12011-01-18 20:35:51 +00002058
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002059 size_t sizeRead = 0;
2060 if (buffer.isValid()) {
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00002061 fPathRef.reset(pathRef);
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002062 SkDEBUGCODE(this->validate();)
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00002063 buffer.skipToAlign4();
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002064 sizeRead = buffer.pos();
bsalomon49f085d2014-09-05 13:34:00 -07002065 } else if (pathRef) {
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00002066 // If the buffer is not valid, pathRef should be NULL
2067 sk_throw();
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002068 }
2069 return sizeRead;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002070}
2071
2072///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00002073
reede05fed02014-12-15 07:59:53 -08002074#include "SkStringUtils.h"
caryclark66a5d8b2014-06-24 08:30:15 -07002075#include "SkStream.h"
reed@google.com51bbe752013-01-17 22:07:50 +00002076
reed@google.com51bbe752013-01-17 22:07:50 +00002077static void append_params(SkString* str, const char label[], const SkPoint pts[],
reede05fed02014-12-15 07:59:53 -08002078 int count, SkScalarAsStringType strType, SkScalar conicWeight = -1) {
reed@google.com51bbe752013-01-17 22:07:50 +00002079 str->append(label);
2080 str->append("(");
skia.committer@gmail.com15dd3002013-01-18 07:07:28 +00002081
reed@google.com51bbe752013-01-17 22:07:50 +00002082 const SkScalar* values = &pts[0].fX;
2083 count *= 2;
2084
2085 for (int i = 0; i < count; ++i) {
reede05fed02014-12-15 07:59:53 -08002086 SkAppendScalar(str, values[i], strType);
reed@google.com51bbe752013-01-17 22:07:50 +00002087 if (i < count - 1) {
2088 str->append(", ");
2089 }
2090 }
reed@google.com277c3f82013-05-31 15:17:50 +00002091 if (conicWeight >= 0) {
2092 str->append(", ");
reede05fed02014-12-15 07:59:53 -08002093 SkAppendScalar(str, conicWeight, strType);
reed@google.com277c3f82013-05-31 15:17:50 +00002094 }
caryclark08fa28c2014-10-23 13:08:56 -07002095 str->append(");");
reede05fed02014-12-15 07:59:53 -08002096 if (kHex_SkScalarAsStringType == strType) {
caryclark08fa28c2014-10-23 13:08:56 -07002097 str->append(" // ");
2098 for (int i = 0; i < count; ++i) {
reede05fed02014-12-15 07:59:53 -08002099 SkAppendScalarDec(str, values[i]);
caryclark08fa28c2014-10-23 13:08:56 -07002100 if (i < count - 1) {
2101 str->append(", ");
2102 }
2103 }
2104 if (conicWeight >= 0) {
2105 str->append(", ");
reede05fed02014-12-15 07:59:53 -08002106 SkAppendScalarDec(str, conicWeight);
caryclark08fa28c2014-10-23 13:08:56 -07002107 }
2108 }
2109 str->append("\n");
reed@google.com51bbe752013-01-17 22:07:50 +00002110}
2111
caryclarke9562592014-09-15 09:26:09 -07002112void SkPath::dump(SkWStream* wStream, bool forceClose, bool dumpAsHex) const {
reede05fed02014-12-15 07:59:53 -08002113 SkScalarAsStringType asType = dumpAsHex ? kHex_SkScalarAsStringType : kDec_SkScalarAsStringType;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002114 Iter iter(*this, forceClose);
2115 SkPoint pts[4];
2116 Verb verb;
2117
caryclark66a5d8b2014-06-24 08:30:15 -07002118 if (!wStream) {
2119 SkDebugf("path: forceClose=%s\n", forceClose ? "true" : "false");
2120 }
reed@google.com51bbe752013-01-17 22:07:50 +00002121 SkString builder;
2122
reed@google.com4a3b7142012-05-16 17:16:46 +00002123 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002124 switch (verb) {
2125 case kMove_Verb:
reede05fed02014-12-15 07:59:53 -08002126 append_params(&builder, "path.moveTo", &pts[0], 1, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002127 break;
2128 case kLine_Verb:
reede05fed02014-12-15 07:59:53 -08002129 append_params(&builder, "path.lineTo", &pts[1], 1, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002130 break;
2131 case kQuad_Verb:
reede05fed02014-12-15 07:59:53 -08002132 append_params(&builder, "path.quadTo", &pts[1], 2, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002133 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002134 case kConic_Verb:
reede05fed02014-12-15 07:59:53 -08002135 append_params(&builder, "path.conicTo", &pts[1], 2, asType, iter.conicWeight());
reed@google.com277c3f82013-05-31 15:17:50 +00002136 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002137 case kCubic_Verb:
reede05fed02014-12-15 07:59:53 -08002138 append_params(&builder, "path.cubicTo", &pts[1], 3, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002139 break;
2140 case kClose_Verb:
caryclark66a5d8b2014-06-24 08:30:15 -07002141 builder.append("path.close();\n");
reed@android.com8a1c16f2008-12-17 15:59:43 +00002142 break;
2143 default:
2144 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
2145 verb = kDone_Verb; // stop the loop
2146 break;
2147 }
2148 }
caryclark66a5d8b2014-06-24 08:30:15 -07002149 if (wStream) {
2150 wStream->writeText(builder.c_str());
2151 } else {
2152 SkDebugf("%s", builder.c_str());
2153 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002154}
2155
reed@android.come522ca52009-11-23 20:10:41 +00002156void SkPath::dump() const {
caryclarke9562592014-09-15 09:26:09 -07002157 this->dump(NULL, false, false);
2158}
2159
2160void SkPath::dumpHex() const {
2161 this->dump(NULL, false, true);
reed@android.come522ca52009-11-23 20:10:41 +00002162}
2163
2164#ifdef SK_DEBUG
2165void SkPath::validate() const {
2166 SkASSERT(this != NULL);
2167 SkASSERT((fFillType & ~3) == 0);
reed@google.comabf15c12011-01-18 20:35:51 +00002168
djsollen@google.com077348c2012-10-22 20:23:32 +00002169#ifdef SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002170 if (!fBoundsIsDirty) {
2171 SkRect bounds;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002172
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002173 bool isFinite = compute_pt_bounds(&bounds, *fPathRef.get());
robertphillips@google.com5d8d1862012-08-15 14:36:41 +00002174 SkASSERT(SkToBool(fIsFinite) == isFinite);
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002175
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002176 if (fPathRef->countPoints() <= 1) {
reed@android.come522ca52009-11-23 20:10:41 +00002177 // if we're empty, fBounds may be empty but translated, so we can't
2178 // necessarily compare to bounds directly
2179 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
2180 // be [2, 2, 2, 2]
2181 SkASSERT(bounds.isEmpty());
2182 SkASSERT(fBounds.isEmpty());
2183 } else {
reed@google.comeac52bd2011-11-14 18:13:59 +00002184 if (bounds.isEmpty()) {
2185 SkASSERT(fBounds.isEmpty());
2186 } else {
reed@google.com3563c9e2011-11-14 19:34:57 +00002187 if (!fBounds.isEmpty()) {
2188 SkASSERT(fBounds.contains(bounds));
2189 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002190 }
reed@android.come522ca52009-11-23 20:10:41 +00002191 }
2192 }
djsollen@google.com077348c2012-10-22 20:23:32 +00002193#endif // SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002194}
djsollen@google.com077348c2012-10-22 20:23:32 +00002195#endif // SK_DEBUG
reed@android.come522ca52009-11-23 20:10:41 +00002196
reed@google.com04863fa2011-05-15 04:08:24 +00002197///////////////////////////////////////////////////////////////////////////////
2198
reed@google.com0b7b9822011-05-16 12:29:27 +00002199static int sign(SkScalar x) { return x < 0; }
2200#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00002201
robertphillipsc506e302014-09-16 09:43:31 -07002202enum DirChange {
2203 kLeft_DirChange,
2204 kRight_DirChange,
2205 kStraight_DirChange,
2206 kBackwards_DirChange,
2207
2208 kInvalid_DirChange
2209};
2210
2211
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002212static bool almost_equal(SkScalar compA, SkScalar compB) {
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002213 // The error epsilon was empirically derived; worse case round rects
2214 // with a mid point outset by 2x float epsilon in tests had an error
2215 // of 12.
2216 const int epsilon = 16;
2217 if (!SkScalarIsFinite(compA) || !SkScalarIsFinite(compB)) {
2218 return false;
2219 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002220 // no need to check for small numbers because SkPath::Iter has removed degenerate values
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002221 int aBits = SkFloatAs2sCompliment(compA);
2222 int bBits = SkFloatAs2sCompliment(compB);
2223 return aBits < bBits + epsilon && bBits < aBits + epsilon;
reed@google.com04863fa2011-05-15 04:08:24 +00002224}
2225
robertphillipsc506e302014-09-16 09:43:31 -07002226static DirChange direction_change(const SkPoint& lastPt, const SkVector& curPt,
2227 const SkVector& lastVec, const SkVector& curVec) {
2228 SkScalar cross = SkPoint::CrossProduct(lastVec, curVec);
2229
2230 SkScalar smallest = SkTMin(curPt.fX, SkTMin(curPt.fY, SkTMin(lastPt.fX, lastPt.fY)));
2231 SkScalar largest = SkTMax(curPt.fX, SkTMax(curPt.fY, SkTMax(lastPt.fX, lastPt.fY)));
2232 largest = SkTMax(largest, -smallest);
2233
2234 if (!almost_equal(largest, largest + cross)) {
2235 int sign = SkScalarSignAsInt(cross);
2236 if (sign) {
2237 return (1 == sign) ? kRight_DirChange : kLeft_DirChange;
2238 }
2239 }
2240
2241 if (!SkScalarNearlyZero(lastVec.lengthSqd(), SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
2242 !SkScalarNearlyZero(curVec.lengthSqd(), SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
2243 lastVec.dot(curVec) < 0.0f) {
2244 return kBackwards_DirChange;
2245 }
2246
2247 return kStraight_DirChange;
2248}
2249
reed@google.com04863fa2011-05-15 04:08:24 +00002250// only valid for a single contour
2251struct Convexicator {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002252 Convexicator()
2253 : fPtCount(0)
2254 , fConvexity(SkPath::kConvex_Convexity)
caryclarkd3d1a982014-12-08 04:57:38 -08002255 , fDirection(SkPath::kUnknown_Direction)
2256 , fIsFinite(true) {
robertphillipsc506e302014-09-16 09:43:31 -07002257 fExpectedDir = kInvalid_DirChange;
reed@google.com04863fa2011-05-15 04:08:24 +00002258 // warnings
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002259 fLastPt.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002260 fCurrPt.set(0, 0);
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002261 fLastVec.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002262 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00002263
2264 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00002265 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00002266 }
2267
2268 SkPath::Convexity getConvexity() const { return fConvexity; }
2269
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002270 /** The direction returned is only valid if the path is determined convex */
2271 SkPath::Direction getDirection() const { return fDirection; }
2272
reed@google.com04863fa2011-05-15 04:08:24 +00002273 void addPt(const SkPoint& pt) {
caryclarkd3d1a982014-12-08 04:57:38 -08002274 if (SkPath::kConcave_Convexity == fConvexity || !fIsFinite) {
reed@google.com04863fa2011-05-15 04:08:24 +00002275 return;
2276 }
2277
2278 if (0 == fPtCount) {
2279 fCurrPt = pt;
2280 ++fPtCount;
2281 } else {
2282 SkVector vec = pt - fCurrPt;
caryclarkd3d1a982014-12-08 04:57:38 -08002283 SkScalar lengthSqd = vec.lengthSqd();
2284 if (!SkScalarIsFinite(lengthSqd)) {
2285 fIsFinite = false;
2286 } else if (!SkScalarNearlyZero(lengthSqd, SK_ScalarNearlyZero*SK_ScalarNearlyZero)) {
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002287 fLastPt = fCurrPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002288 fCurrPt = pt;
2289 if (++fPtCount == 2) {
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002290 fFirstVec = fLastVec = vec;
reed@google.com04863fa2011-05-15 04:08:24 +00002291 } else {
2292 SkASSERT(fPtCount > 2);
2293 this->addVec(vec);
2294 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002295
reed@google.com85b6e392011-05-15 20:25:17 +00002296 int sx = sign(vec.fX);
2297 int sy = sign(vec.fY);
2298 fDx += (sx != fSx);
2299 fDy += (sy != fSy);
2300 fSx = sx;
2301 fSy = sy;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002302
reed@google.com85b6e392011-05-15 20:25:17 +00002303 if (fDx > 3 || fDy > 3) {
2304 fConvexity = SkPath::kConcave_Convexity;
2305 }
reed@google.com04863fa2011-05-15 04:08:24 +00002306 }
2307 }
2308 }
2309
2310 void close() {
2311 if (fPtCount > 2) {
2312 this->addVec(fFirstVec);
2313 }
2314 }
2315
caryclarkd3d1a982014-12-08 04:57:38 -08002316 bool isFinite() const {
2317 return fIsFinite;
2318 }
2319
reed@google.com04863fa2011-05-15 04:08:24 +00002320private:
2321 void addVec(const SkVector& vec) {
2322 SkASSERT(vec.fX || vec.fY);
robertphillipsc506e302014-09-16 09:43:31 -07002323 DirChange dir = direction_change(fLastPt, fCurrPt, fLastVec, vec);
2324 switch (dir) {
2325 case kLeft_DirChange: // fall through
2326 case kRight_DirChange:
2327 if (kInvalid_DirChange == fExpectedDir) {
2328 fExpectedDir = dir;
2329 fDirection = (kRight_DirChange == dir) ? SkPath::kCW_Direction
2330 : SkPath::kCCW_Direction;
2331 } else if (dir != fExpectedDir) {
2332 fConvexity = SkPath::kConcave_Convexity;
2333 fDirection = SkPath::kUnknown_Direction;
2334 }
2335 fLastVec = vec;
2336 break;
2337 case kStraight_DirChange:
2338 break;
2339 case kBackwards_DirChange:
2340 fLastVec = vec;
2341 break;
2342 case kInvalid_DirChange:
2343 SkFAIL("Use of invalid direction change flag");
2344 break;
reed@google.com04863fa2011-05-15 04:08:24 +00002345 }
2346 }
2347
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002348 SkPoint fLastPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002349 SkPoint fCurrPt;
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002350 // fLastVec does not necessarily start at fLastPt. We only advance it when the cross product
2351 // value with the current vec is deemed to be of a significant value.
2352 SkVector fLastVec, fFirstVec;
reed@google.com04863fa2011-05-15 04:08:24 +00002353 int fPtCount; // non-degenerate points
robertphillipsc506e302014-09-16 09:43:31 -07002354 DirChange fExpectedDir;
reed@google.com04863fa2011-05-15 04:08:24 +00002355 SkPath::Convexity fConvexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002356 SkPath::Direction fDirection;
reed@google.com0b7b9822011-05-16 12:29:27 +00002357 int fDx, fDy, fSx, fSy;
caryclarkd3d1a982014-12-08 04:57:38 -08002358 bool fIsFinite;
reed@google.com04863fa2011-05-15 04:08:24 +00002359};
2360
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002361SkPath::Convexity SkPath::internalGetConvexity() const {
2362 SkASSERT(kUnknown_Convexity == fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002363 SkPoint pts[4];
2364 SkPath::Verb verb;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002365 SkPath::Iter iter(*this, true);
reed@google.com04863fa2011-05-15 04:08:24 +00002366
2367 int contourCount = 0;
2368 int count;
2369 Convexicator state;
2370
caryclarkd3d1a982014-12-08 04:57:38 -08002371 if (!isFinite()) {
2372 return kUnknown_Convexity;
2373 }
reed@google.com04863fa2011-05-15 04:08:24 +00002374 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
2375 switch (verb) {
2376 case kMove_Verb:
2377 if (++contourCount > 1) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002378 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002379 return kConcave_Convexity;
2380 }
2381 pts[1] = pts[0];
2382 count = 1;
2383 break;
2384 case kLine_Verb: count = 1; break;
2385 case kQuad_Verb: count = 2; break;
reed@google.com277c3f82013-05-31 15:17:50 +00002386 case kConic_Verb: count = 2; break;
reed@google.com04863fa2011-05-15 04:08:24 +00002387 case kCubic_Verb: count = 3; break;
2388 case kClose_Verb:
2389 state.close();
2390 count = 0;
2391 break;
2392 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00002393 SkDEBUGFAIL("bad verb");
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002394 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002395 return kConcave_Convexity;
2396 }
2397
2398 for (int i = 1; i <= count; i++) {
2399 state.addPt(pts[i]);
2400 }
2401 // early exit
caryclarkd3d1a982014-12-08 04:57:38 -08002402 if (!state.isFinite()) {
2403 return kUnknown_Convexity;
2404 }
reed@google.com04863fa2011-05-15 04:08:24 +00002405 if (kConcave_Convexity == state.getConvexity()) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002406 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002407 return kConcave_Convexity;
2408 }
2409 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002410 fConvexity = state.getConvexity();
2411 if (kConvex_Convexity == fConvexity && kUnknown_Direction == fDirection) {
2412 fDirection = state.getDirection();
2413 }
2414 return static_cast<Convexity>(fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002415}
reed@google.com69a99432012-01-10 18:00:10 +00002416
2417///////////////////////////////////////////////////////////////////////////////
2418
2419class ContourIter {
2420public:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002421 ContourIter(const SkPathRef& pathRef);
reed@google.com69a99432012-01-10 18:00:10 +00002422
2423 bool done() const { return fDone; }
2424 // if !done() then these may be called
2425 int count() const { return fCurrPtCount; }
2426 const SkPoint* pts() const { return fCurrPt; }
2427 void next();
2428
2429private:
2430 int fCurrPtCount;
2431 const SkPoint* fCurrPt;
2432 const uint8_t* fCurrVerb;
2433 const uint8_t* fStopVerbs;
reed@google.com277c3f82013-05-31 15:17:50 +00002434 const SkScalar* fCurrConicWeight;
reed@google.com69a99432012-01-10 18:00:10 +00002435 bool fDone;
reed@google.comd1ab9322012-01-10 18:40:03 +00002436 SkDEBUGCODE(int fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002437};
2438
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002439ContourIter::ContourIter(const SkPathRef& pathRef) {
2440 fStopVerbs = pathRef.verbsMemBegin();
reed@google.com69a99432012-01-10 18:00:10 +00002441 fDone = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002442 fCurrPt = pathRef.points();
2443 fCurrVerb = pathRef.verbs();
reed@google.com277c3f82013-05-31 15:17:50 +00002444 fCurrConicWeight = pathRef.conicWeights();
reed@google.com69a99432012-01-10 18:00:10 +00002445 fCurrPtCount = 0;
reed@google.comd1ab9322012-01-10 18:40:03 +00002446 SkDEBUGCODE(fContourCounter = 0;)
reed@google.com69a99432012-01-10 18:00:10 +00002447 this->next();
2448}
2449
2450void ContourIter::next() {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002451 if (fCurrVerb <= fStopVerbs) {
reed@google.com69a99432012-01-10 18:00:10 +00002452 fDone = true;
2453 }
2454 if (fDone) {
2455 return;
2456 }
2457
2458 // skip pts of prev contour
2459 fCurrPt += fCurrPtCount;
2460
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002461 SkASSERT(SkPath::kMove_Verb == fCurrVerb[~0]);
reed@google.com69a99432012-01-10 18:00:10 +00002462 int ptCount = 1; // moveTo
2463 const uint8_t* verbs = fCurrVerb;
2464
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002465 for (--verbs; verbs > fStopVerbs; --verbs) {
2466 switch (verbs[~0]) {
reed@google.com69a99432012-01-10 18:00:10 +00002467 case SkPath::kMove_Verb:
reed@google.com69a99432012-01-10 18:00:10 +00002468 goto CONTOUR_END;
2469 case SkPath::kLine_Verb:
2470 ptCount += 1;
2471 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002472 case SkPath::kConic_Verb:
2473 fCurrConicWeight += 1;
2474 // fall-through
reed@google.com69a99432012-01-10 18:00:10 +00002475 case SkPath::kQuad_Verb:
2476 ptCount += 2;
2477 break;
2478 case SkPath::kCubic_Verb:
2479 ptCount += 3;
2480 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002481 case SkPath::kClose_Verb:
2482 break;
2483 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00002484 SkDEBUGFAIL("unexpected verb");
reed@google.com69a99432012-01-10 18:00:10 +00002485 break;
2486 }
2487 }
2488CONTOUR_END:
2489 fCurrPtCount = ptCount;
2490 fCurrVerb = verbs;
reed@google.comd1ab9322012-01-10 18:40:03 +00002491 SkDEBUGCODE(++fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002492}
2493
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002494// returns cross product of (p1 - p0) and (p2 - p0)
reed@google.com69a99432012-01-10 18:00:10 +00002495static SkScalar cross_prod(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002496 SkScalar cross = SkPoint::CrossProduct(p1 - p0, p2 - p0);
2497 // We may get 0 when the above subtracts underflow. We expect this to be
2498 // very rare and lazily promote to double.
2499 if (0 == cross) {
2500 double p0x = SkScalarToDouble(p0.fX);
2501 double p0y = SkScalarToDouble(p0.fY);
2502
2503 double p1x = SkScalarToDouble(p1.fX);
2504 double p1y = SkScalarToDouble(p1.fY);
2505
2506 double p2x = SkScalarToDouble(p2.fX);
2507 double p2y = SkScalarToDouble(p2.fY);
2508
2509 cross = SkDoubleToScalar((p1x - p0x) * (p2y - p0y) -
2510 (p1y - p0y) * (p2x - p0x));
2511
2512 }
2513 return cross;
reed@google.com69a99432012-01-10 18:00:10 +00002514}
2515
reed@google.comc1ea60a2012-01-31 15:15:36 +00002516// Returns the first pt with the maximum Y coordinate
reed@google.com69a99432012-01-10 18:00:10 +00002517static int find_max_y(const SkPoint pts[], int count) {
2518 SkASSERT(count > 0);
2519 SkScalar max = pts[0].fY;
reed@google.comc1ea60a2012-01-31 15:15:36 +00002520 int firstIndex = 0;
reed@google.com69a99432012-01-10 18:00:10 +00002521 for (int i = 1; i < count; ++i) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002522 SkScalar y = pts[i].fY;
2523 if (y > max) {
2524 max = y;
2525 firstIndex = i;
reed@google.com69a99432012-01-10 18:00:10 +00002526 }
2527 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002528 return firstIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002529}
2530
reed@google.comcabaf1d2012-01-11 21:03:05 +00002531static int find_diff_pt(const SkPoint pts[], int index, int n, int inc) {
2532 int i = index;
2533 for (;;) {
2534 i = (i + inc) % n;
2535 if (i == index) { // we wrapped around, so abort
2536 break;
2537 }
2538 if (pts[index] != pts[i]) { // found a different point, success!
2539 break;
2540 }
2541 }
2542 return i;
2543}
2544
reed@google.comc1ea60a2012-01-31 15:15:36 +00002545/**
2546 * Starting at index, and moving forward (incrementing), find the xmin and
2547 * xmax of the contiguous points that have the same Y.
2548 */
2549static int find_min_max_x_at_y(const SkPoint pts[], int index, int n,
2550 int* maxIndexPtr) {
2551 const SkScalar y = pts[index].fY;
2552 SkScalar min = pts[index].fX;
2553 SkScalar max = min;
2554 int minIndex = index;
2555 int maxIndex = index;
2556 for (int i = index + 1; i < n; ++i) {
2557 if (pts[i].fY != y) {
2558 break;
2559 }
2560 SkScalar x = pts[i].fX;
2561 if (x < min) {
2562 min = x;
2563 minIndex = i;
2564 } else if (x > max) {
2565 max = x;
2566 maxIndex = i;
2567 }
2568 }
2569 *maxIndexPtr = maxIndex;
2570 return minIndex;
2571}
2572
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002573static void crossToDir(SkScalar cross, SkPath::Direction* dir) {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002574 *dir = cross > 0 ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002575}
2576
reed@google.comac8543f2012-01-30 20:51:25 +00002577/*
2578 * We loop through all contours, and keep the computed cross-product of the
2579 * contour that contained the global y-max. If we just look at the first
2580 * contour, we may find one that is wound the opposite way (correctly) since
2581 * it is the interior of a hole (e.g. 'o'). Thus we must find the contour
2582 * that is outer most (or at least has the global y-max) before we can consider
2583 * its cross product.
2584 */
reed@google.com69a99432012-01-10 18:00:10 +00002585bool SkPath::cheapComputeDirection(Direction* dir) const {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002586 if (kUnknown_Direction != fDirection) {
2587 *dir = static_cast<Direction>(fDirection);
2588 return true;
2589 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002590
2591 // don't want to pay the cost for computing this if it
2592 // is unknown, so we don't call isConvex()
2593 if (kConvex_Convexity == this->getConvexityOrUnknown()) {
2594 SkASSERT(kUnknown_Direction == fDirection);
2595 *dir = static_cast<Direction>(fDirection);
2596 return false;
2597 }
reed@google.com69a99432012-01-10 18:00:10 +00002598
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002599 ContourIter iter(*fPathRef.get());
reed@google.com69a99432012-01-10 18:00:10 +00002600
reed@google.comac8543f2012-01-30 20:51:25 +00002601 // initialize with our logical y-min
2602 SkScalar ymax = this->getBounds().fTop;
2603 SkScalar ymaxCross = 0;
2604
reed@google.com69a99432012-01-10 18:00:10 +00002605 for (; !iter.done(); iter.next()) {
2606 int n = iter.count();
reed@google.comcabaf1d2012-01-11 21:03:05 +00002607 if (n < 3) {
2608 continue;
2609 }
djsollen@google.come63793a2012-03-21 15:39:03 +00002610
reed@google.comcabaf1d2012-01-11 21:03:05 +00002611 const SkPoint* pts = iter.pts();
reed@google.com69a99432012-01-10 18:00:10 +00002612 SkScalar cross = 0;
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002613 int index = find_max_y(pts, n);
2614 if (pts[index].fY < ymax) {
2615 continue;
2616 }
2617
2618 // If there is more than 1 distinct point at the y-max, we take the
2619 // x-min and x-max of them and just subtract to compute the dir.
2620 if (pts[(index + 1) % n].fY == pts[index].fY) {
2621 int maxIndex;
2622 int minIndex = find_min_max_x_at_y(pts, index, n, &maxIndex);
2623 if (minIndex == maxIndex) {
2624 goto TRY_CROSSPROD;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002625 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002626 SkASSERT(pts[minIndex].fY == pts[index].fY);
2627 SkASSERT(pts[maxIndex].fY == pts[index].fY);
2628 SkASSERT(pts[minIndex].fX <= pts[maxIndex].fX);
2629 // we just subtract the indices, and let that auto-convert to
2630 // SkScalar, since we just want - or + to signal the direction.
2631 cross = minIndex - maxIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002632 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002633 TRY_CROSSPROD:
2634 // Find a next and prev index to use for the cross-product test,
2635 // but we try to find pts that form non-zero vectors from pts[index]
2636 //
2637 // Its possible that we can't find two non-degenerate vectors, so
2638 // we have to guard our search (e.g. all the pts could be in the
2639 // same place).
2640
2641 // we pass n - 1 instead of -1 so we don't foul up % operator by
2642 // passing it a negative LH argument.
2643 int prev = find_diff_pt(pts, index, n, n - 1);
2644 if (prev == index) {
2645 // completely degenerate, skip to next contour
reed@google.comac8543f2012-01-30 20:51:25 +00002646 continue;
2647 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002648 int next = find_diff_pt(pts, index, n, 1);
2649 SkASSERT(next != index);
2650 cross = cross_prod(pts[prev], pts[index], pts[next]);
2651 // if we get a zero and the points are horizontal, then we look at the spread in
2652 // x-direction. We really should continue to walk away from the degeneracy until
2653 // there is a divergence.
2654 if (0 == cross && pts[prev].fY == pts[index].fY && pts[next].fY == pts[index].fY) {
2655 // construct the subtract so we get the correct Direction below
2656 cross = pts[index].fX - pts[next].fX;
reed@google.com188bfcf2012-01-17 18:26:38 +00002657 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002658 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002659
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002660 if (cross) {
2661 // record our best guess so far
2662 ymax = pts[index].fY;
2663 ymaxCross = cross;
reed@google.com69a99432012-01-10 18:00:10 +00002664 }
2665 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002666 if (ymaxCross) {
2667 crossToDir(ymaxCross, dir);
2668 fDirection = *dir;
2669 return true;
2670 } else {
2671 return false;
2672 }
reed@google.comac8543f2012-01-30 20:51:25 +00002673}
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002674
2675///////////////////////////////////////////////////////////////////////////////
2676
2677static SkScalar eval_cubic_coeff(SkScalar A, SkScalar B, SkScalar C,
2678 SkScalar D, SkScalar t) {
2679 return SkScalarMulAdd(SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C), t, D);
2680}
2681
2682static SkScalar eval_cubic_pts(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2683 SkScalar t) {
2684 SkScalar A = c3 + 3*(c1 - c2) - c0;
2685 SkScalar B = 3*(c2 - c1 - c1 + c0);
2686 SkScalar C = 3*(c1 - c0);
2687 SkScalar D = c0;
2688 return eval_cubic_coeff(A, B, C, D, t);
2689}
2690
2691/* Given 4 cubic points (either Xs or Ys), and a target X or Y, compute the
2692 t value such that cubic(t) = target
2693 */
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002694static void chopMonoCubicAt(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002695 SkScalar target, SkScalar* t) {
2696 // SkASSERT(c0 <= c1 && c1 <= c2 && c2 <= c3);
2697 SkASSERT(c0 < target && target < c3);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002698
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002699 SkScalar D = c0 - target;
2700 SkScalar A = c3 + 3*(c1 - c2) - c0;
2701 SkScalar B = 3*(c2 - c1 - c1 + c0);
2702 SkScalar C = 3*(c1 - c0);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002703
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002704 const SkScalar TOLERANCE = SK_Scalar1 / 4096;
2705 SkScalar minT = 0;
2706 SkScalar maxT = SK_Scalar1;
2707 SkScalar mid;
2708 int i;
2709 for (i = 0; i < 16; i++) {
2710 mid = SkScalarAve(minT, maxT);
2711 SkScalar delta = eval_cubic_coeff(A, B, C, D, mid);
2712 if (delta < 0) {
2713 minT = mid;
2714 delta = -delta;
2715 } else {
2716 maxT = mid;
2717 }
2718 if (delta < TOLERANCE) {
2719 break;
2720 }
2721 }
2722 *t = mid;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002723}
2724
2725template <size_t N> static void find_minmax(const SkPoint pts[],
2726 SkScalar* minPtr, SkScalar* maxPtr) {
2727 SkScalar min, max;
2728 min = max = pts[0].fX;
2729 for (size_t i = 1; i < N; ++i) {
2730 min = SkMinScalar(min, pts[i].fX);
2731 max = SkMaxScalar(max, pts[i].fX);
2732 }
2733 *minPtr = min;
2734 *maxPtr = max;
2735}
2736
2737static int winding_mono_cubic(const SkPoint pts[], SkScalar x, SkScalar y) {
2738 SkPoint storage[4];
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002739
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002740 int dir = 1;
2741 if (pts[0].fY > pts[3].fY) {
2742 storage[0] = pts[3];
2743 storage[1] = pts[2];
2744 storage[2] = pts[1];
2745 storage[3] = pts[0];
2746 pts = storage;
2747 dir = -1;
2748 }
2749 if (y < pts[0].fY || y >= pts[3].fY) {
2750 return 0;
2751 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002752
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002753 // quickreject or quickaccept
2754 SkScalar min, max;
2755 find_minmax<4>(pts, &min, &max);
2756 if (x < min) {
2757 return 0;
2758 }
2759 if (x > max) {
2760 return dir;
2761 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002762
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002763 // compute the actual x(t) value
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002764 SkScalar t;
2765 chopMonoCubicAt(pts[0].fY, pts[1].fY, pts[2].fY, pts[3].fY, y, &t);
2766 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 +00002767 return xt < x ? dir : 0;
2768}
2769
2770static int winding_cubic(const SkPoint pts[], SkScalar x, SkScalar y) {
2771 SkPoint dst[10];
2772 int n = SkChopCubicAtYExtrema(pts, dst);
2773 int w = 0;
2774 for (int i = 0; i <= n; ++i) {
2775 w += winding_mono_cubic(&dst[i * 3], x, y);
2776 }
2777 return w;
2778}
2779
2780static int winding_mono_quad(const SkPoint pts[], SkScalar x, SkScalar y) {
2781 SkScalar y0 = pts[0].fY;
2782 SkScalar y2 = pts[2].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002783
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002784 int dir = 1;
2785 if (y0 > y2) {
2786 SkTSwap(y0, y2);
2787 dir = -1;
2788 }
2789 if (y < y0 || y >= y2) {
2790 return 0;
2791 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002792
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002793 // bounds check on X (not required. is it faster?)
2794#if 0
2795 if (pts[0].fX > x && pts[1].fX > x && pts[2].fX > x) {
2796 return 0;
2797 }
2798#endif
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002799
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002800 SkScalar roots[2];
2801 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
2802 2 * (pts[1].fY - pts[0].fY),
2803 pts[0].fY - y,
2804 roots);
2805 SkASSERT(n <= 1);
2806 SkScalar xt;
2807 if (0 == n) {
2808 SkScalar mid = SkScalarAve(y0, y2);
2809 // Need [0] and [2] if dir == 1
2810 // and [2] and [0] if dir == -1
2811 xt = y < mid ? pts[1 - dir].fX : pts[dir - 1].fX;
2812 } else {
2813 SkScalar t = roots[0];
2814 SkScalar C = pts[0].fX;
2815 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
2816 SkScalar B = 2 * (pts[1].fX - C);
2817 xt = SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C);
2818 }
2819 return xt < x ? dir : 0;
2820}
2821
2822static bool is_mono_quad(SkScalar y0, SkScalar y1, SkScalar y2) {
2823 // return SkScalarSignAsInt(y0 - y1) + SkScalarSignAsInt(y1 - y2) != 0;
2824 if (y0 == y1) {
2825 return true;
2826 }
2827 if (y0 < y1) {
2828 return y1 <= y2;
2829 } else {
2830 return y1 >= y2;
2831 }
2832}
2833
2834static int winding_quad(const SkPoint pts[], SkScalar x, SkScalar y) {
2835 SkPoint dst[5];
2836 int n = 0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002837
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002838 if (!is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY)) {
2839 n = SkChopQuadAtYExtrema(pts, dst);
2840 pts = dst;
2841 }
2842 int w = winding_mono_quad(pts, x, y);
2843 if (n > 0) {
2844 w += winding_mono_quad(&pts[2], x, y);
2845 }
2846 return w;
2847}
2848
2849static int winding_line(const SkPoint pts[], SkScalar x, SkScalar y) {
2850 SkScalar x0 = pts[0].fX;
2851 SkScalar y0 = pts[0].fY;
2852 SkScalar x1 = pts[1].fX;
2853 SkScalar y1 = pts[1].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002854
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002855 SkScalar dy = y1 - y0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002856
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002857 int dir = 1;
2858 if (y0 > y1) {
2859 SkTSwap(y0, y1);
2860 dir = -1;
2861 }
2862 if (y < y0 || y >= y1) {
2863 return 0;
2864 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002865
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002866 SkScalar cross = SkScalarMul(x1 - x0, y - pts[0].fY) -
2867 SkScalarMul(dy, x - pts[0].fX);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002868
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002869 if (SkScalarSignAsInt(cross) == dir) {
2870 dir = 0;
2871 }
2872 return dir;
2873}
2874
reed@google.com4db592c2013-10-30 17:39:43 +00002875static bool contains_inclusive(const SkRect& r, SkScalar x, SkScalar y) {
2876 return r.fLeft <= x && x <= r.fRight && r.fTop <= y && y <= r.fBottom;
2877}
2878
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002879bool SkPath::contains(SkScalar x, SkScalar y) const {
2880 bool isInverse = this->isInverseFillType();
2881 if (this->isEmpty()) {
2882 return isInverse;
2883 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002884
reed@google.com4db592c2013-10-30 17:39:43 +00002885 if (!contains_inclusive(this->getBounds(), x, y)) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002886 return isInverse;
2887 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002888
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002889 SkPath::Iter iter(*this, true);
2890 bool done = false;
2891 int w = 0;
2892 do {
2893 SkPoint pts[4];
2894 switch (iter.next(pts, false)) {
2895 case SkPath::kMove_Verb:
2896 case SkPath::kClose_Verb:
2897 break;
2898 case SkPath::kLine_Verb:
2899 w += winding_line(pts, x, y);
2900 break;
2901 case SkPath::kQuad_Verb:
2902 w += winding_quad(pts, x, y);
2903 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002904 case SkPath::kConic_Verb:
2905 SkASSERT(0);
2906 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002907 case SkPath::kCubic_Verb:
2908 w += winding_cubic(pts, x, y);
2909 break;
2910 case SkPath::kDone_Verb:
2911 done = true;
2912 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002913 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002914 } while (!done);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002915
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002916 switch (this->getFillType()) {
2917 case SkPath::kEvenOdd_FillType:
2918 case SkPath::kInverseEvenOdd_FillType:
2919 w &= 1;
2920 break;
reed@google.come9bb6232012-07-11 18:56:10 +00002921 default:
2922 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002923 }
2924 return SkToBool(w);
2925}