blob: d25ec3c1df83f684bd886706297e82abcbe70b0c [file] [log] [blame]
epoger@google.comec3ed6a2011-07-28 14:26:00 +00001
2/*
3 * Copyright 2006 The Android Open Source Project
4 *
5 * Use of this source code is governed by a BSD-style license that can be
6 * found in the LICENSE file.
7 */
8
reed@android.com8a1c16f2008-12-17 15:59:43 +00009
djsollen@google.com94e75ee2012-06-08 18:30:46 +000010#include "SkBuffer.h"
humper@google.com75e3ca12013-04-08 21:44:11 +000011#include "SkErrorInternals.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000012#include "SkMath.h"
humper@google.com75e3ca12013-04-08 21:44:11 +000013#include "SkPath.h"
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +000014#include "SkPathRef.h"
reed@google.com4ed0fb72012-12-12 20:48:18 +000015#include "SkRRect.h"
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +000016#include "SkThread.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000017
bsalomon@google.com65a87cc2012-08-14 13:15:44 +000018SK_DEFINE_INST_COUNT(SkPath);
19
reed@google.com744faba2012-05-29 19:54:52 +000020// This value is just made-up for now. When count is 4, calling memset was much
21// slower than just writing the loop. This seems odd, and hopefully in the
22// future this we appear to have been a fluke...
23#define MIN_COUNT_FOR_MEMSET_TO_BE_FAST 16
24
reed@android.com8a1c16f2008-12-17 15:59:43 +000025////////////////////////////////////////////////////////////////////////////
26
reed@google.com3563c9e2011-11-14 19:34:57 +000027/**
28 * Path.bounds is defined to be the bounds of all the control points.
29 * If we called bounds.join(r) we would skip r if r was empty, which breaks
30 * our promise. Hence we have a custom joiner that doesn't look at emptiness
31 */
32static void joinNoEmptyChecks(SkRect* dst, const SkRect& src) {
33 dst->fLeft = SkMinScalar(dst->fLeft, src.fLeft);
34 dst->fTop = SkMinScalar(dst->fTop, src.fTop);
35 dst->fRight = SkMaxScalar(dst->fRight, src.fRight);
36 dst->fBottom = SkMaxScalar(dst->fBottom, src.fBottom);
37}
38
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000039static bool is_degenerate(const SkPath& path) {
40 SkPath::Iter iter(path, false);
41 SkPoint pts[4];
42 return SkPath::kDone_Verb == iter.next(pts);
43}
44
bsalomon@google.com6aa29652012-04-18 13:29:52 +000045class SkAutoDisableOvalCheck {
46public:
47 SkAutoDisableOvalCheck(SkPath* path) : fPath(path) {
48 fSaved = fPath->fIsOval;
49 }
50
51 ~SkAutoDisableOvalCheck() {
52 fPath->fIsOval = fSaved;
53 }
54
55private:
56 SkPath* fPath;
57 bool fSaved;
58};
commit-bot@chromium.orge61a86c2013-11-18 16:03:59 +000059#define SkAutoDisableOvalCheck(...) SK_REQUIRE_LOCAL_VAR(SkAutoDisableOvalCheck)
bsalomon@google.com6aa29652012-04-18 13:29:52 +000060
bsalomon@google.com30c174b2012-11-13 14:36:42 +000061class SkAutoDisableDirectionCheck {
62public:
63 SkAutoDisableDirectionCheck(SkPath* path) : fPath(path) {
64 fSaved = static_cast<SkPath::Direction>(fPath->fDirection);
65 }
66
67 ~SkAutoDisableDirectionCheck() {
68 fPath->fDirection = fSaved;
69 }
70
71private:
72 SkPath* fPath;
73 SkPath::Direction fSaved;
74};
commit-bot@chromium.orge61a86c2013-11-18 16:03:59 +000075#define SkAutoDisableDirectionCheck(...) SK_REQUIRE_LOCAL_VAR(SkAutoDisableDirectionCheck)
bsalomon@google.com30c174b2012-11-13 14:36:42 +000076
reed@android.com8a1c16f2008-12-17 15:59:43 +000077/* This guy's constructor/destructor bracket a path editing operation. It is
78 used when we know the bounds of the amount we are going to add to the path
79 (usually a new contour, but not required).
reed@google.comabf15c12011-01-18 20:35:51 +000080
reed@android.com8a1c16f2008-12-17 15:59:43 +000081 It captures some state about the path up front (i.e. if it already has a
robertphillips@google.comca0c8382013-09-26 12:18:23 +000082 cached bounds), and then if it can, it updates the cache bounds explicitly,
reed@android.comd252db02009-04-01 18:31:44 +000083 avoiding the need to revisit all of the points in getBounds().
reed@google.comabf15c12011-01-18 20:35:51 +000084
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000085 It also notes if the path was originally degenerate, and if so, sets
86 isConvex to true. Thus it can only be used if the contour being added is
robertphillips@google.comca0c8382013-09-26 12:18:23 +000087 convex (which is always true since we only allow the addition of rects).
reed@android.com8a1c16f2008-12-17 15:59:43 +000088 */
89class SkAutoPathBoundsUpdate {
90public:
91 SkAutoPathBoundsUpdate(SkPath* path, const SkRect& r) : fRect(r) {
92 this->init(path);
93 }
94
95 SkAutoPathBoundsUpdate(SkPath* path, SkScalar left, SkScalar top,
96 SkScalar right, SkScalar bottom) {
97 fRect.set(left, top, right, bottom);
98 this->init(path);
99 }
reed@google.comabf15c12011-01-18 20:35:51 +0000100
reed@android.com8a1c16f2008-12-17 15:59:43 +0000101 ~SkAutoPathBoundsUpdate() {
reed@google.com44699382013-10-31 17:28:30 +0000102 fPath->setConvexity(fDegenerate ? SkPath::kConvex_Convexity
103 : SkPath::kUnknown_Convexity);
robertphillips@google.comca0c8382013-09-26 12:18:23 +0000104 if (fEmpty || fHasValidBounds) {
105 fPath->setBounds(fRect);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000106 }
107 }
reed@google.comabf15c12011-01-18 20:35:51 +0000108
reed@android.com8a1c16f2008-12-17 15:59:43 +0000109private:
reed@android.com6b82d1a2009-06-03 02:35:01 +0000110 SkPath* fPath;
111 SkRect fRect;
robertphillips@google.comca0c8382013-09-26 12:18:23 +0000112 bool fHasValidBounds;
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +0000113 bool fDegenerate;
reed@android.com6b82d1a2009-06-03 02:35:01 +0000114 bool fEmpty;
reed@google.comabf15c12011-01-18 20:35:51 +0000115
reed@android.com6b82d1a2009-06-03 02:35:01 +0000116 void init(SkPath* path) {
robertphillips@google.comca0c8382013-09-26 12:18:23 +0000117 // Cannot use fRect for our bounds unless we know it is sorted
118 fRect.sort();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000119 fPath = path;
reed@google.coma8790de2012-10-24 21:04:04 +0000120 // Mark the path's bounds as dirty if (1) they are, or (2) the path
121 // is non-finite, and therefore its bounds are not meaningful
robertphillips@google.comca0c8382013-09-26 12:18:23 +0000122 fHasValidBounds = path->hasComputedBounds() && path->isFinite();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000123 fEmpty = path->isEmpty();
robertphillips@google.comca0c8382013-09-26 12:18:23 +0000124 if (fHasValidBounds && !fEmpty) {
125 joinNoEmptyChecks(&fRect, fPath->getBounds());
126 }
127 fDegenerate = is_degenerate(*path);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000128 }
129};
commit-bot@chromium.orge61a86c2013-11-18 16:03:59 +0000130#define SkAutoPathBoundsUpdate(...) SK_REQUIRE_LOCAL_VAR(SkAutoPathBoundsUpdate)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000131
reed@android.com8a1c16f2008-12-17 15:59:43 +0000132////////////////////////////////////////////////////////////////////////////
133
134/*
135 Stores the verbs and points as they are given to us, with exceptions:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000136 - we only record "Close" if it was immediately preceeded by Move | Line | Quad | Cubic
reed@android.com8a1c16f2008-12-17 15:59:43 +0000137 - we insert a Move(0,0) if Line | Quad | Cubic is our first command
138
139 The iterator does more cleanup, especially if forceClose == true
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000140 1. If we encounter degenerate segments, remove them
141 2. if we encounter Close, return a cons'd up Line() first (if the curr-pt != start-pt)
142 3. if we encounter Move without a preceeding Close, and forceClose is true, goto #2
143 4. if we encounter Line | Quad | Cubic after Close, cons up a Move
reed@android.com8a1c16f2008-12-17 15:59:43 +0000144*/
145
146////////////////////////////////////////////////////////////////////////////
147
reed@google.comd335d1d2012-01-12 18:17:11 +0000148// flag to require a moveTo if we begin with something else, like lineTo etc.
149#define INITIAL_LASTMOVETOINDEX_VALUE ~0
150
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000151SkPath::SkPath()
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000152 : fPathRef(SkPathRef::CreateEmpty())
bungeman@google.coma5809a32013-06-21 15:13:34 +0000153#ifdef SK_BUILD_FOR_ANDROID
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000154 , fSourcePath(NULL)
bungeman@google.coma5809a32013-06-21 15:13:34 +0000155#endif
156{
157 this->resetFields();
158}
159
160void SkPath::resetFields() {
161 //fPathRef is assumed to have been emptied by the caller.
162 fLastMoveToIndex = INITIAL_LASTMOVETOINDEX_VALUE;
163 fFillType = kWinding_FillType;
164 fSegmentMask = 0;
reed@google.com04863fa2011-05-15 04:08:24 +0000165 fConvexity = kUnknown_Convexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000166 fDirection = kUnknown_Direction;
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000167 fIsOval = false;
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000168
169 // We don't touch Android's fSourcePath. It's used to track texture garbage collection, so we
170 // don't want to muck with it if it's been set to something non-NULL.
reed@android.com6b82d1a2009-06-03 02:35:01 +0000171}
reed@android.com8a1c16f2008-12-17 15:59:43 +0000172
bungeman@google.coma5809a32013-06-21 15:13:34 +0000173SkPath::SkPath(const SkPath& that)
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000174 : fPathRef(SkRef(that.fPathRef.get())) {
bungeman@google.coma5809a32013-06-21 15:13:34 +0000175 this->copyFields(that);
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000176#ifdef SK_BUILD_FOR_ANDROID
mtklein@google.comcb8b0ee2013-08-15 21:14:51 +0000177 fSourcePath = that.fSourcePath;
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000178#endif
bungeman@google.coma5809a32013-06-21 15:13:34 +0000179 SkDEBUGCODE(that.validate();)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000180}
181
182SkPath::~SkPath() {
183 SkDEBUGCODE(this->validate();)
184}
185
bungeman@google.coma5809a32013-06-21 15:13:34 +0000186SkPath& SkPath::operator=(const SkPath& that) {
187 SkDEBUGCODE(that.validate();)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000188
bungeman@google.coma5809a32013-06-21 15:13:34 +0000189 if (this != &that) {
190 fPathRef.reset(SkRef(that.fPathRef.get()));
191 this->copyFields(that);
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000192#ifdef SK_BUILD_FOR_ANDROID
mtklein@google.comcb8b0ee2013-08-15 21:14:51 +0000193 fSourcePath = that.fSourcePath;
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000194#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000195 }
196 SkDEBUGCODE(this->validate();)
197 return *this;
198}
199
bungeman@google.coma5809a32013-06-21 15:13:34 +0000200void SkPath::copyFields(const SkPath& that) {
201 //fPathRef is assumed to have been set by the caller.
bungeman@google.coma5809a32013-06-21 15:13:34 +0000202 fLastMoveToIndex = that.fLastMoveToIndex;
203 fFillType = that.fFillType;
204 fSegmentMask = that.fSegmentMask;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000205 fConvexity = that.fConvexity;
206 fDirection = that.fDirection;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000207 fIsOval = that.fIsOval;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000208}
209
djsollen@google.com9c1a9672013-08-09 13:49:13 +0000210bool operator==(const SkPath& a, const SkPath& b) {
reed@android.com6b82d1a2009-06-03 02:35:01 +0000211 // note: don't need to look at isConvex or bounds, since just comparing the
212 // raw data is sufficient.
reed@google.com10296cc2011-09-21 12:29:05 +0000213
214 // We explicitly check fSegmentMask as a quick-reject. We could skip it,
215 // since it is only a cache of info in the fVerbs, but its a fast way to
216 // notice a difference
217
reed@android.com3abec1d2009-03-02 05:36:20 +0000218 return &a == &b ||
reed@google.com10296cc2011-09-21 12:29:05 +0000219 (a.fFillType == b.fFillType && a.fSegmentMask == b.fSegmentMask &&
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000220 *a.fPathRef.get() == *b.fPathRef.get());
reed@android.com3abec1d2009-03-02 05:36:20 +0000221}
222
bungeman@google.coma5809a32013-06-21 15:13:34 +0000223void SkPath::swap(SkPath& that) {
224 SkASSERT(&that != NULL);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000225
bungeman@google.coma5809a32013-06-21 15:13:34 +0000226 if (this != &that) {
227 fPathRef.swap(&that.fPathRef);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000228 SkTSwap<int>(fLastMoveToIndex, that.fLastMoveToIndex);
229 SkTSwap<uint8_t>(fFillType, that.fFillType);
230 SkTSwap<uint8_t>(fSegmentMask, that.fSegmentMask);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000231 SkTSwap<uint8_t>(fConvexity, that.fConvexity);
232 SkTSwap<uint8_t>(fDirection, that.fDirection);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000233 SkTSwap<SkBool8>(fIsOval, that.fIsOval);
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000234#ifdef SK_BUILD_FOR_ANDROID
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000235 SkTSwap<const SkPath*>(fSourcePath, that.fSourcePath);
236#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000237 }
238}
239
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000240static inline bool check_edge_against_rect(const SkPoint& p0,
241 const SkPoint& p1,
242 const SkRect& rect,
243 SkPath::Direction dir) {
244 const SkPoint* edgeBegin;
245 SkVector v;
246 if (SkPath::kCW_Direction == dir) {
247 v = p1 - p0;
248 edgeBegin = &p0;
249 } else {
250 v = p0 - p1;
251 edgeBegin = &p1;
252 }
253 if (v.fX || v.fY) {
254 // check the cross product of v with the vec from edgeBegin to each rect corner
255 SkScalar yL = SkScalarMul(v.fY, rect.fLeft - edgeBegin->fX);
256 SkScalar xT = SkScalarMul(v.fX, rect.fTop - edgeBegin->fY);
257 SkScalar yR = SkScalarMul(v.fY, rect.fRight - edgeBegin->fX);
258 SkScalar xB = SkScalarMul(v.fX, rect.fBottom - edgeBegin->fY);
259 if ((xT < yL) || (xT < yR) || (xB < yL) || (xB < yR)) {
260 return false;
261 }
262 }
263 return true;
264}
265
266bool SkPath::conservativelyContainsRect(const SkRect& rect) const {
267 // This only handles non-degenerate convex paths currently.
268 if (kConvex_Convexity != this->getConvexity()) {
269 return false;
270 }
skia.committer@gmail.comcec8de62012-11-14 02:01:22 +0000271
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000272 Direction direction;
273 if (!this->cheapComputeDirection(&direction)) {
274 return false;
275 }
276
277 SkPoint firstPt;
278 SkPoint prevPt;
279 RawIter iter(*this);
280 SkPath::Verb verb;
281 SkPoint pts[4];
282 SkDEBUGCODE(int moveCnt = 0;)
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000283 SkDEBUGCODE(int segmentCount = 0;)
284 SkDEBUGCODE(int closeCount = 0;)
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000285
286 while ((verb = iter.next(pts)) != kDone_Verb) {
287 int nextPt = -1;
288 switch (verb) {
289 case kMove_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000290 SkASSERT(!segmentCount && !closeCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000291 SkDEBUGCODE(++moveCnt);
292 firstPt = prevPt = pts[0];
293 break;
294 case kLine_Verb:
295 nextPt = 1;
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000296 SkASSERT(moveCnt && !closeCount);
297 SkDEBUGCODE(++segmentCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000298 break;
299 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000300 case kConic_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000301 SkASSERT(moveCnt && !closeCount);
302 SkDEBUGCODE(++segmentCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000303 nextPt = 2;
304 break;
305 case kCubic_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000306 SkASSERT(moveCnt && !closeCount);
307 SkDEBUGCODE(++segmentCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000308 nextPt = 3;
309 break;
310 case kClose_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000311 SkDEBUGCODE(++closeCount;)
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000312 break;
313 default:
314 SkDEBUGFAIL("unknown verb");
315 }
316 if (-1 != nextPt) {
317 if (!check_edge_against_rect(prevPt, pts[nextPt], rect, direction)) {
318 return false;
319 }
320 prevPt = pts[nextPt];
321 }
322 }
323
324 return check_edge_against_rect(prevPt, firstPt, rect, direction);
325}
326
robertphillips@google.com7101abe2013-10-29 22:45:37 +0000327uint32_t SkPath::getGenerationID() const {
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000328 uint32_t genID = fPathRef->genID();
329#ifdef SK_BUILD_FOR_ANDROID
330 SkASSERT((unsigned)fFillType < (1 << (32 - kPathRefGenIDBitCnt)));
331 genID |= static_cast<uint32_t>(fFillType) << kPathRefGenIDBitCnt;
332#endif
333 return genID;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000334}
djsollen@google.come63793a2012-03-21 15:39:03 +0000335
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000336#ifdef SK_BUILD_FOR_ANDROID
djsollen@google.come63793a2012-03-21 15:39:03 +0000337const SkPath* SkPath::getSourcePath() const {
338 return fSourcePath;
339}
340
341void SkPath::setSourcePath(const SkPath* path) {
342 fSourcePath = path;
343}
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000344#endif
345
reed@android.com8a1c16f2008-12-17 15:59:43 +0000346void SkPath::reset() {
347 SkDEBUGCODE(this->validate();)
348
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000349 fPathRef.reset(SkPathRef::CreateEmpty());
bungeman@google.coma5809a32013-06-21 15:13:34 +0000350 this->resetFields();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000351}
352
353void SkPath::rewind() {
354 SkDEBUGCODE(this->validate();)
355
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000356 SkPathRef::Rewind(&fPathRef);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000357 this->resetFields();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000358}
359
reed@google.com7e6c4d12012-05-10 14:05:43 +0000360bool SkPath::isLine(SkPoint line[2]) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000361 int verbCount = fPathRef->countVerbs();
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000362
commit-bot@chromium.orga62efcc2013-08-05 13:23:13 +0000363 if (2 == verbCount) {
364 SkASSERT(kMove_Verb == fPathRef->atVerb(0));
365 if (kLine_Verb == fPathRef->atVerb(1)) {
366 SkASSERT(2 == fPathRef->countPoints());
reed@google.com7e6c4d12012-05-10 14:05:43 +0000367 if (line) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000368 const SkPoint* pts = fPathRef->points();
reed@google.com7e6c4d12012-05-10 14:05:43 +0000369 line[0] = pts[0];
370 line[1] = pts[1];
371 }
372 return true;
373 }
374 }
375 return false;
376}
377
caryclark@google.comf1316942011-07-26 19:54:45 +0000378/*
379 Determines if path is a rect by keeping track of changes in direction
380 and looking for a loop either clockwise or counterclockwise.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000381
caryclark@google.comf1316942011-07-26 19:54:45 +0000382 The direction is computed such that:
383 0: vertical up
caryclark@google.comf68154a2012-11-21 15:18:06 +0000384 1: horizontal left
caryclark@google.comf1316942011-07-26 19:54:45 +0000385 2: vertical down
caryclark@google.comf68154a2012-11-21 15:18:06 +0000386 3: horizontal right
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000387
caryclark@google.comf1316942011-07-26 19:54:45 +0000388A rectangle cycles up/right/down/left or up/left/down/right.
389
390The test fails if:
391 The path is closed, and followed by a line.
392 A second move creates a new endpoint.
393 A diagonal line is parsed.
394 There's more than four changes of direction.
395 There's a discontinuity on the line (e.g., a move in the middle)
396 The line reverses direction.
397 The rectangle doesn't complete a cycle.
398 The path contains a quadratic or cubic.
399 The path contains fewer than four points.
400 The final point isn't equal to the first point.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000401
caryclark@google.comf1316942011-07-26 19:54:45 +0000402It's OK if the path has:
403 Several colinear line segments composing a rectangle side.
404 Single points on the rectangle side.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000405
caryclark@google.comf1316942011-07-26 19:54:45 +0000406The direction takes advantage of the corners found since opposite sides
407must travel in opposite directions.
408
409FIXME: Allow colinear quads and cubics to be treated like lines.
410FIXME: If the API passes fill-only, return true if the filled stroke
411 is a rectangle, though the caller failed to close the path.
412 */
caryclark@google.comf68154a2012-11-21 15:18:06 +0000413bool SkPath::isRectContour(bool allowPartial, int* currVerb, const SkPoint** ptsPtr,
414 bool* isClosed, Direction* direction) const {
caryclark@google.comf1316942011-07-26 19:54:45 +0000415 int corners = 0;
416 SkPoint first, last;
caryclark@google.com56f233a2012-11-19 13:06:06 +0000417 const SkPoint* pts = *ptsPtr;
caryclark@google.combfe90372012-11-21 13:56:20 +0000418 const SkPoint* savePts = NULL;
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000419 first.set(0, 0);
420 last.set(0, 0);
421 int firstDirection = 0;
422 int lastDirection = 0;
423 int nextDirection = 0;
424 bool closedOrMoved = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000425 bool autoClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000426 int verbCnt = fPathRef->countVerbs();
caryclark@google.com56f233a2012-11-19 13:06:06 +0000427 while (*currVerb < verbCnt && (!allowPartial || !autoClose)) {
428 switch (fPathRef->atVerb(*currVerb)) {
caryclark@google.comf1316942011-07-26 19:54:45 +0000429 case kClose_Verb:
caryclark@google.com56f233a2012-11-19 13:06:06 +0000430 savePts = pts;
431 pts = *ptsPtr;
caryclark@google.comf1316942011-07-26 19:54:45 +0000432 autoClose = true;
433 case kLine_Verb: {
434 SkScalar left = last.fX;
435 SkScalar top = last.fY;
436 SkScalar right = pts->fX;
437 SkScalar bottom = pts->fY;
438 ++pts;
439 if (left != right && top != bottom) {
440 return false; // diagonal
441 }
442 if (left == right && top == bottom) {
443 break; // single point on side OK
444 }
445 nextDirection = (left != right) << 0 |
446 (left < right || top < bottom) << 1;
447 if (0 == corners) {
448 firstDirection = nextDirection;
449 first = last;
450 last = pts[-1];
451 corners = 1;
452 closedOrMoved = false;
453 break;
454 }
455 if (closedOrMoved) {
456 return false; // closed followed by a line
457 }
caryclark@google.combfe90372012-11-21 13:56:20 +0000458 if (autoClose && nextDirection == firstDirection) {
459 break; // colinear with first
460 }
caryclark@google.comf1316942011-07-26 19:54:45 +0000461 closedOrMoved = autoClose;
462 if (lastDirection != nextDirection) {
463 if (++corners > 4) {
464 return false; // too many direction changes
465 }
466 }
467 last = pts[-1];
468 if (lastDirection == nextDirection) {
469 break; // colinear segment
470 }
471 // Possible values for corners are 2, 3, and 4.
472 // When corners == 3, nextDirection opposes firstDirection.
473 // Otherwise, nextDirection at corner 2 opposes corner 4.
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000474 int turn = firstDirection ^ (corners - 1);
caryclark@google.comf1316942011-07-26 19:54:45 +0000475 int directionCycle = 3 == corners ? 0 : nextDirection ^ turn;
476 if ((directionCycle ^ turn) != nextDirection) {
477 return false; // direction didn't follow cycle
478 }
479 break;
480 }
481 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000482 case kConic_Verb:
caryclark@google.comf1316942011-07-26 19:54:45 +0000483 case kCubic_Verb:
484 return false; // quadratic, cubic not allowed
485 case kMove_Verb:
486 last = *pts++;
487 closedOrMoved = true;
488 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000489 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000490 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000491 break;
caryclark@google.comf1316942011-07-26 19:54:45 +0000492 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000493 *currVerb += 1;
caryclark@google.comf1316942011-07-26 19:54:45 +0000494 lastDirection = nextDirection;
495 }
496 // Success if 4 corners and first point equals last
caryclark@google.combfe90372012-11-21 13:56:20 +0000497 bool result = 4 == corners && (first == last || autoClose);
498 if (savePts) {
499 *ptsPtr = savePts;
500 }
caryclark@google.comf68154a2012-11-21 15:18:06 +0000501 if (result && isClosed) {
502 *isClosed = autoClose;
503 }
504 if (result && direction) {
sugoi@google.com12b4e272012-12-06 20:13:11 +0000505 *direction = firstDirection == ((lastDirection + 1) & 3) ? kCCW_Direction : kCW_Direction;
caryclark@google.comf68154a2012-11-21 15:18:06 +0000506 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000507 return result;
508}
509
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000510bool SkPath::isRect(SkRect* rect) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000511 SkDEBUGCODE(this->validate();)
512 int currVerb = 0;
513 const SkPoint* pts = fPathRef->points();
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000514 bool result = isRectContour(false, &currVerb, &pts, NULL, NULL);
515 if (result && rect) {
516 *rect = getBounds();
caryclark@google.comf1316942011-07-26 19:54:45 +0000517 }
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000518 return result;
519}
skia.committer@gmail.com020b25b2013-06-22 07:00:58 +0000520
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000521bool SkPath::isRect(bool* isClosed, Direction* direction) const {
522 SkDEBUGCODE(this->validate();)
523 int currVerb = 0;
524 const SkPoint* pts = fPathRef->points();
525 return isRectContour(false, &currVerb, &pts, isClosed, direction);
caryclark@google.comf68154a2012-11-21 15:18:06 +0000526}
527
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000528bool SkPath::isNestedRects(SkRect rects[2], Direction dirs[2]) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000529 SkDEBUGCODE(this->validate();)
530 int currVerb = 0;
531 const SkPoint* pts = fPathRef->points();
532 const SkPoint* first = pts;
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000533 Direction testDirs[2];
534 if (!isRectContour(true, &currVerb, &pts, NULL, &testDirs[0])) {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000535 return false;
536 }
537 const SkPoint* last = pts;
538 SkRect testRects[2];
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000539 if (isRectContour(false, &currVerb, &pts, NULL, &testDirs[1])) {
scroggo@google.com614f9e32013-05-09 18:05:32 +0000540 testRects[0].set(first, SkToS32(last - first));
541 testRects[1].set(last, SkToS32(pts - last));
caryclark@google.com56f233a2012-11-19 13:06:06 +0000542 if (testRects[0].contains(testRects[1])) {
543 if (rects) {
544 rects[0] = testRects[0];
545 rects[1] = testRects[1];
546 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000547 if (dirs) {
548 dirs[0] = testDirs[0];
549 dirs[1] = testDirs[1];
550 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000551 return true;
552 }
553 if (testRects[1].contains(testRects[0])) {
554 if (rects) {
555 rects[0] = testRects[1];
556 rects[1] = testRects[0];
557 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000558 if (dirs) {
559 dirs[0] = testDirs[1];
560 dirs[1] = testDirs[0];
561 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000562 return true;
563 }
564 }
565 return false;
566}
567
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000568int SkPath::countPoints() const {
569 return fPathRef->countPoints();
570}
571
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000572int SkPath::getPoints(SkPoint dst[], int max) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000573 SkDEBUGCODE(this->validate();)
574
575 SkASSERT(max >= 0);
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000576 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000577 int count = SkMin32(max, fPathRef->countPoints());
578 memcpy(dst, fPathRef->points(), count * sizeof(SkPoint));
579 return fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000580}
581
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000582SkPoint SkPath::getPoint(int index) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000583 if ((unsigned)index < (unsigned)fPathRef->countPoints()) {
584 return fPathRef->atPoint(index);
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000585 }
586 return SkPoint::Make(0, 0);
587}
588
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000589int SkPath::countVerbs() const {
590 return fPathRef->countVerbs();
591}
592
593static inline void copy_verbs_reverse(uint8_t* inorderDst,
594 const uint8_t* reversedSrc,
595 int count) {
596 for (int i = 0; i < count; ++i) {
597 inorderDst[i] = reversedSrc[~i];
598 }
599}
600
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000601int SkPath::getVerbs(uint8_t dst[], int max) const {
602 SkDEBUGCODE(this->validate();)
603
604 SkASSERT(max >= 0);
605 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000606 int count = SkMin32(max, fPathRef->countVerbs());
607 copy_verbs_reverse(dst, fPathRef->verbs(), count);
608 return fPathRef->countVerbs();
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000609}
610
reed@google.com294dd7b2011-10-11 11:58:32 +0000611bool SkPath::getLastPt(SkPoint* lastPt) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000612 SkDEBUGCODE(this->validate();)
613
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000614 int count = fPathRef->countPoints();
reed@google.com294dd7b2011-10-11 11:58:32 +0000615 if (count > 0) {
616 if (lastPt) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000617 *lastPt = fPathRef->atPoint(count - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000618 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000619 return true;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000620 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000621 if (lastPt) {
622 lastPt->set(0, 0);
623 }
624 return false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000625}
626
627void SkPath::setLastPt(SkScalar x, SkScalar y) {
628 SkDEBUGCODE(this->validate();)
629
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000630 int count = fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000631 if (count == 0) {
632 this->moveTo(x, y);
633 } else {
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000634 fIsOval = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000635 SkPathRef::Editor ed(&fPathRef);
636 ed.atPoint(count-1)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000637 }
638}
639
reed@google.com04863fa2011-05-15 04:08:24 +0000640void SkPath::setConvexity(Convexity c) {
641 if (fConvexity != c) {
642 fConvexity = c;
reed@google.com04863fa2011-05-15 04:08:24 +0000643 }
644}
645
reed@android.com8a1c16f2008-12-17 15:59:43 +0000646//////////////////////////////////////////////////////////////////////////////
647// Construction methods
648
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000649#define DIRTY_AFTER_EDIT \
650 do { \
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000651 fConvexity = kUnknown_Convexity; \
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000652 fDirection = kUnknown_Direction; \
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000653 fIsOval = false; \
reed@google.comb54455e2011-05-16 14:16:04 +0000654 } while (0)
655
reed@android.com8a1c16f2008-12-17 15:59:43 +0000656void SkPath::incReserve(U16CPU inc) {
657 SkDEBUGCODE(this->validate();)
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000658 SkPathRef::Editor(&fPathRef, inc, inc);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000659 SkDEBUGCODE(this->validate();)
660}
661
662void SkPath::moveTo(SkScalar x, SkScalar y) {
663 SkDEBUGCODE(this->validate();)
664
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000665 SkPathRef::Editor ed(&fPathRef);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000666
reed@google.comd335d1d2012-01-12 18:17:11 +0000667 // remember our index
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +0000668 fLastMoveToIndex = fPathRef->countPoints();
reed@google.comd335d1d2012-01-12 18:17:11 +0000669
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000670 ed.growForVerb(kMove_Verb)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000671}
672
673void SkPath::rMoveTo(SkScalar x, SkScalar y) {
674 SkPoint pt;
675 this->getLastPt(&pt);
676 this->moveTo(pt.fX + x, pt.fY + y);
677}
678
reed@google.comd335d1d2012-01-12 18:17:11 +0000679void SkPath::injectMoveToIfNeeded() {
680 if (fLastMoveToIndex < 0) {
681 SkScalar x, y;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000682 if (fPathRef->countVerbs() == 0) {
reed@google.comd335d1d2012-01-12 18:17:11 +0000683 x = y = 0;
684 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000685 const SkPoint& pt = fPathRef->atPoint(~fLastMoveToIndex);
reed@google.comd335d1d2012-01-12 18:17:11 +0000686 x = pt.fX;
687 y = pt.fY;
688 }
689 this->moveTo(x, y);
690 }
691}
692
reed@android.com8a1c16f2008-12-17 15:59:43 +0000693void SkPath::lineTo(SkScalar x, SkScalar y) {
694 SkDEBUGCODE(this->validate();)
695
reed@google.comd335d1d2012-01-12 18:17:11 +0000696 this->injectMoveToIfNeeded();
697
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000698 SkPathRef::Editor ed(&fPathRef);
699 ed.growForVerb(kLine_Verb)->set(x, y);
reed@google.com10296cc2011-09-21 12:29:05 +0000700 fSegmentMask |= kLine_SegmentMask;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000701
reed@google.comb54455e2011-05-16 14:16:04 +0000702 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000703}
704
705void SkPath::rLineTo(SkScalar x, SkScalar y) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000706 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000707 SkPoint pt;
708 this->getLastPt(&pt);
709 this->lineTo(pt.fX + x, pt.fY + y);
710}
711
712void SkPath::quadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
713 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000714
reed@google.comd335d1d2012-01-12 18:17:11 +0000715 this->injectMoveToIfNeeded();
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000716
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000717 SkPathRef::Editor ed(&fPathRef);
718 SkPoint* pts = ed.growForVerb(kQuad_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000719 pts[0].set(x1, y1);
720 pts[1].set(x2, y2);
reed@google.com10296cc2011-09-21 12:29:05 +0000721 fSegmentMask |= kQuad_SegmentMask;
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000722
reed@google.comb54455e2011-05-16 14:16:04 +0000723 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000724}
725
726void SkPath::rQuadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000727 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000728 SkPoint pt;
729 this->getLastPt(&pt);
730 this->quadTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2);
731}
732
reed@google.com277c3f82013-05-31 15:17:50 +0000733void SkPath::conicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
734 SkScalar w) {
735 // check for <= 0 or NaN with this test
736 if (!(w > 0)) {
737 this->lineTo(x2, y2);
738 } else if (!SkScalarIsFinite(w)) {
739 this->lineTo(x1, y1);
740 this->lineTo(x2, y2);
741 } else if (SK_Scalar1 == w) {
742 this->quadTo(x1, y1, x2, y2);
743 } else {
744 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000745
reed@google.com277c3f82013-05-31 15:17:50 +0000746 this->injectMoveToIfNeeded();
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000747
reed@google.com277c3f82013-05-31 15:17:50 +0000748 SkPathRef::Editor ed(&fPathRef);
749 SkPoint* pts = ed.growForConic(w);
750 pts[0].set(x1, y1);
751 pts[1].set(x2, y2);
752 fSegmentMask |= kConic_SegmentMask;
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000753
reed@google.com277c3f82013-05-31 15:17:50 +0000754 DIRTY_AFTER_EDIT;
755 }
756}
757
758void SkPath::rConicTo(SkScalar dx1, SkScalar dy1, SkScalar dx2, SkScalar dy2,
759 SkScalar w) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000760 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@google.com277c3f82013-05-31 15:17:50 +0000761 SkPoint pt;
762 this->getLastPt(&pt);
763 this->conicTo(pt.fX + dx1, pt.fY + dy1, pt.fX + dx2, pt.fY + dy2, w);
764}
765
reed@android.com8a1c16f2008-12-17 15:59:43 +0000766void SkPath::cubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
767 SkScalar x3, SkScalar y3) {
768 SkDEBUGCODE(this->validate();)
769
reed@google.comd335d1d2012-01-12 18:17:11 +0000770 this->injectMoveToIfNeeded();
771
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000772 SkPathRef::Editor ed(&fPathRef);
773 SkPoint* pts = ed.growForVerb(kCubic_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000774 pts[0].set(x1, y1);
775 pts[1].set(x2, y2);
776 pts[2].set(x3, y3);
reed@google.com10296cc2011-09-21 12:29:05 +0000777 fSegmentMask |= kCubic_SegmentMask;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000778
reed@google.comb54455e2011-05-16 14:16:04 +0000779 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000780}
781
782void SkPath::rCubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
783 SkScalar x3, SkScalar y3) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000784 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000785 SkPoint pt;
786 this->getLastPt(&pt);
787 this->cubicTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2,
788 pt.fX + x3, pt.fY + y3);
789}
790
791void SkPath::close() {
792 SkDEBUGCODE(this->validate();)
793
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000794 int count = fPathRef->countVerbs();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000795 if (count > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000796 switch (fPathRef->atVerb(count - 1)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000797 case kLine_Verb:
798 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000799 case kConic_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +0000800 case kCubic_Verb:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000801 case kMove_Verb: {
802 SkPathRef::Editor ed(&fPathRef);
803 ed.growForVerb(kClose_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000804 break;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000805 }
reed@google.com277c3f82013-05-31 15:17:50 +0000806 case kClose_Verb:
reed@google.comfa2f2a42013-05-30 15:29:48 +0000807 // don't add a close if it's the first verb or a repeat
reed@google.com7950a9e2013-05-30 14:57:55 +0000808 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000809 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000810 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000811 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000812 }
813 }
reed@google.comd335d1d2012-01-12 18:17:11 +0000814
815 // signal that we need a moveTo to follow us (unless we're done)
816#if 0
817 if (fLastMoveToIndex >= 0) {
818 fLastMoveToIndex = ~fLastMoveToIndex;
819 }
820#else
821 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
822#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000823}
824
825///////////////////////////////////////////////////////////////////////////////
reed@google.comabf15c12011-01-18 20:35:51 +0000826
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000827static void assert_known_direction(int dir) {
828 SkASSERT(SkPath::kCW_Direction == dir || SkPath::kCCW_Direction == dir);
829}
830
reed@android.com8a1c16f2008-12-17 15:59:43 +0000831void SkPath::addRect(const SkRect& rect, Direction dir) {
832 this->addRect(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, dir);
833}
834
835void SkPath::addRect(SkScalar left, SkScalar top, SkScalar right,
836 SkScalar bottom, Direction dir) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000837 assert_known_direction(dir);
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000838 fDirection = this->hasOnlyMoveTos() ? dir : kUnknown_Direction;
839 SkAutoDisableDirectionCheck addc(this);
840
reed@android.com8a1c16f2008-12-17 15:59:43 +0000841 SkAutoPathBoundsUpdate apbu(this, left, top, right, bottom);
842
843 this->incReserve(5);
844
845 this->moveTo(left, top);
846 if (dir == kCCW_Direction) {
847 this->lineTo(left, bottom);
848 this->lineTo(right, bottom);
849 this->lineTo(right, top);
850 } else {
851 this->lineTo(right, top);
852 this->lineTo(right, bottom);
853 this->lineTo(left, bottom);
854 }
855 this->close();
856}
857
reed@google.com744faba2012-05-29 19:54:52 +0000858void SkPath::addPoly(const SkPoint pts[], int count, bool close) {
859 SkDEBUGCODE(this->validate();)
860 if (count <= 0) {
861 return;
862 }
863
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000864 SkPathRef::Editor ed(&fPathRef);
865 fLastMoveToIndex = ed.pathRef()->countPoints();
866 uint8_t* vb;
867 SkPoint* p;
bsalomon@google.com6c5418e2012-09-14 20:30:37 +0000868 // +close makes room for the extra kClose_Verb
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000869 ed.grow(count + close, count, &vb, &p);
bsalomon@google.com6c5418e2012-09-14 20:30:37 +0000870
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000871 memcpy(p, pts, count * sizeof(SkPoint));
872 vb[~0] = kMove_Verb;
reed@google.com744faba2012-05-29 19:54:52 +0000873 if (count > 1) {
874 // cast to unsigned, so if MIN_COUNT_FOR_MEMSET_TO_BE_FAST is defined to
875 // be 0, the compiler will remove the test/branch entirely.
876 if ((unsigned)count >= MIN_COUNT_FOR_MEMSET_TO_BE_FAST) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000877 memset(vb - count, kLine_Verb, count - 1);
reed@google.com744faba2012-05-29 19:54:52 +0000878 } else {
879 for (int i = 1; i < count; ++i) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000880 vb[~i] = kLine_Verb;
reed@google.com744faba2012-05-29 19:54:52 +0000881 }
882 }
883 fSegmentMask |= kLine_SegmentMask;
884 }
885 if (close) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000886 vb[~count] = kClose_Verb;
reed@google.com744faba2012-05-29 19:54:52 +0000887 }
888
reed@google.com744faba2012-05-29 19:54:52 +0000889 DIRTY_AFTER_EDIT;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000890 SkDEBUGCODE(this->validate();)
reed@google.com744faba2012-05-29 19:54:52 +0000891}
892
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000893#include "SkGeometry.h"
894
895static int build_arc_points(const SkRect& oval, SkScalar startAngle,
896 SkScalar sweepAngle,
897 SkPoint pts[kSkBuildQuadArcStorage]) {
898
899 if (0 == sweepAngle &&
900 (0 == startAngle || SkIntToScalar(360) == startAngle)) {
901 // Chrome uses this path to move into and out of ovals. If not
902 // treated as a special case the moves can distort the oval's
903 // bounding box (and break the circle special case).
904 pts[0].set(oval.fRight, oval.centerY());
905 return 1;
906 } else if (0 == oval.width() && 0 == oval.height()) {
907 // Chrome will sometimes create 0 radius round rects. Having degenerate
908 // quad segments in the path prevents the path from being recognized as
909 // a rect.
910 // TODO: optimizing the case where only one of width or height is zero
911 // should also be considered. This case, however, doesn't seem to be
912 // as common as the single point case.
913 pts[0].set(oval.fRight, oval.fTop);
914 return 1;
915 }
916
917 SkVector start, stop;
918
919 start.fY = SkScalarSinCos(SkDegreesToRadians(startAngle), &start.fX);
920 stop.fY = SkScalarSinCos(SkDegreesToRadians(startAngle + sweepAngle),
921 &stop.fX);
922
923 /* If the sweep angle is nearly (but less than) 360, then due to precision
924 loss in radians-conversion and/or sin/cos, we may end up with coincident
925 vectors, which will fool SkBuildQuadArc into doing nothing (bad) instead
926 of drawing a nearly complete circle (good).
927 e.g. canvas.drawArc(0, 359.99, ...)
928 -vs- canvas.drawArc(0, 359.9, ...)
929 We try to detect this edge case, and tweak the stop vector
930 */
931 if (start == stop) {
932 SkScalar sw = SkScalarAbs(sweepAngle);
933 if (sw < SkIntToScalar(360) && sw > SkIntToScalar(359)) {
934 SkScalar stopRad = SkDegreesToRadians(startAngle + sweepAngle);
935 // make a guess at a tiny angle (in radians) to tweak by
936 SkScalar deltaRad = SkScalarCopySign(SK_Scalar1/512, sweepAngle);
937 // not sure how much will be enough, so we use a loop
938 do {
939 stopRad -= deltaRad;
940 stop.fY = SkScalarSinCos(stopRad, &stop.fX);
941 } while (start == stop);
942 }
943 }
944
945 SkMatrix matrix;
946
947 matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height()));
948 matrix.postTranslate(oval.centerX(), oval.centerY());
949
950 return SkBuildQuadArc(start, stop,
951 sweepAngle > 0 ? kCW_SkRotationDirection :
952 kCCW_SkRotationDirection,
953 &matrix, pts);
954}
955
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +0000956void SkPath::addRoundRect(const SkRect& rect, const SkScalar radii[],
reed@android.com8a1c16f2008-12-17 15:59:43 +0000957 Direction dir) {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +0000958 SkRRect rrect;
959 rrect.setRectRadii(rect, (const SkVector*) radii);
960 this->addRRect(rrect, dir);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000961}
962
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +0000963/* The inline clockwise and counterclockwise round rect quad approximations
964 make it easier to see the symmetry patterns used by add corner quads.
965Clockwise corner value
966 path->lineTo(rect.fLeft, rect.fTop + ry); 0 upper left
967 path->quadTo(rect.fLeft, rect.fTop + offPtY,
968 rect.fLeft + midPtX, rect.fTop + midPtY);
969 path->quadTo(rect.fLeft + offPtX, rect.fTop,
970 rect.fLeft + rx, rect.fTop);
971
972 path->lineTo(rect.fRight - rx, rect.fTop); 1 upper right
973 path->quadTo(rect.fRight - offPtX, rect.fTop,
974 rect.fRight - midPtX, rect.fTop + midPtY);
975 path->quadTo(rect.fRight, rect.fTop + offPtY,
976 rect.fRight, rect.fTop + ry);
977
978 path->lineTo(rect.fRight, rect.fBottom - ry); 2 lower right
979 path->quadTo(rect.fRight, rect.fBottom - offPtY,
980 rect.fRight - midPtX, rect.fBottom - midPtY);
981 path->quadTo(rect.fRight - offPtX, rect.fBottom,
982 rect.fRight - rx, rect.fBottom);
983
984 path->lineTo(rect.fLeft + rx, rect.fBottom); 3 lower left
985 path->quadTo(rect.fLeft + offPtX, rect.fBottom,
986 rect.fLeft + midPtX, rect.fBottom - midPtY);
987 path->quadTo(rect.fLeft, rect.fBottom - offPtY,
988 rect.fLeft, rect.fBottom - ry);
989
990Counterclockwise
991 path->lineTo(rect.fLeft, rect.fBottom - ry); 3 lower left
992 path->quadTo(rect.fLeft, rect.fBottom - offPtY,
993 rect.fLeft + midPtX, rect.fBottom - midPtY);
994 path->quadTo(rect.fLeft + offPtX, rect.fBottom,
995 rect.fLeft + rx, rect.fBottom);
996
997 path->lineTo(rect.fRight - rx, rect.fBottom); 2 lower right
998 path->quadTo(rect.fRight - offPtX, rect.fBottom,
999 rect.fRight - midPtX, rect.fBottom - midPtY);
1000 path->quadTo(rect.fRight, rect.fBottom - offPtY,
1001 rect.fRight, rect.fBottom - ry);
1002
1003 path->lineTo(rect.fRight, rect.fTop + ry); 1 upper right
1004 path->quadTo(rect.fRight, rect.fTop + offPtY,
1005 rect.fRight - midPtX, rect.fTop + midPtY);
1006 path->quadTo(rect.fRight - offPtX, rect.fTop,
1007 rect.fRight - rx, rect.fTop);
1008
1009 path->lineTo(rect.fLeft + rx, rect.fTop); 0 upper left
1010 path->quadTo(rect.fLeft + offPtX, rect.fTop,
1011 rect.fLeft + midPtX, rect.fTop + midPtY);
1012 path->quadTo(rect.fLeft, rect.fTop + offPtY,
1013 rect.fLeft, rect.fTop + ry);
1014*/
1015static void add_corner_quads(SkPath* path, const SkRRect& rrect,
1016 SkRRect::Corner corner, SkPath::Direction dir) {
1017 const SkRect& rect = rrect.rect();
1018 const SkVector& radii = rrect.radii(corner);
1019 SkScalar rx = radii.fX;
1020 SkScalar ry = radii.fY;
1021 // The mid point of the quadratic arc approximation is half way between the two
1022 // control points.
caryclark@google.com2e1b99e2013-11-08 18:05:02 +00001023 const SkScalar mid = 1 - (SK_Scalar1 + SK_ScalarTanPIOver8) / 2;
1024 SkScalar midPtX = rx * mid;
1025 SkScalar midPtY = ry * mid;
1026 const SkScalar control = 1 - SK_ScalarTanPIOver8;
1027 SkScalar offPtX = rx * control;
1028 SkScalar offPtY = ry * control;
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001029 static const int kCornerPts = 5;
1030 SkScalar xOff[kCornerPts];
1031 SkScalar yOff[kCornerPts];
1032
1033 if ((corner & 1) == (dir == SkPath::kCCW_Direction)) { // corners always alternate direction
1034 SkASSERT(dir == SkPath::kCCW_Direction
1035 ? corner == SkRRect::kLowerLeft_Corner || corner == SkRRect::kUpperRight_Corner
1036 : corner == SkRRect::kUpperLeft_Corner || corner == SkRRect::kLowerRight_Corner);
1037 xOff[0] = xOff[1] = 0;
1038 xOff[2] = midPtX;
1039 xOff[3] = offPtX;
1040 xOff[4] = rx;
1041 yOff[0] = ry;
1042 yOff[1] = offPtY;
1043 yOff[2] = midPtY;
1044 yOff[3] = yOff[4] = 0;
1045 } else {
1046 xOff[0] = rx;
1047 xOff[1] = offPtX;
1048 xOff[2] = midPtX;
1049 xOff[3] = xOff[4] = 0;
1050 yOff[0] = yOff[1] = 0;
1051 yOff[2] = midPtY;
1052 yOff[3] = offPtY;
1053 yOff[4] = ry;
1054 }
1055 if ((corner - 1) & 2) {
1056 SkASSERT(corner == SkRRect::kLowerLeft_Corner || corner == SkRRect::kUpperLeft_Corner);
1057 for (int i = 0; i < kCornerPts; ++i) {
1058 xOff[i] = rect.fLeft + xOff[i];
1059 }
1060 } else {
1061 SkASSERT(corner == SkRRect::kLowerRight_Corner || corner == SkRRect::kUpperRight_Corner);
1062 for (int i = 0; i < kCornerPts; ++i) {
1063 xOff[i] = rect.fRight - xOff[i];
1064 }
1065 }
1066 if (corner < SkRRect::kLowerRight_Corner) {
1067 for (int i = 0; i < kCornerPts; ++i) {
1068 yOff[i] = rect.fTop + yOff[i];
1069 }
1070 } else {
1071 for (int i = 0; i < kCornerPts; ++i) {
1072 yOff[i] = rect.fBottom - yOff[i];
1073 }
1074 }
1075
1076 SkPoint lastPt;
1077 SkAssertResult(path->getLastPt(&lastPt));
1078 if (lastPt.fX != xOff[0] || lastPt.fY != yOff[0]) {
1079 path->lineTo(xOff[0], yOff[0]);
1080 }
1081 if (rx || ry) {
1082 path->quadTo(xOff[1], yOff[1], xOff[2], yOff[2]);
1083 path->quadTo(xOff[3], yOff[3], xOff[4], yOff[4]);
1084 } else {
1085 path->lineTo(xOff[2], yOff[2]);
1086 path->lineTo(xOff[4], yOff[4]);
1087 }
1088}
1089
reed@google.com4ed0fb72012-12-12 20:48:18 +00001090void SkPath::addRRect(const SkRRect& rrect, Direction dir) {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001091 assert_known_direction(dir);
1092
1093 if (rrect.isEmpty()) {
1094 return;
1095 }
1096
reed@google.com4ed0fb72012-12-12 20:48:18 +00001097 const SkRect& bounds = rrect.getBounds();
1098
1099 if (rrect.isRect()) {
1100 this->addRect(bounds, dir);
1101 } else if (rrect.isOval()) {
1102 this->addOval(bounds, dir);
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001103#ifdef SK_IGNORE_QUAD_RR_CORNERS_OPT
reed@google.com4ed0fb72012-12-12 20:48:18 +00001104 } else if (rrect.isSimple()) {
1105 const SkVector& rad = rrect.getSimpleRadii();
1106 this->addRoundRect(bounds, rad.x(), rad.y(), dir);
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001107#endif
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
1114 this->incReserve(21);
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001115 if (kCW_Direction == dir) {
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001116 this->moveTo(bounds.fLeft,
1117 bounds.fBottom - rrect.fRadii[SkRRect::kLowerLeft_Corner].fY);
1118 add_corner_quads(this, rrect, SkRRect::kUpperLeft_Corner, dir);
1119 add_corner_quads(this, rrect, SkRRect::kUpperRight_Corner, dir);
1120 add_corner_quads(this, rrect, SkRRect::kLowerRight_Corner, dir);
1121 add_corner_quads(this, rrect, SkRRect::kLowerLeft_Corner, dir);
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001122 } else {
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001123 this->moveTo(bounds.fLeft,
1124 bounds.fTop + rrect.fRadii[SkRRect::kUpperLeft_Corner].fY);
1125 add_corner_quads(this, rrect, SkRRect::kLowerLeft_Corner, dir);
1126 add_corner_quads(this, rrect, SkRRect::kLowerRight_Corner, dir);
1127 add_corner_quads(this, rrect, SkRRect::kUpperRight_Corner, dir);
1128 add_corner_quads(this, rrect, SkRRect::kUpperLeft_Corner, dir);
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001129 }
1130 this->close();
reed@google.com4ed0fb72012-12-12 20:48:18 +00001131 }
1132}
1133
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001134bool SkPath::hasOnlyMoveTos() const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001135 int count = fPathRef->countVerbs();
1136 const uint8_t* verbs = const_cast<const SkPathRef*>(fPathRef.get())->verbsMemBegin();
1137 for (int i = 0; i < count; ++i) {
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001138 if (*verbs == kLine_Verb ||
1139 *verbs == kQuad_Verb ||
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001140 *verbs == kConic_Verb ||
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001141 *verbs == kCubic_Verb) {
1142 return false;
1143 }
1144 ++verbs;
1145 }
1146 return true;
1147}
1148
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001149#ifdef SK_IGNORE_QUAD_RR_CORNERS_OPT
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001150#define CUBIC_ARC_FACTOR ((SK_ScalarSqrt2 - SK_Scalar1) * 4 / 3)
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001151#endif
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001152
1153void SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
1154 Direction dir) {
1155 assert_known_direction(dir);
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001156
humper@google.com75e3ca12013-04-08 21:44:11 +00001157 if (rx < 0 || ry < 0) {
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001158 SkErrorInternals::SetError( kInvalidArgument_SkError,
humper@google.com75e3ca12013-04-08 21:44:11 +00001159 "I got %f and %f as radii to SkPath::AddRoundRect, "
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001160 "but negative radii are not allowed.",
humper@google.com75e3ca12013-04-08 21:44:11 +00001161 SkScalarToDouble(rx), SkScalarToDouble(ry) );
1162 return;
1163 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001164
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001165#ifdef SK_IGNORE_QUAD_RR_CORNERS_OPT
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001166 SkScalar w = rect.width();
1167 SkScalar halfW = SkScalarHalf(w);
1168 SkScalar h = rect.height();
1169 SkScalar halfH = SkScalarHalf(h);
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001170
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001171 if (halfW <= 0 || halfH <= 0) {
1172 return;
1173 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001174
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001175 bool skip_hori = rx >= halfW;
1176 bool skip_vert = ry >= halfH;
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001177
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001178 if (skip_hori && skip_vert) {
1179 this->addOval(rect, dir);
1180 return;
1181 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001182
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001183 fDirection = this->hasOnlyMoveTos() ? dir : kUnknown_Direction;
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001184
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001185 SkAutoPathBoundsUpdate apbu(this, rect);
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001186 SkAutoDisableDirectionCheck addc(this);
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001187
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001188 if (skip_hori) {
1189 rx = halfW;
1190 } else if (skip_vert) {
1191 ry = halfH;
1192 }
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001193 SkScalar sx = SkScalarMul(rx, CUBIC_ARC_FACTOR);
1194 SkScalar sy = SkScalarMul(ry, CUBIC_ARC_FACTOR);
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001195
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001196 this->incReserve(17);
robertphillips@google.com12367192013-10-20 13:11:16 +00001197 this->moveTo(rect.fRight - rx, rect.fTop); // top-right
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001198 if (dir == kCCW_Direction) {
1199 if (!skip_hori) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001200 this->lineTo(rect.fLeft + rx, rect.fTop); // top
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001201 }
1202 this->cubicTo(rect.fLeft + rx - sx, rect.fTop,
1203 rect.fLeft, rect.fTop + ry - sy,
1204 rect.fLeft, rect.fTop + ry); // top-left
1205 if (!skip_vert) {
1206 this->lineTo(rect.fLeft, rect.fBottom - ry); // left
1207 }
1208 this->cubicTo(rect.fLeft, rect.fBottom - ry + sy,
1209 rect.fLeft + rx - sx, rect.fBottom,
1210 rect.fLeft + rx, rect.fBottom); // bot-left
1211 if (!skip_hori) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001212 this->lineTo(rect.fRight - rx, rect.fBottom); // bottom
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001213 }
1214 this->cubicTo(rect.fRight - rx + sx, rect.fBottom,
1215 rect.fRight, rect.fBottom - ry + sy,
1216 rect.fRight, rect.fBottom - ry); // bot-right
1217 if (!skip_vert) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001218 this->lineTo(rect.fRight, rect.fTop + ry); // right
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001219 }
1220 this->cubicTo(rect.fRight, rect.fTop + ry - sy,
1221 rect.fRight - rx + sx, rect.fTop,
1222 rect.fRight - rx, rect.fTop); // top-right
1223 } else {
1224 this->cubicTo(rect.fRight - rx + sx, rect.fTop,
1225 rect.fRight, rect.fTop + ry - sy,
1226 rect.fRight, rect.fTop + ry); // top-right
1227 if (!skip_vert) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001228 this->lineTo(rect.fRight, rect.fBottom - ry); // right
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001229 }
1230 this->cubicTo(rect.fRight, rect.fBottom - ry + sy,
1231 rect.fRight - rx + sx, rect.fBottom,
1232 rect.fRight - rx, rect.fBottom); // bot-right
1233 if (!skip_hori) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001234 this->lineTo(rect.fLeft + rx, rect.fBottom); // bottom
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001235 }
1236 this->cubicTo(rect.fLeft + rx - sx, rect.fBottom,
1237 rect.fLeft, rect.fBottom - ry + sy,
1238 rect.fLeft, rect.fBottom - ry); // bot-left
1239 if (!skip_vert) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001240 this->lineTo(rect.fLeft, rect.fTop + ry); // left
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001241 }
1242 this->cubicTo(rect.fLeft, rect.fTop + ry - sy,
1243 rect.fLeft + rx - sx, rect.fTop,
1244 rect.fLeft + rx, rect.fTop); // top-left
1245 if (!skip_hori) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001246 this->lineTo(rect.fRight - rx, rect.fTop); // top
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001247 }
1248 }
1249 this->close();
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001250#else
1251 SkRRect rrect;
1252 rrect.setRectXY(rect, rx, ry);
1253 this->addRRect(rrect, dir);
1254#endif
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001255}
1256
reed@android.com8a1c16f2008-12-17 15:59:43 +00001257void SkPath::addOval(const SkRect& oval, Direction dir) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +00001258 assert_known_direction(dir);
1259
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001260 /* If addOval() is called after previous moveTo(),
1261 this path is still marked as an oval. This is used to
1262 fit into WebKit's calling sequences.
1263 We can't simply check isEmpty() in this case, as additional
1264 moveTo() would mark the path non empty.
1265 */
1266 fIsOval = hasOnlyMoveTos();
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001267 if (fIsOval) {
1268 fDirection = dir;
1269 } else {
1270 fDirection = kUnknown_Direction;
1271 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001272
1273 SkAutoDisableOvalCheck adoc(this);
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001274 SkAutoDisableDirectionCheck addc(this);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001275
reed@android.com8a1c16f2008-12-17 15:59:43 +00001276 SkAutoPathBoundsUpdate apbu(this, oval);
1277
1278 SkScalar cx = oval.centerX();
1279 SkScalar cy = oval.centerY();
1280 SkScalar rx = SkScalarHalf(oval.width());
1281 SkScalar ry = SkScalarHalf(oval.height());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001282
reed@android.com8a1c16f2008-12-17 15:59:43 +00001283 SkScalar sx = SkScalarMul(rx, SK_ScalarTanPIOver8);
1284 SkScalar sy = SkScalarMul(ry, SK_ScalarTanPIOver8);
1285 SkScalar mx = SkScalarMul(rx, SK_ScalarRoot2Over2);
1286 SkScalar my = SkScalarMul(ry, SK_ScalarRoot2Over2);
1287
1288 /*
1289 To handle imprecision in computing the center and radii, we revert to
1290 the provided bounds when we can (i.e. use oval.fLeft instead of cx-rx)
1291 to ensure that we don't exceed the oval's bounds *ever*, since we want
1292 to use oval for our fast-bounds, rather than have to recompute it.
1293 */
1294 const SkScalar L = oval.fLeft; // cx - rx
1295 const SkScalar T = oval.fTop; // cy - ry
1296 const SkScalar R = oval.fRight; // cx + rx
1297 const SkScalar B = oval.fBottom; // cy + ry
1298
1299 this->incReserve(17); // 8 quads + close
1300 this->moveTo(R, cy);
1301 if (dir == kCCW_Direction) {
1302 this->quadTo( R, cy - sy, cx + mx, cy - my);
1303 this->quadTo(cx + sx, T, cx , T);
1304 this->quadTo(cx - sx, T, cx - mx, cy - my);
1305 this->quadTo( L, cy - sy, L, cy );
1306 this->quadTo( L, cy + sy, cx - mx, cy + my);
1307 this->quadTo(cx - sx, B, cx , B);
1308 this->quadTo(cx + sx, B, cx + mx, cy + my);
1309 this->quadTo( R, cy + sy, R, cy );
1310 } else {
1311 this->quadTo( R, cy + sy, cx + mx, cy + my);
1312 this->quadTo(cx + sx, B, cx , B);
1313 this->quadTo(cx - sx, B, cx - mx, cy + my);
1314 this->quadTo( L, cy + sy, L, cy );
1315 this->quadTo( L, cy - sy, cx - mx, cy - my);
1316 this->quadTo(cx - sx, T, cx , T);
1317 this->quadTo(cx + sx, T, cx + mx, cy - my);
1318 this->quadTo( R, cy - sy, R, cy );
1319 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001320 this->close();
1321}
1322
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001323bool SkPath::isOval(SkRect* rect) const {
1324 if (fIsOval && rect) {
1325 *rect = getBounds();
1326 }
1327
1328 return fIsOval;
1329}
1330
reed@android.com8a1c16f2008-12-17 15:59:43 +00001331void SkPath::addCircle(SkScalar x, SkScalar y, SkScalar r, Direction dir) {
1332 if (r > 0) {
1333 SkRect rect;
1334 rect.set(x - r, y - r, x + r, y + r);
1335 this->addOval(rect, dir);
1336 }
1337}
1338
reed@android.com8a1c16f2008-12-17 15:59:43 +00001339void SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
1340 bool forceMoveTo) {
1341 if (oval.width() < 0 || oval.height() < 0) {
1342 return;
1343 }
1344
1345 SkPoint pts[kSkBuildQuadArcStorage];
1346 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
1347 SkASSERT((count & 1) == 1);
1348
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001349 if (fPathRef->countVerbs() == 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001350 forceMoveTo = true;
1351 }
1352 this->incReserve(count);
1353 forceMoveTo ? this->moveTo(pts[0]) : this->lineTo(pts[0]);
1354 for (int i = 1; i < count; i += 2) {
1355 this->quadTo(pts[i], pts[i+1]);
1356 }
1357}
1358
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001359void SkPath::addArc(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001360 if (oval.isEmpty() || 0 == sweepAngle) {
1361 return;
1362 }
1363
1364 const SkScalar kFullCircleAngle = SkIntToScalar(360);
1365
1366 if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
1367 this->addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction);
1368 return;
1369 }
1370
1371 SkPoint pts[kSkBuildQuadArcStorage];
1372 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
1373
1374 this->incReserve(count);
1375 this->moveTo(pts[0]);
1376 for (int i = 1; i < count; i += 2) {
1377 this->quadTo(pts[i], pts[i+1]);
1378 }
1379}
1380
1381/*
1382 Need to handle the case when the angle is sharp, and our computed end-points
1383 for the arc go behind pt1 and/or p2...
1384*/
1385void SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
1386 SkScalar radius) {
1387 SkVector before, after;
reed@google.comabf15c12011-01-18 20:35:51 +00001388
reed@android.com8a1c16f2008-12-17 15:59:43 +00001389 // need to know our prev pt so we can construct tangent vectors
1390 {
1391 SkPoint start;
1392 this->getLastPt(&start);
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001393 // Handle degenerate cases by adding a line to the first point and
1394 // bailing out.
1395 if ((x1 == start.fX && y1 == start.fY) ||
1396 (x1 == x2 && y1 == y2) ||
1397 radius == 0) {
1398 this->lineTo(x1, y1);
1399 return;
1400 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001401 before.setNormalize(x1 - start.fX, y1 - start.fY);
1402 after.setNormalize(x2 - x1, y2 - y1);
1403 }
reed@google.comabf15c12011-01-18 20:35:51 +00001404
reed@android.com8a1c16f2008-12-17 15:59:43 +00001405 SkScalar cosh = SkPoint::DotProduct(before, after);
1406 SkScalar sinh = SkPoint::CrossProduct(before, after);
1407
1408 if (SkScalarNearlyZero(sinh)) { // angle is too tight
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001409 this->lineTo(x1, y1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001410 return;
1411 }
reed@google.comabf15c12011-01-18 20:35:51 +00001412
reed@android.com8a1c16f2008-12-17 15:59:43 +00001413 SkScalar dist = SkScalarMulDiv(radius, SK_Scalar1 - cosh, sinh);
1414 if (dist < 0) {
1415 dist = -dist;
1416 }
1417
1418 SkScalar xx = x1 - SkScalarMul(dist, before.fX);
1419 SkScalar yy = y1 - SkScalarMul(dist, before.fY);
1420 SkRotationDirection arcDir;
1421
1422 // now turn before/after into normals
1423 if (sinh > 0) {
1424 before.rotateCCW();
1425 after.rotateCCW();
1426 arcDir = kCW_SkRotationDirection;
1427 } else {
1428 before.rotateCW();
1429 after.rotateCW();
1430 arcDir = kCCW_SkRotationDirection;
1431 }
1432
1433 SkMatrix matrix;
1434 SkPoint pts[kSkBuildQuadArcStorage];
reed@google.comabf15c12011-01-18 20:35:51 +00001435
reed@android.com8a1c16f2008-12-17 15:59:43 +00001436 matrix.setScale(radius, radius);
1437 matrix.postTranslate(xx - SkScalarMul(radius, before.fX),
1438 yy - SkScalarMul(radius, before.fY));
reed@google.comabf15c12011-01-18 20:35:51 +00001439
reed@android.com8a1c16f2008-12-17 15:59:43 +00001440 int count = SkBuildQuadArc(before, after, arcDir, &matrix, pts);
reed@google.comabf15c12011-01-18 20:35:51 +00001441
reed@android.com8a1c16f2008-12-17 15:59:43 +00001442 this->incReserve(count);
1443 // [xx,yy] == pts[0]
1444 this->lineTo(xx, yy);
1445 for (int i = 1; i < count; i += 2) {
1446 this->quadTo(pts[i], pts[i+1]);
1447 }
1448}
1449
1450///////////////////////////////////////////////////////////////////////////////
1451
1452void SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy) {
1453 SkMatrix matrix;
1454
1455 matrix.setTranslate(dx, dy);
1456 this->addPath(path, matrix);
1457}
1458
1459void SkPath::addPath(const SkPath& path, const SkMatrix& matrix) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001460 SkPathRef::Editor(&fPathRef, path.countVerbs(), path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001461
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001462 fIsOval = false;
1463
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001464 RawIter iter(path);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001465 SkPoint pts[4];
1466 Verb verb;
1467
1468 SkMatrix::MapPtsProc proc = matrix.getMapPtsProc();
1469
1470 while ((verb = iter.next(pts)) != kDone_Verb) {
1471 switch (verb) {
1472 case kMove_Verb:
1473 proc(matrix, &pts[0], &pts[0], 1);
1474 this->moveTo(pts[0]);
1475 break;
1476 case kLine_Verb:
1477 proc(matrix, &pts[1], &pts[1], 1);
1478 this->lineTo(pts[1]);
1479 break;
1480 case kQuad_Verb:
1481 proc(matrix, &pts[1], &pts[1], 2);
1482 this->quadTo(pts[1], pts[2]);
1483 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001484 case kConic_Verb:
1485 proc(matrix, &pts[1], &pts[1], 2);
1486 this->conicTo(pts[1], pts[2], iter.conicWeight());
1487 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001488 case kCubic_Verb:
1489 proc(matrix, &pts[1], &pts[1], 3);
1490 this->cubicTo(pts[1], pts[2], pts[3]);
1491 break;
1492 case kClose_Verb:
1493 this->close();
1494 break;
1495 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001496 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001497 }
1498 }
1499}
1500
1501///////////////////////////////////////////////////////////////////////////////
1502
reed@google.com277c3f82013-05-31 15:17:50 +00001503static int pts_in_verb(unsigned verb) {
1504 static const uint8_t gPtsInVerb[] = {
1505 1, // kMove
1506 1, // kLine
1507 2, // kQuad
1508 2, // kConic
1509 3, // kCubic
1510 0, // kClose
1511 0 // kDone
1512 };
1513
1514 SkASSERT(verb < SK_ARRAY_COUNT(gPtsInVerb));
1515 return gPtsInVerb[verb];
1516}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001517
reed@android.com8a1c16f2008-12-17 15:59:43 +00001518// ignore the last point of the 1st contour
1519void SkPath::reversePathTo(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001520 int i, vcount = path.fPathRef->countVerbs();
1521 // exit early if the path is empty, or just has a moveTo.
1522 if (vcount < 2) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001523 return;
1524 }
1525
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001526 SkPathRef::Editor(&fPathRef, vcount, path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001527
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001528 fIsOval = false;
1529
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001530 const uint8_t* verbs = path.fPathRef->verbs();
1531 const SkPoint* pts = path.fPathRef->points();
reed@google.com277c3f82013-05-31 15:17:50 +00001532 const SkScalar* conicWeights = path.fPathRef->conicWeights();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001533
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001534 SkASSERT(verbs[~0] == kMove_Verb);
1535 for (i = 1; i < vcount; ++i) {
reed@google.com277c3f82013-05-31 15:17:50 +00001536 unsigned v = verbs[~i];
1537 int n = pts_in_verb(v);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001538 if (n == 0) {
1539 break;
1540 }
1541 pts += n;
reed@google.com277c3f82013-05-31 15:17:50 +00001542 conicWeights += (SkPath::kConic_Verb == v);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001543 }
1544
1545 while (--i > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001546 switch (verbs[~i]) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001547 case kLine_Verb:
1548 this->lineTo(pts[-1].fX, pts[-1].fY);
1549 break;
1550 case kQuad_Verb:
1551 this->quadTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY);
1552 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001553 case kConic_Verb:
1554 this->conicTo(pts[-1], pts[-2], *--conicWeights);
1555 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001556 case kCubic_Verb:
1557 this->cubicTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY,
1558 pts[-3].fX, pts[-3].fY);
1559 break;
1560 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001561 SkDEBUGFAIL("bad verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001562 break;
1563 }
reed@google.com277c3f82013-05-31 15:17:50 +00001564 pts -= pts_in_verb(verbs[~i]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001565 }
1566}
1567
reed@google.com63d73742012-01-10 15:33:12 +00001568void SkPath::reverseAddPath(const SkPath& src) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001569 SkPathRef::Editor ed(&fPathRef, src.fPathRef->countPoints(), src.fPathRef->countVerbs());
reed@google.com63d73742012-01-10 15:33:12 +00001570
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001571 const SkPoint* pts = src.fPathRef->pointsEnd();
1572 // we will iterator through src's verbs backwards
1573 const uint8_t* verbs = src.fPathRef->verbsMemBegin(); // points at the last verb
1574 const uint8_t* verbsEnd = src.fPathRef->verbs(); // points just past the first verb
reed@google.com277c3f82013-05-31 15:17:50 +00001575 const SkScalar* conicWeights = src.fPathRef->conicWeightsEnd();
reed@google.com63d73742012-01-10 15:33:12 +00001576
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001577 fIsOval = false;
1578
reed@google.com63d73742012-01-10 15:33:12 +00001579 bool needMove = true;
1580 bool needClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001581 while (verbs < verbsEnd) {
1582 uint8_t v = *(verbs++);
reed@google.com277c3f82013-05-31 15:17:50 +00001583 int n = pts_in_verb(v);
reed@google.com63d73742012-01-10 15:33:12 +00001584
1585 if (needMove) {
1586 --pts;
1587 this->moveTo(pts->fX, pts->fY);
1588 needMove = false;
1589 }
1590 pts -= n;
1591 switch (v) {
1592 case kMove_Verb:
1593 if (needClose) {
1594 this->close();
1595 needClose = false;
1596 }
1597 needMove = true;
1598 pts += 1; // so we see the point in "if (needMove)" above
1599 break;
1600 case kLine_Verb:
1601 this->lineTo(pts[0]);
1602 break;
1603 case kQuad_Verb:
1604 this->quadTo(pts[1], pts[0]);
1605 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001606 case kConic_Verb:
1607 this->conicTo(pts[1], pts[0], *--conicWeights);
1608 break;
reed@google.com63d73742012-01-10 15:33:12 +00001609 case kCubic_Verb:
1610 this->cubicTo(pts[2], pts[1], pts[0]);
1611 break;
1612 case kClose_Verb:
1613 needClose = true;
1614 break;
1615 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00001616 SkDEBUGFAIL("unexpected verb");
reed@google.com63d73742012-01-10 15:33:12 +00001617 }
1618 }
1619}
1620
reed@android.com8a1c16f2008-12-17 15:59:43 +00001621///////////////////////////////////////////////////////////////////////////////
1622
1623void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
1624 SkMatrix matrix;
1625
1626 matrix.setTranslate(dx, dy);
1627 this->transform(matrix, dst);
1628}
1629
1630#include "SkGeometry.h"
1631
1632static void subdivide_quad_to(SkPath* path, const SkPoint pts[3],
1633 int level = 2) {
1634 if (--level >= 0) {
1635 SkPoint tmp[5];
1636
1637 SkChopQuadAtHalf(pts, tmp);
1638 subdivide_quad_to(path, &tmp[0], level);
1639 subdivide_quad_to(path, &tmp[2], level);
1640 } else {
1641 path->quadTo(pts[1], pts[2]);
1642 }
1643}
1644
1645static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
1646 int level = 2) {
1647 if (--level >= 0) {
1648 SkPoint tmp[7];
1649
1650 SkChopCubicAtHalf(pts, tmp);
1651 subdivide_cubic_to(path, &tmp[0], level);
1652 subdivide_cubic_to(path, &tmp[3], level);
1653 } else {
1654 path->cubicTo(pts[1], pts[2], pts[3]);
1655 }
1656}
1657
1658void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
1659 SkDEBUGCODE(this->validate();)
1660 if (dst == NULL) {
1661 dst = (SkPath*)this;
1662 }
1663
tomhudson@google.com8d430182011-06-06 19:11:19 +00001664 if (matrix.hasPerspective()) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001665 SkPath tmp;
1666 tmp.fFillType = fFillType;
1667
1668 SkPath::Iter iter(*this, false);
1669 SkPoint pts[4];
1670 SkPath::Verb verb;
1671
reed@google.com4a3b7142012-05-16 17:16:46 +00001672 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001673 switch (verb) {
1674 case kMove_Verb:
1675 tmp.moveTo(pts[0]);
1676 break;
1677 case kLine_Verb:
1678 tmp.lineTo(pts[1]);
1679 break;
1680 case kQuad_Verb:
1681 subdivide_quad_to(&tmp, pts);
1682 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001683 case kConic_Verb:
mtklein@google.com330313a2013-08-22 15:37:26 +00001684 SkDEBUGFAIL("TODO: compute new weight");
reed@google.com277c3f82013-05-31 15:17:50 +00001685 tmp.conicTo(pts[1], pts[2], iter.conicWeight());
1686 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001687 case kCubic_Verb:
1688 subdivide_cubic_to(&tmp, pts);
1689 break;
1690 case kClose_Verb:
1691 tmp.close();
1692 break;
1693 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001694 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001695 break;
1696 }
1697 }
1698
1699 dst->swap(tmp);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001700 SkPathRef::Editor ed(&dst->fPathRef);
1701 matrix.mapPoints(ed.points(), ed.pathRef()->countPoints());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001702 dst->fDirection = kUnknown_Direction;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001703 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001704 SkPathRef::CreateTransformedCopy(&dst->fPathRef, *fPathRef.get(), matrix);
1705
reed@android.com8a1c16f2008-12-17 15:59:43 +00001706 if (this != dst) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001707 dst->fFillType = fFillType;
reed@google.comb3b0a5b2011-09-21 12:58:51 +00001708 dst->fSegmentMask = fSegmentMask;
reed@google.com2a6f8ab2011-10-25 18:41:23 +00001709 dst->fConvexity = fConvexity;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001710 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001711
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001712 if (kUnknown_Direction == fDirection) {
1713 dst->fDirection = kUnknown_Direction;
1714 } else {
1715 SkScalar det2x2 =
1716 SkScalarMul(matrix.get(SkMatrix::kMScaleX), matrix.get(SkMatrix::kMScaleY)) -
1717 SkScalarMul(matrix.get(SkMatrix::kMSkewX), matrix.get(SkMatrix::kMSkewY));
1718 if (det2x2 < 0) {
1719 dst->fDirection = SkPath::OppositeDirection(static_cast<Direction>(fDirection));
1720 } else if (det2x2 > 0) {
1721 dst->fDirection = fDirection;
1722 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001723 dst->fConvexity = kUnknown_Convexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001724 dst->fDirection = kUnknown_Direction;
1725 }
1726 }
1727
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001728 // It's an oval only if it stays a rect.
1729 dst->fIsOval = fIsOval && matrix.rectStaysRect();
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001730
reed@android.com8a1c16f2008-12-17 15:59:43 +00001731 SkDEBUGCODE(dst->validate();)
1732 }
1733}
1734
reed@android.com8a1c16f2008-12-17 15:59:43 +00001735///////////////////////////////////////////////////////////////////////////////
1736///////////////////////////////////////////////////////////////////////////////
1737
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001738enum SegmentState {
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001739 kEmptyContour_SegmentState, // The current contour is empty. We may be
1740 // starting processing or we may have just
1741 // closed a contour.
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001742 kAfterMove_SegmentState, // We have seen a move, but nothing else.
1743 kAfterPrimitive_SegmentState // We have seen a primitive but not yet
1744 // closed the path. Also the initial state.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001745};
1746
1747SkPath::Iter::Iter() {
1748#ifdef SK_DEBUG
1749 fPts = NULL;
reed@google.com277c3f82013-05-31 15:17:50 +00001750 fConicWeights = NULL;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001751 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001752 fForceClose = fCloseLine = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001753 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001754#endif
1755 // need to init enough to make next() harmlessly return kDone_Verb
1756 fVerbs = NULL;
1757 fVerbStop = NULL;
1758 fNeedClose = false;
1759}
1760
1761SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
1762 this->setPath(path, forceClose);
1763}
1764
1765void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001766 fPts = path.fPathRef->points();
1767 fVerbs = path.fPathRef->verbs();
1768 fVerbStop = path.fPathRef->verbsMemBegin();
reed@google.com277c3f82013-05-31 15:17:50 +00001769 fConicWeights = path.fPathRef->conicWeights() - 1; // begin one behind
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001770 fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org72785c42011-12-29 21:03:28 +00001771 fMoveTo.fX = fMoveTo.fY = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001772 fForceClose = SkToU8(forceClose);
1773 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001774 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001775}
1776
1777bool SkPath::Iter::isClosedContour() const {
1778 if (fVerbs == NULL || fVerbs == fVerbStop) {
1779 return false;
1780 }
1781 if (fForceClose) {
1782 return true;
1783 }
1784
1785 const uint8_t* verbs = fVerbs;
1786 const uint8_t* stop = fVerbStop;
reed@google.comabf15c12011-01-18 20:35:51 +00001787
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001788 if (kMove_Verb == *(verbs - 1)) {
1789 verbs -= 1; // skip the initial moveto
reed@android.com8a1c16f2008-12-17 15:59:43 +00001790 }
1791
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001792 while (verbs > stop) {
1793 // verbs points one beyond the current verb, decrement first.
1794 unsigned v = *(--verbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001795 if (kMove_Verb == v) {
1796 break;
1797 }
1798 if (kClose_Verb == v) {
1799 return true;
1800 }
1801 }
1802 return false;
1803}
1804
1805SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001806 SkASSERT(pts);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001807 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001808 // A special case: if both points are NaN, SkPoint::operation== returns
1809 // false, but the iterator expects that they are treated as the same.
1810 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001811 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1812 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001813 return kClose_Verb;
1814 }
1815
reed@google.com9e25dbf2012-05-15 17:05:38 +00001816 pts[0] = fLastPt;
1817 pts[1] = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001818 fLastPt = fMoveTo;
1819 fCloseLine = true;
1820 return kLine_Verb;
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +00001821 } else {
1822 pts[0] = fMoveTo;
1823 return kClose_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001824 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001825}
1826
reed@google.com9e25dbf2012-05-15 17:05:38 +00001827const SkPoint& SkPath::Iter::cons_moveTo() {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001828 if (fSegmentState == kAfterMove_SegmentState) {
1829 // Set the first return pt to the move pt
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001830 fSegmentState = kAfterPrimitive_SegmentState;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001831 return fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001832 } else {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001833 SkASSERT(fSegmentState == kAfterPrimitive_SegmentState);
1834 // Set the first return pt to the last pt of the previous primitive.
reed@google.com9e25dbf2012-05-15 17:05:38 +00001835 return fPts[-1];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001836 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001837}
1838
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001839void SkPath::Iter::consumeDegenerateSegments() {
1840 // We need to step over anything that will not move the current draw point
1841 // forward before the next move is seen
1842 const uint8_t* lastMoveVerb = 0;
1843 const SkPoint* lastMovePt = 0;
1844 SkPoint lastPt = fLastPt;
1845 while (fVerbs != fVerbStop) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001846 unsigned verb = *(fVerbs - 1); // fVerbs is one beyond the current verb
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001847 switch (verb) {
1848 case kMove_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001849 // Keep a record of this most recent move
1850 lastMoveVerb = fVerbs;
1851 lastMovePt = fPts;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001852 lastPt = fPts[0];
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001853 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001854 fPts++;
1855 break;
1856
1857 case kClose_Verb:
schenney@chromium.org7e963602012-06-13 17:05:43 +00001858 // A close when we are in a segment is always valid except when it
1859 // follows a move which follows a segment.
1860 if (fSegmentState == kAfterPrimitive_SegmentState && !lastMoveVerb) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001861 return;
1862 }
1863 // A close at any other time must be ignored
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001864 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001865 break;
1866
1867 case kLine_Verb:
1868 if (!IsLineDegenerate(lastPt, fPts[0])) {
1869 if (lastMoveVerb) {
1870 fVerbs = lastMoveVerb;
1871 fPts = lastMovePt;
1872 return;
1873 }
1874 return;
1875 }
1876 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001877 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001878 fPts++;
1879 break;
1880
reed@google.com277c3f82013-05-31 15:17:50 +00001881 case kConic_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001882 case kQuad_Verb:
1883 if (!IsQuadDegenerate(lastPt, fPts[0], fPts[1])) {
1884 if (lastMoveVerb) {
1885 fVerbs = lastMoveVerb;
1886 fPts = lastMovePt;
1887 return;
1888 }
1889 return;
1890 }
1891 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001892 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001893 fPts += 2;
reed@google.com277c3f82013-05-31 15:17:50 +00001894 fConicWeights += (kConic_Verb == verb);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001895 break;
1896
1897 case kCubic_Verb:
1898 if (!IsCubicDegenerate(lastPt, fPts[0], fPts[1], fPts[2])) {
1899 if (lastMoveVerb) {
1900 fVerbs = lastMoveVerb;
1901 fPts = lastMovePt;
1902 return;
1903 }
1904 return;
1905 }
1906 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001907 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001908 fPts += 3;
1909 break;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001910
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001911 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001912 SkDEBUGFAIL("Should never see kDone_Verb");
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001913 }
1914 }
1915}
1916
reed@google.com4a3b7142012-05-16 17:16:46 +00001917SkPath::Verb SkPath::Iter::doNext(SkPoint ptsParam[4]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001918 SkASSERT(ptsParam);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001919
reed@android.com8a1c16f2008-12-17 15:59:43 +00001920 if (fVerbs == fVerbStop) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001921 // Close the curve if requested and if there is some curve to close
1922 if (fNeedClose && fSegmentState == kAfterPrimitive_SegmentState) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001923 if (kLine_Verb == this->autoClose(ptsParam)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001924 return kLine_Verb;
1925 }
1926 fNeedClose = false;
1927 return kClose_Verb;
1928 }
1929 return kDone_Verb;
1930 }
1931
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001932 // fVerbs is one beyond the current verb, decrement first
1933 unsigned verb = *(--fVerbs);
reed@google.com9e25dbf2012-05-15 17:05:38 +00001934 const SkPoint* SK_RESTRICT srcPts = fPts;
1935 SkPoint* SK_RESTRICT pts = ptsParam;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001936
1937 switch (verb) {
1938 case kMove_Verb:
1939 if (fNeedClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001940 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001941 verb = this->autoClose(pts);
1942 if (verb == kClose_Verb) {
1943 fNeedClose = false;
1944 }
1945 return (Verb)verb;
1946 }
1947 if (fVerbs == fVerbStop) { // might be a trailing moveto
1948 return kDone_Verb;
1949 }
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001950 fMoveTo = *srcPts;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001951 pts[0] = *srcPts;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001952 srcPts += 1;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001953 fSegmentState = kAfterMove_SegmentState;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001954 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001955 fNeedClose = fForceClose;
1956 break;
1957 case kLine_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001958 pts[0] = this->cons_moveTo();
1959 pts[1] = srcPts[0];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001960 fLastPt = srcPts[0];
1961 fCloseLine = false;
1962 srcPts += 1;
1963 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001964 case kConic_Verb:
1965 fConicWeights += 1;
1966 // fall-through
reed@android.com8a1c16f2008-12-17 15:59:43 +00001967 case kQuad_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001968 pts[0] = this->cons_moveTo();
1969 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001970 fLastPt = srcPts[1];
1971 srcPts += 2;
1972 break;
1973 case kCubic_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001974 pts[0] = this->cons_moveTo();
1975 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001976 fLastPt = srcPts[2];
1977 srcPts += 3;
1978 break;
1979 case kClose_Verb:
1980 verb = this->autoClose(pts);
1981 if (verb == kLine_Verb) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001982 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001983 } else {
1984 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001985 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001986 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001987 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001988 break;
1989 }
1990 fPts = srcPts;
1991 return (Verb)verb;
1992}
1993
1994///////////////////////////////////////////////////////////////////////////////
1995
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001996SkPath::RawIter::RawIter() {
1997#ifdef SK_DEBUG
1998 fPts = NULL;
reed@google.com277c3f82013-05-31 15:17:50 +00001999 fConicWeights = NULL;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002000 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
2001#endif
2002 // need to init enough to make next() harmlessly return kDone_Verb
2003 fVerbs = NULL;
2004 fVerbStop = NULL;
2005}
2006
2007SkPath::RawIter::RawIter(const SkPath& path) {
2008 this->setPath(path);
2009}
2010
2011void SkPath::RawIter::setPath(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002012 fPts = path.fPathRef->points();
2013 fVerbs = path.fPathRef->verbs();
2014 fVerbStop = path.fPathRef->verbsMemBegin();
reed@google.com277c3f82013-05-31 15:17:50 +00002015 fConicWeights = path.fPathRef->conicWeights() - 1; // begin one behind
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002016 fMoveTo.fX = fMoveTo.fY = 0;
2017 fLastPt.fX = fLastPt.fY = 0;
2018}
2019
2020SkPath::Verb SkPath::RawIter::next(SkPoint pts[4]) {
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002021 SkASSERT(NULL != pts);
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002022 if (fVerbs == fVerbStop) {
2023 return kDone_Verb;
2024 }
2025
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002026 // fVerbs points one beyond next verb so decrement first.
2027 unsigned verb = *(--fVerbs);
2028 const SkPoint* srcPts = fPts;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002029
2030 switch (verb) {
2031 case kMove_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002032 pts[0] = *srcPts;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002033 fMoveTo = srcPts[0];
2034 fLastPt = fMoveTo;
2035 srcPts += 1;
2036 break;
2037 case kLine_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002038 pts[0] = fLastPt;
2039 pts[1] = srcPts[0];
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002040 fLastPt = srcPts[0];
2041 srcPts += 1;
2042 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002043 case kConic_Verb:
2044 fConicWeights += 1;
2045 // fall-through
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002046 case kQuad_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002047 pts[0] = fLastPt;
2048 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002049 fLastPt = srcPts[1];
2050 srcPts += 2;
2051 break;
2052 case kCubic_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002053 pts[0] = fLastPt;
2054 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002055 fLastPt = srcPts[2];
2056 srcPts += 3;
2057 break;
2058 case kClose_Verb:
2059 fLastPt = fMoveTo;
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002060 pts[0] = fMoveTo;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002061 break;
2062 }
2063 fPts = srcPts;
2064 return (Verb)verb;
2065}
2066
2067///////////////////////////////////////////////////////////////////////////////
2068
reed@android.com8a1c16f2008-12-17 15:59:43 +00002069/*
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002070 Format in compressed buffer: [ptCount, verbCount, pts[], verbs[]]
reed@android.com8a1c16f2008-12-17 15:59:43 +00002071*/
2072
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002073size_t SkPath::writeToMemory(void* storage) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002074 SkDEBUGCODE(this->validate();)
2075
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002076 if (NULL == storage) {
robertphillips@google.comca0c8382013-09-26 12:18:23 +00002077 const int byteCount = sizeof(int32_t) + fPathRef->writeSize();
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002078 return SkAlign4(byteCount);
2079 }
2080
2081 SkWBuffer buffer(storage);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002082
robertphillips@google.comca0c8382013-09-26 12:18:23 +00002083 int32_t packed = ((fIsOval & 1) << kIsOval_SerializationShift) |
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002084 (fConvexity << kConvexity_SerializationShift) |
2085 (fFillType << kFillType_SerializationShift) |
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002086 (fSegmentMask << kSegmentMask_SerializationShift) |
robertphillips@google.comca0c8382013-09-26 12:18:23 +00002087 (fDirection << kDirection_SerializationShift)
2088#ifndef DELETE_THIS_CODE_WHEN_SKPS_ARE_REBUILT_AT_V14_AND_ALL_OTHER_INSTANCES_TOO
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00002089 | (0x1 << kNewFormat_SerializationShift)
robertphillips@google.comca0c8382013-09-26 12:18:23 +00002090#endif
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00002091 ;
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002092
robertphillips@google.com2972bb52012-08-07 17:32:51 +00002093 buffer.write32(packed);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002094
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002095 fPathRef->writeToBuffer(&buffer);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002096
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002097 buffer.padToAlign4();
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002098 return buffer.pos();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002099}
2100
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002101size_t SkPath::readFromMemory(const void* storage, size_t length) {
2102 SkRBufferWithSizeCheck buffer(storage, length);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002103
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00002104 int32_t packed;
2105 if (!buffer.readS32(&packed)) {
2106 return 0;
2107 }
2108
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002109 fIsOval = (packed >> kIsOval_SerializationShift) & 1;
2110 fConvexity = (packed >> kConvexity_SerializationShift) & 0xFF;
2111 fFillType = (packed >> kFillType_SerializationShift) & 0xFF;
reed@google.com277c3f82013-05-31 15:17:50 +00002112 fSegmentMask = (packed >> kSegmentMask_SerializationShift) & 0xF;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002113 fDirection = (packed >> kDirection_SerializationShift) & 0x3;
robertphillips@google.comca0c8382013-09-26 12:18:23 +00002114#ifndef DELETE_THIS_CODE_WHEN_SKPS_ARE_REBUILT_AT_V14_AND_ALL_OTHER_INSTANCES_TOO
2115 bool newFormat = (packed >> kNewFormat_SerializationShift) & 1;
2116#endif
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002117
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00002118 SkPathRef* pathRef = SkPathRef::CreateFromBuffer(&buffer
robertphillips@google.comca0c8382013-09-26 12:18:23 +00002119#ifndef DELETE_THIS_CODE_WHEN_SKPS_ARE_REBUILT_AT_V14_AND_ALL_OTHER_INSTANCES_TOO
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002120 , newFormat, packed
robertphillips@google.comca0c8382013-09-26 12:18:23 +00002121#endif
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00002122 );
reed@google.comabf15c12011-01-18 20:35:51 +00002123
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002124 size_t sizeRead = 0;
2125 if (buffer.isValid()) {
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00002126 fPathRef.reset(pathRef);
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002127 SkDEBUGCODE(this->validate();)
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00002128 buffer.skipToAlign4();
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002129 sizeRead = buffer.pos();
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00002130 } else if (NULL != pathRef) {
2131 // If the buffer is not valid, pathRef should be NULL
2132 sk_throw();
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002133 }
2134 return sizeRead;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002135}
2136
2137///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00002138
reed@google.com51bbe752013-01-17 22:07:50 +00002139#include "SkString.h"
2140
2141static void append_scalar(SkString* str, SkScalar value) {
2142 SkString tmp;
2143 tmp.printf("%g", value);
2144 if (tmp.contains('.')) {
2145 tmp.appendUnichar('f');
2146 }
2147 str->append(tmp);
2148}
2149
2150static void append_params(SkString* str, const char label[], const SkPoint pts[],
reed@google.com277c3f82013-05-31 15:17:50 +00002151 int count, SkScalar conicWeight = -1) {
reed@google.com51bbe752013-01-17 22:07:50 +00002152 str->append(label);
2153 str->append("(");
skia.committer@gmail.com15dd3002013-01-18 07:07:28 +00002154
reed@google.com51bbe752013-01-17 22:07:50 +00002155 const SkScalar* values = &pts[0].fX;
2156 count *= 2;
2157
2158 for (int i = 0; i < count; ++i) {
2159 append_scalar(str, values[i]);
2160 if (i < count - 1) {
2161 str->append(", ");
2162 }
2163 }
reed@google.com277c3f82013-05-31 15:17:50 +00002164 if (conicWeight >= 0) {
2165 str->append(", ");
2166 append_scalar(str, conicWeight);
2167 }
reed@google.com51bbe752013-01-17 22:07:50 +00002168 str->append(");\n");
2169}
2170
reed@android.com8a1c16f2008-12-17 15:59:43 +00002171void SkPath::dump(bool forceClose, const char title[]) const {
2172 Iter iter(*this, forceClose);
2173 SkPoint pts[4];
2174 Verb verb;
2175
2176 SkDebugf("path: forceClose=%s %s\n", forceClose ? "true" : "false",
2177 title ? title : "");
2178
reed@google.com51bbe752013-01-17 22:07:50 +00002179 SkString builder;
2180
reed@google.com4a3b7142012-05-16 17:16:46 +00002181 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002182 switch (verb) {
2183 case kMove_Verb:
reed@google.com51bbe752013-01-17 22:07:50 +00002184 append_params(&builder, "path.moveTo", &pts[0], 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002185 break;
2186 case kLine_Verb:
reed@google.com51bbe752013-01-17 22:07:50 +00002187 append_params(&builder, "path.lineTo", &pts[1], 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002188 break;
2189 case kQuad_Verb:
reed@google.com51bbe752013-01-17 22:07:50 +00002190 append_params(&builder, "path.quadTo", &pts[1], 2);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002191 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002192 case kConic_Verb:
2193 append_params(&builder, "path.conicTo", &pts[1], 2, iter.conicWeight());
2194 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002195 case kCubic_Verb:
reed@google.com51bbe752013-01-17 22:07:50 +00002196 append_params(&builder, "path.cubicTo", &pts[1], 3);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002197 break;
2198 case kClose_Verb:
reed@google.comf919b722013-01-18 17:53:36 +00002199 builder.append("path.close();\n");
reed@android.com8a1c16f2008-12-17 15:59:43 +00002200 break;
2201 default:
2202 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
2203 verb = kDone_Verb; // stop the loop
2204 break;
2205 }
2206 }
reed@google.com51bbe752013-01-17 22:07:50 +00002207 SkDebugf("%s\n", builder.c_str());
reed@android.com8a1c16f2008-12-17 15:59:43 +00002208}
2209
reed@android.come522ca52009-11-23 20:10:41 +00002210void SkPath::dump() const {
2211 this->dump(false);
2212}
2213
2214#ifdef SK_DEBUG
2215void SkPath::validate() const {
2216 SkASSERT(this != NULL);
2217 SkASSERT((fFillType & ~3) == 0);
reed@google.comabf15c12011-01-18 20:35:51 +00002218
djsollen@google.com077348c2012-10-22 20:23:32 +00002219#ifdef SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002220 if (!fBoundsIsDirty) {
2221 SkRect bounds;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002222
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002223 bool isFinite = compute_pt_bounds(&bounds, *fPathRef.get());
robertphillips@google.com5d8d1862012-08-15 14:36:41 +00002224 SkASSERT(SkToBool(fIsFinite) == isFinite);
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002225
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002226 if (fPathRef->countPoints() <= 1) {
reed@android.come522ca52009-11-23 20:10:41 +00002227 // if we're empty, fBounds may be empty but translated, so we can't
2228 // necessarily compare to bounds directly
2229 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
2230 // be [2, 2, 2, 2]
2231 SkASSERT(bounds.isEmpty());
2232 SkASSERT(fBounds.isEmpty());
2233 } else {
reed@google.comeac52bd2011-11-14 18:13:59 +00002234 if (bounds.isEmpty()) {
2235 SkASSERT(fBounds.isEmpty());
2236 } else {
reed@google.com3563c9e2011-11-14 19:34:57 +00002237 if (!fBounds.isEmpty()) {
2238 SkASSERT(fBounds.contains(bounds));
2239 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002240 }
reed@android.come522ca52009-11-23 20:10:41 +00002241 }
2242 }
reed@google.com10296cc2011-09-21 12:29:05 +00002243
2244 uint32_t mask = 0;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002245 const uint8_t* verbs = const_cast<const SkPathRef*>(fPathRef.get())->verbs();
2246 for (int i = 0; i < fPathRef->countVerbs(); i++) {
2247 switch (verbs[~i]) {
reed@google.com10296cc2011-09-21 12:29:05 +00002248 case kLine_Verb:
2249 mask |= kLine_SegmentMask;
2250 break;
2251 case kQuad_Verb:
2252 mask |= kQuad_SegmentMask;
2253 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002254 case kConic_Verb:
2255 mask |= kConic_SegmentMask;
2256 break;
reed@google.com10296cc2011-09-21 12:29:05 +00002257 case kCubic_Verb:
2258 mask |= kCubic_SegmentMask;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002259 case kMove_Verb: // these verbs aren't included in the segment mask.
2260 case kClose_Verb:
2261 break;
2262 case kDone_Verb:
2263 SkDEBUGFAIL("Done verb shouldn't be recorded.");
2264 break;
2265 default:
2266 SkDEBUGFAIL("Unknown Verb");
2267 break;
reed@google.com10296cc2011-09-21 12:29:05 +00002268 }
2269 }
2270 SkASSERT(mask == fSegmentMask);
djsollen@google.com077348c2012-10-22 20:23:32 +00002271#endif // SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002272}
djsollen@google.com077348c2012-10-22 20:23:32 +00002273#endif // SK_DEBUG
reed@android.come522ca52009-11-23 20:10:41 +00002274
reed@google.com04863fa2011-05-15 04:08:24 +00002275///////////////////////////////////////////////////////////////////////////////
2276
reed@google.com0b7b9822011-05-16 12:29:27 +00002277static int sign(SkScalar x) { return x < 0; }
2278#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00002279
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002280static bool AlmostEqual(SkScalar compA, SkScalar compB) {
2281 // The error epsilon was empirically derived; worse case round rects
2282 // with a mid point outset by 2x float epsilon in tests had an error
2283 // of 12.
2284 const int epsilon = 16;
2285 if (!SkScalarIsFinite(compA) || !SkScalarIsFinite(compB)) {
2286 return false;
2287 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002288 // no need to check for small numbers because SkPath::Iter has removed degenerate values
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002289 int aBits = SkFloatAs2sCompliment(compA);
2290 int bBits = SkFloatAs2sCompliment(compB);
2291 return aBits < bBits + epsilon && bBits < aBits + epsilon;
reed@google.com04863fa2011-05-15 04:08:24 +00002292}
2293
2294// only valid for a single contour
2295struct Convexicator {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002296 Convexicator()
2297 : fPtCount(0)
2298 , fConvexity(SkPath::kConvex_Convexity)
2299 , fDirection(SkPath::kUnknown_Direction) {
reed@google.com04863fa2011-05-15 04:08:24 +00002300 fSign = 0;
2301 // warnings
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002302 fLastPt.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002303 fCurrPt.set(0, 0);
2304 fVec0.set(0, 0);
2305 fVec1.set(0, 0);
2306 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00002307
2308 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00002309 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00002310 }
2311
2312 SkPath::Convexity getConvexity() const { return fConvexity; }
2313
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002314 /** The direction returned is only valid if the path is determined convex */
2315 SkPath::Direction getDirection() const { return fDirection; }
2316
reed@google.com04863fa2011-05-15 04:08:24 +00002317 void addPt(const SkPoint& pt) {
2318 if (SkPath::kConcave_Convexity == fConvexity) {
2319 return;
2320 }
2321
2322 if (0 == fPtCount) {
2323 fCurrPt = pt;
2324 ++fPtCount;
2325 } else {
2326 SkVector vec = pt - fCurrPt;
2327 if (vec.fX || vec.fY) {
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002328 fLastPt = fCurrPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002329 fCurrPt = pt;
2330 if (++fPtCount == 2) {
2331 fFirstVec = fVec1 = vec;
2332 } else {
2333 SkASSERT(fPtCount > 2);
2334 this->addVec(vec);
2335 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002336
reed@google.com85b6e392011-05-15 20:25:17 +00002337 int sx = sign(vec.fX);
2338 int sy = sign(vec.fY);
2339 fDx += (sx != fSx);
2340 fDy += (sy != fSy);
2341 fSx = sx;
2342 fSy = sy;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002343
reed@google.com85b6e392011-05-15 20:25:17 +00002344 if (fDx > 3 || fDy > 3) {
2345 fConvexity = SkPath::kConcave_Convexity;
2346 }
reed@google.com04863fa2011-05-15 04:08:24 +00002347 }
2348 }
2349 }
2350
2351 void close() {
2352 if (fPtCount > 2) {
2353 this->addVec(fFirstVec);
2354 }
2355 }
2356
2357private:
2358 void addVec(const SkVector& vec) {
2359 SkASSERT(vec.fX || vec.fY);
2360 fVec0 = fVec1;
2361 fVec1 = vec;
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002362 SkScalar cross = SkPoint::CrossProduct(fVec0, fVec1);
2363 SkScalar smallest = SkTMin(fCurrPt.fX, SkTMin(fCurrPt.fY, SkTMin(fLastPt.fX, fLastPt.fY)));
2364 SkScalar largest = SkTMax(fCurrPt.fX, SkTMax(fCurrPt.fY, SkTMax(fLastPt.fX, fLastPt.fY)));
2365 largest = SkTMax(largest, -smallest);
2366 int sign = AlmostEqual(largest, largest + cross) ? 0 : SkScalarSignAsInt(cross);
reed@google.com04863fa2011-05-15 04:08:24 +00002367 if (0 == fSign) {
2368 fSign = sign;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002369 if (1 == sign) {
2370 fDirection = SkPath::kCW_Direction;
2371 } else if (-1 == sign) {
2372 fDirection = SkPath::kCCW_Direction;
2373 }
reed@google.com04863fa2011-05-15 04:08:24 +00002374 } else if (sign) {
reed@google.comb54455e2011-05-16 14:16:04 +00002375 if (fSign != sign) {
reed@google.com04863fa2011-05-15 04:08:24 +00002376 fConvexity = SkPath::kConcave_Convexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002377 fDirection = SkPath::kUnknown_Direction;
reed@google.com04863fa2011-05-15 04:08:24 +00002378 }
2379 }
2380 }
2381
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002382 SkPoint fLastPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002383 SkPoint fCurrPt;
2384 SkVector fVec0, fVec1, fFirstVec;
2385 int fPtCount; // non-degenerate points
2386 int fSign;
2387 SkPath::Convexity fConvexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002388 SkPath::Direction fDirection;
reed@google.com0b7b9822011-05-16 12:29:27 +00002389 int fDx, fDy, fSx, fSy;
reed@google.com04863fa2011-05-15 04:08:24 +00002390};
2391
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002392SkPath::Convexity SkPath::internalGetConvexity() const {
2393 SkASSERT(kUnknown_Convexity == fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002394 SkPoint pts[4];
2395 SkPath::Verb verb;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002396 SkPath::Iter iter(*this, true);
reed@google.com04863fa2011-05-15 04:08:24 +00002397
2398 int contourCount = 0;
2399 int count;
2400 Convexicator state;
2401
2402 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
2403 switch (verb) {
2404 case kMove_Verb:
2405 if (++contourCount > 1) {
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 pts[1] = pts[0];
2410 count = 1;
2411 break;
2412 case kLine_Verb: count = 1; break;
2413 case kQuad_Verb: count = 2; break;
reed@google.com277c3f82013-05-31 15:17:50 +00002414 case kConic_Verb: count = 2; break;
reed@google.com04863fa2011-05-15 04:08:24 +00002415 case kCubic_Verb: count = 3; break;
2416 case kClose_Verb:
2417 state.close();
2418 count = 0;
2419 break;
2420 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00002421 SkDEBUGFAIL("bad verb");
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002422 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002423 return kConcave_Convexity;
2424 }
2425
2426 for (int i = 1; i <= count; i++) {
2427 state.addPt(pts[i]);
2428 }
2429 // early exit
2430 if (kConcave_Convexity == state.getConvexity()) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002431 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002432 return kConcave_Convexity;
2433 }
2434 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002435 fConvexity = state.getConvexity();
2436 if (kConvex_Convexity == fConvexity && kUnknown_Direction == fDirection) {
2437 fDirection = state.getDirection();
2438 }
2439 return static_cast<Convexity>(fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002440}
reed@google.com69a99432012-01-10 18:00:10 +00002441
2442///////////////////////////////////////////////////////////////////////////////
2443
2444class ContourIter {
2445public:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002446 ContourIter(const SkPathRef& pathRef);
reed@google.com69a99432012-01-10 18:00:10 +00002447
2448 bool done() const { return fDone; }
2449 // if !done() then these may be called
2450 int count() const { return fCurrPtCount; }
2451 const SkPoint* pts() const { return fCurrPt; }
2452 void next();
2453
2454private:
2455 int fCurrPtCount;
2456 const SkPoint* fCurrPt;
2457 const uint8_t* fCurrVerb;
2458 const uint8_t* fStopVerbs;
reed@google.com277c3f82013-05-31 15:17:50 +00002459 const SkScalar* fCurrConicWeight;
reed@google.com69a99432012-01-10 18:00:10 +00002460 bool fDone;
reed@google.comd1ab9322012-01-10 18:40:03 +00002461 SkDEBUGCODE(int fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002462};
2463
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002464ContourIter::ContourIter(const SkPathRef& pathRef) {
2465 fStopVerbs = pathRef.verbsMemBegin();
reed@google.com69a99432012-01-10 18:00:10 +00002466 fDone = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002467 fCurrPt = pathRef.points();
2468 fCurrVerb = pathRef.verbs();
reed@google.com277c3f82013-05-31 15:17:50 +00002469 fCurrConicWeight = pathRef.conicWeights();
reed@google.com69a99432012-01-10 18:00:10 +00002470 fCurrPtCount = 0;
reed@google.comd1ab9322012-01-10 18:40:03 +00002471 SkDEBUGCODE(fContourCounter = 0;)
reed@google.com69a99432012-01-10 18:00:10 +00002472 this->next();
2473}
2474
2475void ContourIter::next() {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002476 if (fCurrVerb <= fStopVerbs) {
reed@google.com69a99432012-01-10 18:00:10 +00002477 fDone = true;
2478 }
2479 if (fDone) {
2480 return;
2481 }
2482
2483 // skip pts of prev contour
2484 fCurrPt += fCurrPtCount;
2485
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002486 SkASSERT(SkPath::kMove_Verb == fCurrVerb[~0]);
reed@google.com69a99432012-01-10 18:00:10 +00002487 int ptCount = 1; // moveTo
2488 const uint8_t* verbs = fCurrVerb;
2489
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002490 for (--verbs; verbs > fStopVerbs; --verbs) {
2491 switch (verbs[~0]) {
reed@google.com69a99432012-01-10 18:00:10 +00002492 case SkPath::kMove_Verb:
reed@google.com69a99432012-01-10 18:00:10 +00002493 goto CONTOUR_END;
2494 case SkPath::kLine_Verb:
2495 ptCount += 1;
2496 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002497 case SkPath::kConic_Verb:
2498 fCurrConicWeight += 1;
2499 // fall-through
reed@google.com69a99432012-01-10 18:00:10 +00002500 case SkPath::kQuad_Verb:
2501 ptCount += 2;
2502 break;
2503 case SkPath::kCubic_Verb:
2504 ptCount += 3;
2505 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002506 case SkPath::kClose_Verb:
2507 break;
2508 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00002509 SkDEBUGFAIL("unexpected verb");
reed@google.com69a99432012-01-10 18:00:10 +00002510 break;
2511 }
2512 }
2513CONTOUR_END:
2514 fCurrPtCount = ptCount;
2515 fCurrVerb = verbs;
reed@google.comd1ab9322012-01-10 18:40:03 +00002516 SkDEBUGCODE(++fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002517}
2518
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002519// returns cross product of (p1 - p0) and (p2 - p0)
reed@google.com69a99432012-01-10 18:00:10 +00002520static SkScalar cross_prod(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002521 SkScalar cross = SkPoint::CrossProduct(p1 - p0, p2 - p0);
2522 // We may get 0 when the above subtracts underflow. We expect this to be
2523 // very rare and lazily promote to double.
2524 if (0 == cross) {
2525 double p0x = SkScalarToDouble(p0.fX);
2526 double p0y = SkScalarToDouble(p0.fY);
2527
2528 double p1x = SkScalarToDouble(p1.fX);
2529 double p1y = SkScalarToDouble(p1.fY);
2530
2531 double p2x = SkScalarToDouble(p2.fX);
2532 double p2y = SkScalarToDouble(p2.fY);
2533
2534 cross = SkDoubleToScalar((p1x - p0x) * (p2y - p0y) -
2535 (p1y - p0y) * (p2x - p0x));
2536
2537 }
2538 return cross;
reed@google.com69a99432012-01-10 18:00:10 +00002539}
2540
reed@google.comc1ea60a2012-01-31 15:15:36 +00002541// Returns the first pt with the maximum Y coordinate
reed@google.com69a99432012-01-10 18:00:10 +00002542static int find_max_y(const SkPoint pts[], int count) {
2543 SkASSERT(count > 0);
2544 SkScalar max = pts[0].fY;
reed@google.comc1ea60a2012-01-31 15:15:36 +00002545 int firstIndex = 0;
reed@google.com69a99432012-01-10 18:00:10 +00002546 for (int i = 1; i < count; ++i) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002547 SkScalar y = pts[i].fY;
2548 if (y > max) {
2549 max = y;
2550 firstIndex = i;
reed@google.com69a99432012-01-10 18:00:10 +00002551 }
2552 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002553 return firstIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002554}
2555
reed@google.comcabaf1d2012-01-11 21:03:05 +00002556static int find_diff_pt(const SkPoint pts[], int index, int n, int inc) {
2557 int i = index;
2558 for (;;) {
2559 i = (i + inc) % n;
2560 if (i == index) { // we wrapped around, so abort
2561 break;
2562 }
2563 if (pts[index] != pts[i]) { // found a different point, success!
2564 break;
2565 }
2566 }
2567 return i;
2568}
2569
reed@google.comc1ea60a2012-01-31 15:15:36 +00002570/**
2571 * Starting at index, and moving forward (incrementing), find the xmin and
2572 * xmax of the contiguous points that have the same Y.
2573 */
2574static int find_min_max_x_at_y(const SkPoint pts[], int index, int n,
2575 int* maxIndexPtr) {
2576 const SkScalar y = pts[index].fY;
2577 SkScalar min = pts[index].fX;
2578 SkScalar max = min;
2579 int minIndex = index;
2580 int maxIndex = index;
2581 for (int i = index + 1; i < n; ++i) {
2582 if (pts[i].fY != y) {
2583 break;
2584 }
2585 SkScalar x = pts[i].fX;
2586 if (x < min) {
2587 min = x;
2588 minIndex = i;
2589 } else if (x > max) {
2590 max = x;
2591 maxIndex = i;
2592 }
2593 }
2594 *maxIndexPtr = maxIndex;
2595 return minIndex;
2596}
2597
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002598static void crossToDir(SkScalar cross, SkPath::Direction* dir) {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002599 *dir = cross > 0 ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002600}
2601
reed@google.comac8543f2012-01-30 20:51:25 +00002602/*
2603 * We loop through all contours, and keep the computed cross-product of the
2604 * contour that contained the global y-max. If we just look at the first
2605 * contour, we may find one that is wound the opposite way (correctly) since
2606 * it is the interior of a hole (e.g. 'o'). Thus we must find the contour
2607 * that is outer most (or at least has the global y-max) before we can consider
2608 * its cross product.
2609 */
reed@google.com69a99432012-01-10 18:00:10 +00002610bool SkPath::cheapComputeDirection(Direction* dir) const {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002611 if (kUnknown_Direction != fDirection) {
2612 *dir = static_cast<Direction>(fDirection);
2613 return true;
2614 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002615
2616 // don't want to pay the cost for computing this if it
2617 // is unknown, so we don't call isConvex()
2618 if (kConvex_Convexity == this->getConvexityOrUnknown()) {
2619 SkASSERT(kUnknown_Direction == fDirection);
2620 *dir = static_cast<Direction>(fDirection);
2621 return false;
2622 }
reed@google.com69a99432012-01-10 18:00:10 +00002623
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002624 ContourIter iter(*fPathRef.get());
reed@google.com69a99432012-01-10 18:00:10 +00002625
reed@google.comac8543f2012-01-30 20:51:25 +00002626 // initialize with our logical y-min
2627 SkScalar ymax = this->getBounds().fTop;
2628 SkScalar ymaxCross = 0;
2629
reed@google.com69a99432012-01-10 18:00:10 +00002630 for (; !iter.done(); iter.next()) {
2631 int n = iter.count();
reed@google.comcabaf1d2012-01-11 21:03:05 +00002632 if (n < 3) {
2633 continue;
2634 }
djsollen@google.come63793a2012-03-21 15:39:03 +00002635
reed@google.comcabaf1d2012-01-11 21:03:05 +00002636 const SkPoint* pts = iter.pts();
reed@google.com69a99432012-01-10 18:00:10 +00002637 SkScalar cross = 0;
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002638 int index = find_max_y(pts, n);
2639 if (pts[index].fY < ymax) {
2640 continue;
2641 }
2642
2643 // If there is more than 1 distinct point at the y-max, we take the
2644 // x-min and x-max of them and just subtract to compute the dir.
2645 if (pts[(index + 1) % n].fY == pts[index].fY) {
2646 int maxIndex;
2647 int minIndex = find_min_max_x_at_y(pts, index, n, &maxIndex);
2648 if (minIndex == maxIndex) {
2649 goto TRY_CROSSPROD;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002650 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002651 SkASSERT(pts[minIndex].fY == pts[index].fY);
2652 SkASSERT(pts[maxIndex].fY == pts[index].fY);
2653 SkASSERT(pts[minIndex].fX <= pts[maxIndex].fX);
2654 // we just subtract the indices, and let that auto-convert to
2655 // SkScalar, since we just want - or + to signal the direction.
2656 cross = minIndex - maxIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002657 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002658 TRY_CROSSPROD:
2659 // Find a next and prev index to use for the cross-product test,
2660 // but we try to find pts that form non-zero vectors from pts[index]
2661 //
2662 // Its possible that we can't find two non-degenerate vectors, so
2663 // we have to guard our search (e.g. all the pts could be in the
2664 // same place).
2665
2666 // we pass n - 1 instead of -1 so we don't foul up % operator by
2667 // passing it a negative LH argument.
2668 int prev = find_diff_pt(pts, index, n, n - 1);
2669 if (prev == index) {
2670 // completely degenerate, skip to next contour
reed@google.comac8543f2012-01-30 20:51:25 +00002671 continue;
2672 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002673 int next = find_diff_pt(pts, index, n, 1);
2674 SkASSERT(next != index);
2675 cross = cross_prod(pts[prev], pts[index], pts[next]);
2676 // if we get a zero and the points are horizontal, then we look at the spread in
2677 // x-direction. We really should continue to walk away from the degeneracy until
2678 // there is a divergence.
2679 if (0 == cross && pts[prev].fY == pts[index].fY && pts[next].fY == pts[index].fY) {
2680 // construct the subtract so we get the correct Direction below
2681 cross = pts[index].fX - pts[next].fX;
reed@google.com188bfcf2012-01-17 18:26:38 +00002682 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002683 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002684
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002685 if (cross) {
2686 // record our best guess so far
2687 ymax = pts[index].fY;
2688 ymaxCross = cross;
reed@google.com69a99432012-01-10 18:00:10 +00002689 }
2690 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002691 if (ymaxCross) {
2692 crossToDir(ymaxCross, dir);
2693 fDirection = *dir;
2694 return true;
2695 } else {
2696 return false;
2697 }
reed@google.comac8543f2012-01-30 20:51:25 +00002698}
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002699
2700///////////////////////////////////////////////////////////////////////////////
2701
2702static SkScalar eval_cubic_coeff(SkScalar A, SkScalar B, SkScalar C,
2703 SkScalar D, SkScalar t) {
2704 return SkScalarMulAdd(SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C), t, D);
2705}
2706
2707static SkScalar eval_cubic_pts(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2708 SkScalar t) {
2709 SkScalar A = c3 + 3*(c1 - c2) - c0;
2710 SkScalar B = 3*(c2 - c1 - c1 + c0);
2711 SkScalar C = 3*(c1 - c0);
2712 SkScalar D = c0;
2713 return eval_cubic_coeff(A, B, C, D, t);
2714}
2715
2716/* Given 4 cubic points (either Xs or Ys), and a target X or Y, compute the
2717 t value such that cubic(t) = target
2718 */
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002719static void chopMonoCubicAt(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002720 SkScalar target, SkScalar* t) {
2721 // SkASSERT(c0 <= c1 && c1 <= c2 && c2 <= c3);
2722 SkASSERT(c0 < target && target < c3);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002723
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002724 SkScalar D = c0 - target;
2725 SkScalar A = c3 + 3*(c1 - c2) - c0;
2726 SkScalar B = 3*(c2 - c1 - c1 + c0);
2727 SkScalar C = 3*(c1 - c0);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002728
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002729 const SkScalar TOLERANCE = SK_Scalar1 / 4096;
2730 SkScalar minT = 0;
2731 SkScalar maxT = SK_Scalar1;
2732 SkScalar mid;
2733 int i;
2734 for (i = 0; i < 16; i++) {
2735 mid = SkScalarAve(minT, maxT);
2736 SkScalar delta = eval_cubic_coeff(A, B, C, D, mid);
2737 if (delta < 0) {
2738 minT = mid;
2739 delta = -delta;
2740 } else {
2741 maxT = mid;
2742 }
2743 if (delta < TOLERANCE) {
2744 break;
2745 }
2746 }
2747 *t = mid;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002748}
2749
2750template <size_t N> static void find_minmax(const SkPoint pts[],
2751 SkScalar* minPtr, SkScalar* maxPtr) {
2752 SkScalar min, max;
2753 min = max = pts[0].fX;
2754 for (size_t i = 1; i < N; ++i) {
2755 min = SkMinScalar(min, pts[i].fX);
2756 max = SkMaxScalar(max, pts[i].fX);
2757 }
2758 *minPtr = min;
2759 *maxPtr = max;
2760}
2761
2762static int winding_mono_cubic(const SkPoint pts[], SkScalar x, SkScalar y) {
2763 SkPoint storage[4];
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002764
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002765 int dir = 1;
2766 if (pts[0].fY > pts[3].fY) {
2767 storage[0] = pts[3];
2768 storage[1] = pts[2];
2769 storage[2] = pts[1];
2770 storage[3] = pts[0];
2771 pts = storage;
2772 dir = -1;
2773 }
2774 if (y < pts[0].fY || y >= pts[3].fY) {
2775 return 0;
2776 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002777
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002778 // quickreject or quickaccept
2779 SkScalar min, max;
2780 find_minmax<4>(pts, &min, &max);
2781 if (x < min) {
2782 return 0;
2783 }
2784 if (x > max) {
2785 return dir;
2786 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002787
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002788 // compute the actual x(t) value
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002789 SkScalar t;
2790 chopMonoCubicAt(pts[0].fY, pts[1].fY, pts[2].fY, pts[3].fY, y, &t);
2791 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 +00002792 return xt < x ? dir : 0;
2793}
2794
2795static int winding_cubic(const SkPoint pts[], SkScalar x, SkScalar y) {
2796 SkPoint dst[10];
2797 int n = SkChopCubicAtYExtrema(pts, dst);
2798 int w = 0;
2799 for (int i = 0; i <= n; ++i) {
2800 w += winding_mono_cubic(&dst[i * 3], x, y);
2801 }
2802 return w;
2803}
2804
2805static int winding_mono_quad(const SkPoint pts[], SkScalar x, SkScalar y) {
2806 SkScalar y0 = pts[0].fY;
2807 SkScalar y2 = pts[2].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002808
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002809 int dir = 1;
2810 if (y0 > y2) {
2811 SkTSwap(y0, y2);
2812 dir = -1;
2813 }
2814 if (y < y0 || y >= y2) {
2815 return 0;
2816 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002817
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002818 // bounds check on X (not required. is it faster?)
2819#if 0
2820 if (pts[0].fX > x && pts[1].fX > x && pts[2].fX > x) {
2821 return 0;
2822 }
2823#endif
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002824
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002825 SkScalar roots[2];
2826 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
2827 2 * (pts[1].fY - pts[0].fY),
2828 pts[0].fY - y,
2829 roots);
2830 SkASSERT(n <= 1);
2831 SkScalar xt;
2832 if (0 == n) {
2833 SkScalar mid = SkScalarAve(y0, y2);
2834 // Need [0] and [2] if dir == 1
2835 // and [2] and [0] if dir == -1
2836 xt = y < mid ? pts[1 - dir].fX : pts[dir - 1].fX;
2837 } else {
2838 SkScalar t = roots[0];
2839 SkScalar C = pts[0].fX;
2840 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
2841 SkScalar B = 2 * (pts[1].fX - C);
2842 xt = SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C);
2843 }
2844 return xt < x ? dir : 0;
2845}
2846
2847static bool is_mono_quad(SkScalar y0, SkScalar y1, SkScalar y2) {
2848 // return SkScalarSignAsInt(y0 - y1) + SkScalarSignAsInt(y1 - y2) != 0;
2849 if (y0 == y1) {
2850 return true;
2851 }
2852 if (y0 < y1) {
2853 return y1 <= y2;
2854 } else {
2855 return y1 >= y2;
2856 }
2857}
2858
2859static int winding_quad(const SkPoint pts[], SkScalar x, SkScalar y) {
2860 SkPoint dst[5];
2861 int n = 0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002862
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002863 if (!is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY)) {
2864 n = SkChopQuadAtYExtrema(pts, dst);
2865 pts = dst;
2866 }
2867 int w = winding_mono_quad(pts, x, y);
2868 if (n > 0) {
2869 w += winding_mono_quad(&pts[2], x, y);
2870 }
2871 return w;
2872}
2873
2874static int winding_line(const SkPoint pts[], SkScalar x, SkScalar y) {
2875 SkScalar x0 = pts[0].fX;
2876 SkScalar y0 = pts[0].fY;
2877 SkScalar x1 = pts[1].fX;
2878 SkScalar y1 = pts[1].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002879
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002880 SkScalar dy = y1 - y0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002881
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002882 int dir = 1;
2883 if (y0 > y1) {
2884 SkTSwap(y0, y1);
2885 dir = -1;
2886 }
2887 if (y < y0 || y >= y1) {
2888 return 0;
2889 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002890
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002891 SkScalar cross = SkScalarMul(x1 - x0, y - pts[0].fY) -
2892 SkScalarMul(dy, x - pts[0].fX);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002893
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002894 if (SkScalarSignAsInt(cross) == dir) {
2895 dir = 0;
2896 }
2897 return dir;
2898}
2899
reed@google.com4db592c2013-10-30 17:39:43 +00002900static bool contains_inclusive(const SkRect& r, SkScalar x, SkScalar y) {
2901 return r.fLeft <= x && x <= r.fRight && r.fTop <= y && y <= r.fBottom;
2902}
2903
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002904bool SkPath::contains(SkScalar x, SkScalar y) const {
2905 bool isInverse = this->isInverseFillType();
2906 if (this->isEmpty()) {
2907 return isInverse;
2908 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002909
reed@google.com4db592c2013-10-30 17:39:43 +00002910 if (!contains_inclusive(this->getBounds(), x, y)) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002911 return isInverse;
2912 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002913
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002914 SkPath::Iter iter(*this, true);
2915 bool done = false;
2916 int w = 0;
2917 do {
2918 SkPoint pts[4];
2919 switch (iter.next(pts, false)) {
2920 case SkPath::kMove_Verb:
2921 case SkPath::kClose_Verb:
2922 break;
2923 case SkPath::kLine_Verb:
2924 w += winding_line(pts, x, y);
2925 break;
2926 case SkPath::kQuad_Verb:
2927 w += winding_quad(pts, x, y);
2928 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002929 case SkPath::kConic_Verb:
2930 SkASSERT(0);
2931 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002932 case SkPath::kCubic_Verb:
2933 w += winding_cubic(pts, x, y);
2934 break;
2935 case SkPath::kDone_Verb:
2936 done = true;
2937 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002938 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002939 } while (!done);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002940
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002941 switch (this->getFillType()) {
2942 case SkPath::kEvenOdd_FillType:
2943 case SkPath::kInverseEvenOdd_FillType:
2944 w &= 1;
2945 break;
reed@google.come9bb6232012-07-11 18:56:10 +00002946 default:
2947 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002948 }
2949 return SkToBool(w);
2950}