blob: 60cfe0373c08628573cf5be16ff829f428e7cd0f [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};
59
bsalomon@google.com30c174b2012-11-13 14:36:42 +000060class SkAutoDisableDirectionCheck {
61public:
62 SkAutoDisableDirectionCheck(SkPath* path) : fPath(path) {
63 fSaved = static_cast<SkPath::Direction>(fPath->fDirection);
64 }
65
66 ~SkAutoDisableDirectionCheck() {
67 fPath->fDirection = fSaved;
68 }
69
70private:
71 SkPath* fPath;
72 SkPath::Direction fSaved;
73};
74
reed@android.com8a1c16f2008-12-17 15:59:43 +000075/* This guy's constructor/destructor bracket a path editing operation. It is
76 used when we know the bounds of the amount we are going to add to the path
77 (usually a new contour, but not required).
reed@google.comabf15c12011-01-18 20:35:51 +000078
reed@android.com8a1c16f2008-12-17 15:59:43 +000079 It captures some state about the path up front (i.e. if it already has a
robertphillips@google.comca0c8382013-09-26 12:18:23 +000080 cached bounds), and then if it can, it updates the cache bounds explicitly,
reed@android.comd252db02009-04-01 18:31:44 +000081 avoiding the need to revisit all of the points in getBounds().
reed@google.comabf15c12011-01-18 20:35:51 +000082
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000083 It also notes if the path was originally degenerate, and if so, sets
84 isConvex to true. Thus it can only be used if the contour being added is
robertphillips@google.comca0c8382013-09-26 12:18:23 +000085 convex (which is always true since we only allow the addition of rects).
reed@android.com8a1c16f2008-12-17 15:59:43 +000086 */
87class SkAutoPathBoundsUpdate {
88public:
89 SkAutoPathBoundsUpdate(SkPath* path, const SkRect& r) : fRect(r) {
90 this->init(path);
91 }
92
93 SkAutoPathBoundsUpdate(SkPath* path, SkScalar left, SkScalar top,
94 SkScalar right, SkScalar bottom) {
95 fRect.set(left, top, right, bottom);
96 this->init(path);
97 }
reed@google.comabf15c12011-01-18 20:35:51 +000098
reed@android.com8a1c16f2008-12-17 15:59:43 +000099 ~SkAutoPathBoundsUpdate() {
reed@google.com44699382013-10-31 17:28:30 +0000100 fPath->setConvexity(fDegenerate ? SkPath::kConvex_Convexity
101 : SkPath::kUnknown_Convexity);
robertphillips@google.comca0c8382013-09-26 12:18:23 +0000102 if (fEmpty || fHasValidBounds) {
103 fPath->setBounds(fRect);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000104 }
105 }
reed@google.comabf15c12011-01-18 20:35:51 +0000106
reed@android.com8a1c16f2008-12-17 15:59:43 +0000107private:
reed@android.com6b82d1a2009-06-03 02:35:01 +0000108 SkPath* fPath;
109 SkRect fRect;
robertphillips@google.comca0c8382013-09-26 12:18:23 +0000110 bool fHasValidBounds;
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +0000111 bool fDegenerate;
reed@android.com6b82d1a2009-06-03 02:35:01 +0000112 bool fEmpty;
reed@google.comabf15c12011-01-18 20:35:51 +0000113
reed@android.com6b82d1a2009-06-03 02:35:01 +0000114 void init(SkPath* path) {
robertphillips@google.comca0c8382013-09-26 12:18:23 +0000115 // Cannot use fRect for our bounds unless we know it is sorted
116 fRect.sort();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000117 fPath = path;
reed@google.coma8790de2012-10-24 21:04:04 +0000118 // Mark the path's bounds as dirty if (1) they are, or (2) the path
119 // is non-finite, and therefore its bounds are not meaningful
robertphillips@google.comca0c8382013-09-26 12:18:23 +0000120 fHasValidBounds = path->hasComputedBounds() && path->isFinite();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000121 fEmpty = path->isEmpty();
robertphillips@google.comca0c8382013-09-26 12:18:23 +0000122 if (fHasValidBounds && !fEmpty) {
123 joinNoEmptyChecks(&fRect, fPath->getBounds());
124 }
125 fDegenerate = is_degenerate(*path);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000126 }
127};
128
reed@android.com8a1c16f2008-12-17 15:59:43 +0000129////////////////////////////////////////////////////////////////////////////
130
131/*
132 Stores the verbs and points as they are given to us, with exceptions:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000133 - we only record "Close" if it was immediately preceeded by Move | Line | Quad | Cubic
reed@android.com8a1c16f2008-12-17 15:59:43 +0000134 - we insert a Move(0,0) if Line | Quad | Cubic is our first command
135
136 The iterator does more cleanup, especially if forceClose == true
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000137 1. If we encounter degenerate segments, remove them
138 2. if we encounter Close, return a cons'd up Line() first (if the curr-pt != start-pt)
139 3. if we encounter Move without a preceeding Close, and forceClose is true, goto #2
140 4. if we encounter Line | Quad | Cubic after Close, cons up a Move
reed@android.com8a1c16f2008-12-17 15:59:43 +0000141*/
142
143////////////////////////////////////////////////////////////////////////////
144
reed@google.comd335d1d2012-01-12 18:17:11 +0000145// flag to require a moveTo if we begin with something else, like lineTo etc.
146#define INITIAL_LASTMOVETOINDEX_VALUE ~0
147
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000148SkPath::SkPath()
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000149 : fPathRef(SkPathRef::CreateEmpty())
bungeman@google.coma5809a32013-06-21 15:13:34 +0000150#ifdef SK_BUILD_FOR_ANDROID
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000151 , fSourcePath(NULL)
bungeman@google.coma5809a32013-06-21 15:13:34 +0000152#endif
153{
154 this->resetFields();
155}
156
157void SkPath::resetFields() {
158 //fPathRef is assumed to have been emptied by the caller.
159 fLastMoveToIndex = INITIAL_LASTMOVETOINDEX_VALUE;
160 fFillType = kWinding_FillType;
161 fSegmentMask = 0;
reed@google.com04863fa2011-05-15 04:08:24 +0000162 fConvexity = kUnknown_Convexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000163 fDirection = kUnknown_Direction;
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000164 fIsOval = false;
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000165
166 // We don't touch Android's fSourcePath. It's used to track texture garbage collection, so we
167 // don't want to muck with it if it's been set to something non-NULL.
reed@android.com6b82d1a2009-06-03 02:35:01 +0000168}
reed@android.com8a1c16f2008-12-17 15:59:43 +0000169
bungeman@google.coma5809a32013-06-21 15:13:34 +0000170SkPath::SkPath(const SkPath& that)
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000171 : fPathRef(SkRef(that.fPathRef.get())) {
bungeman@google.coma5809a32013-06-21 15:13:34 +0000172 this->copyFields(that);
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000173#ifdef SK_BUILD_FOR_ANDROID
mtklein@google.comcb8b0ee2013-08-15 21:14:51 +0000174 fSourcePath = that.fSourcePath;
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000175#endif
bungeman@google.coma5809a32013-06-21 15:13:34 +0000176 SkDEBUGCODE(that.validate();)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000177}
178
179SkPath::~SkPath() {
180 SkDEBUGCODE(this->validate();)
181}
182
bungeman@google.coma5809a32013-06-21 15:13:34 +0000183SkPath& SkPath::operator=(const SkPath& that) {
184 SkDEBUGCODE(that.validate();)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000185
bungeman@google.coma5809a32013-06-21 15:13:34 +0000186 if (this != &that) {
187 fPathRef.reset(SkRef(that.fPathRef.get()));
188 this->copyFields(that);
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000189#ifdef SK_BUILD_FOR_ANDROID
mtklein@google.comcb8b0ee2013-08-15 21:14:51 +0000190 fSourcePath = that.fSourcePath;
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000191#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000192 }
193 SkDEBUGCODE(this->validate();)
194 return *this;
195}
196
bungeman@google.coma5809a32013-06-21 15:13:34 +0000197void SkPath::copyFields(const SkPath& that) {
198 //fPathRef is assumed to have been set by the caller.
bungeman@google.coma5809a32013-06-21 15:13:34 +0000199 fLastMoveToIndex = that.fLastMoveToIndex;
200 fFillType = that.fFillType;
201 fSegmentMask = that.fSegmentMask;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000202 fConvexity = that.fConvexity;
203 fDirection = that.fDirection;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000204 fIsOval = that.fIsOval;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000205}
206
djsollen@google.com9c1a9672013-08-09 13:49:13 +0000207bool operator==(const SkPath& a, const SkPath& b) {
reed@android.com6b82d1a2009-06-03 02:35:01 +0000208 // note: don't need to look at isConvex or bounds, since just comparing the
209 // raw data is sufficient.
reed@google.com10296cc2011-09-21 12:29:05 +0000210
211 // We explicitly check fSegmentMask as a quick-reject. We could skip it,
212 // since it is only a cache of info in the fVerbs, but its a fast way to
213 // notice a difference
214
reed@android.com3abec1d2009-03-02 05:36:20 +0000215 return &a == &b ||
reed@google.com10296cc2011-09-21 12:29:05 +0000216 (a.fFillType == b.fFillType && a.fSegmentMask == b.fSegmentMask &&
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000217 *a.fPathRef.get() == *b.fPathRef.get());
reed@android.com3abec1d2009-03-02 05:36:20 +0000218}
219
bungeman@google.coma5809a32013-06-21 15:13:34 +0000220void SkPath::swap(SkPath& that) {
221 SkASSERT(&that != NULL);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000222
bungeman@google.coma5809a32013-06-21 15:13:34 +0000223 if (this != &that) {
224 fPathRef.swap(&that.fPathRef);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000225 SkTSwap<int>(fLastMoveToIndex, that.fLastMoveToIndex);
226 SkTSwap<uint8_t>(fFillType, that.fFillType);
227 SkTSwap<uint8_t>(fSegmentMask, that.fSegmentMask);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000228 SkTSwap<uint8_t>(fConvexity, that.fConvexity);
229 SkTSwap<uint8_t>(fDirection, that.fDirection);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000230 SkTSwap<SkBool8>(fIsOval, that.fIsOval);
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000231#ifdef SK_BUILD_FOR_ANDROID
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000232 SkTSwap<const SkPath*>(fSourcePath, that.fSourcePath);
233#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000234 }
235}
236
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000237static inline bool check_edge_against_rect(const SkPoint& p0,
238 const SkPoint& p1,
239 const SkRect& rect,
240 SkPath::Direction dir) {
241 const SkPoint* edgeBegin;
242 SkVector v;
243 if (SkPath::kCW_Direction == dir) {
244 v = p1 - p0;
245 edgeBegin = &p0;
246 } else {
247 v = p0 - p1;
248 edgeBegin = &p1;
249 }
250 if (v.fX || v.fY) {
251 // check the cross product of v with the vec from edgeBegin to each rect corner
252 SkScalar yL = SkScalarMul(v.fY, rect.fLeft - edgeBegin->fX);
253 SkScalar xT = SkScalarMul(v.fX, rect.fTop - edgeBegin->fY);
254 SkScalar yR = SkScalarMul(v.fY, rect.fRight - edgeBegin->fX);
255 SkScalar xB = SkScalarMul(v.fX, rect.fBottom - edgeBegin->fY);
256 if ((xT < yL) || (xT < yR) || (xB < yL) || (xB < yR)) {
257 return false;
258 }
259 }
260 return true;
261}
262
263bool SkPath::conservativelyContainsRect(const SkRect& rect) const {
264 // This only handles non-degenerate convex paths currently.
265 if (kConvex_Convexity != this->getConvexity()) {
266 return false;
267 }
skia.committer@gmail.comcec8de62012-11-14 02:01:22 +0000268
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000269 Direction direction;
270 if (!this->cheapComputeDirection(&direction)) {
271 return false;
272 }
273
274 SkPoint firstPt;
275 SkPoint prevPt;
276 RawIter iter(*this);
277 SkPath::Verb verb;
278 SkPoint pts[4];
279 SkDEBUGCODE(int moveCnt = 0;)
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000280 SkDEBUGCODE(int segmentCount = 0;)
281 SkDEBUGCODE(int closeCount = 0;)
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000282
283 while ((verb = iter.next(pts)) != kDone_Verb) {
284 int nextPt = -1;
285 switch (verb) {
286 case kMove_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000287 SkASSERT(!segmentCount && !closeCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000288 SkDEBUGCODE(++moveCnt);
289 firstPt = prevPt = pts[0];
290 break;
291 case kLine_Verb:
292 nextPt = 1;
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000293 SkASSERT(moveCnt && !closeCount);
294 SkDEBUGCODE(++segmentCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000295 break;
296 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000297 case kConic_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000298 SkASSERT(moveCnt && !closeCount);
299 SkDEBUGCODE(++segmentCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000300 nextPt = 2;
301 break;
302 case kCubic_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000303 SkASSERT(moveCnt && !closeCount);
304 SkDEBUGCODE(++segmentCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000305 nextPt = 3;
306 break;
307 case kClose_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000308 SkDEBUGCODE(++closeCount;)
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000309 break;
310 default:
311 SkDEBUGFAIL("unknown verb");
312 }
313 if (-1 != nextPt) {
314 if (!check_edge_against_rect(prevPt, pts[nextPt], rect, direction)) {
315 return false;
316 }
317 prevPt = pts[nextPt];
318 }
319 }
320
321 return check_edge_against_rect(prevPt, firstPt, rect, direction);
322}
323
robertphillips@google.com7101abe2013-10-29 22:45:37 +0000324uint32_t SkPath::getGenerationID() const {
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000325 uint32_t genID = fPathRef->genID();
326#ifdef SK_BUILD_FOR_ANDROID
327 SkASSERT((unsigned)fFillType < (1 << (32 - kPathRefGenIDBitCnt)));
328 genID |= static_cast<uint32_t>(fFillType) << kPathRefGenIDBitCnt;
329#endif
330 return genID;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000331}
djsollen@google.come63793a2012-03-21 15:39:03 +0000332
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000333#ifdef SK_BUILD_FOR_ANDROID
djsollen@google.come63793a2012-03-21 15:39:03 +0000334const SkPath* SkPath::getSourcePath() const {
335 return fSourcePath;
336}
337
338void SkPath::setSourcePath(const SkPath* path) {
339 fSourcePath = path;
340}
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000341#endif
342
reed@android.com8a1c16f2008-12-17 15:59:43 +0000343void SkPath::reset() {
344 SkDEBUGCODE(this->validate();)
345
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000346 fPathRef.reset(SkPathRef::CreateEmpty());
bungeman@google.coma5809a32013-06-21 15:13:34 +0000347 this->resetFields();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000348}
349
350void SkPath::rewind() {
351 SkDEBUGCODE(this->validate();)
352
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000353 SkPathRef::Rewind(&fPathRef);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000354 this->resetFields();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000355}
356
reed@google.com7e6c4d12012-05-10 14:05:43 +0000357bool SkPath::isLine(SkPoint line[2]) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000358 int verbCount = fPathRef->countVerbs();
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000359
commit-bot@chromium.orga62efcc2013-08-05 13:23:13 +0000360 if (2 == verbCount) {
361 SkASSERT(kMove_Verb == fPathRef->atVerb(0));
362 if (kLine_Verb == fPathRef->atVerb(1)) {
363 SkASSERT(2 == fPathRef->countPoints());
reed@google.com7e6c4d12012-05-10 14:05:43 +0000364 if (line) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000365 const SkPoint* pts = fPathRef->points();
reed@google.com7e6c4d12012-05-10 14:05:43 +0000366 line[0] = pts[0];
367 line[1] = pts[1];
368 }
369 return true;
370 }
371 }
372 return false;
373}
374
caryclark@google.comf1316942011-07-26 19:54:45 +0000375/*
376 Determines if path is a rect by keeping track of changes in direction
377 and looking for a loop either clockwise or counterclockwise.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000378
caryclark@google.comf1316942011-07-26 19:54:45 +0000379 The direction is computed such that:
380 0: vertical up
caryclark@google.comf68154a2012-11-21 15:18:06 +0000381 1: horizontal left
caryclark@google.comf1316942011-07-26 19:54:45 +0000382 2: vertical down
caryclark@google.comf68154a2012-11-21 15:18:06 +0000383 3: horizontal right
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000384
caryclark@google.comf1316942011-07-26 19:54:45 +0000385A rectangle cycles up/right/down/left or up/left/down/right.
386
387The test fails if:
388 The path is closed, and followed by a line.
389 A second move creates a new endpoint.
390 A diagonal line is parsed.
391 There's more than four changes of direction.
392 There's a discontinuity on the line (e.g., a move in the middle)
393 The line reverses direction.
394 The rectangle doesn't complete a cycle.
395 The path contains a quadratic or cubic.
396 The path contains fewer than four points.
397 The final point isn't equal to the first point.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000398
caryclark@google.comf1316942011-07-26 19:54:45 +0000399It's OK if the path has:
400 Several colinear line segments composing a rectangle side.
401 Single points on the rectangle side.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000402
caryclark@google.comf1316942011-07-26 19:54:45 +0000403The direction takes advantage of the corners found since opposite sides
404must travel in opposite directions.
405
406FIXME: Allow colinear quads and cubics to be treated like lines.
407FIXME: If the API passes fill-only, return true if the filled stroke
408 is a rectangle, though the caller failed to close the path.
409 */
caryclark@google.comf68154a2012-11-21 15:18:06 +0000410bool SkPath::isRectContour(bool allowPartial, int* currVerb, const SkPoint** ptsPtr,
411 bool* isClosed, Direction* direction) const {
caryclark@google.comf1316942011-07-26 19:54:45 +0000412 int corners = 0;
413 SkPoint first, last;
caryclark@google.com56f233a2012-11-19 13:06:06 +0000414 const SkPoint* pts = *ptsPtr;
caryclark@google.combfe90372012-11-21 13:56:20 +0000415 const SkPoint* savePts = NULL;
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000416 first.set(0, 0);
417 last.set(0, 0);
418 int firstDirection = 0;
419 int lastDirection = 0;
420 int nextDirection = 0;
421 bool closedOrMoved = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000422 bool autoClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000423 int verbCnt = fPathRef->countVerbs();
caryclark@google.com56f233a2012-11-19 13:06:06 +0000424 while (*currVerb < verbCnt && (!allowPartial || !autoClose)) {
425 switch (fPathRef->atVerb(*currVerb)) {
caryclark@google.comf1316942011-07-26 19:54:45 +0000426 case kClose_Verb:
caryclark@google.com56f233a2012-11-19 13:06:06 +0000427 savePts = pts;
428 pts = *ptsPtr;
caryclark@google.comf1316942011-07-26 19:54:45 +0000429 autoClose = true;
430 case kLine_Verb: {
431 SkScalar left = last.fX;
432 SkScalar top = last.fY;
433 SkScalar right = pts->fX;
434 SkScalar bottom = pts->fY;
435 ++pts;
436 if (left != right && top != bottom) {
437 return false; // diagonal
438 }
439 if (left == right && top == bottom) {
440 break; // single point on side OK
441 }
442 nextDirection = (left != right) << 0 |
443 (left < right || top < bottom) << 1;
444 if (0 == corners) {
445 firstDirection = nextDirection;
446 first = last;
447 last = pts[-1];
448 corners = 1;
449 closedOrMoved = false;
450 break;
451 }
452 if (closedOrMoved) {
453 return false; // closed followed by a line
454 }
caryclark@google.combfe90372012-11-21 13:56:20 +0000455 if (autoClose && nextDirection == firstDirection) {
456 break; // colinear with first
457 }
caryclark@google.comf1316942011-07-26 19:54:45 +0000458 closedOrMoved = autoClose;
459 if (lastDirection != nextDirection) {
460 if (++corners > 4) {
461 return false; // too many direction changes
462 }
463 }
464 last = pts[-1];
465 if (lastDirection == nextDirection) {
466 break; // colinear segment
467 }
468 // Possible values for corners are 2, 3, and 4.
469 // When corners == 3, nextDirection opposes firstDirection.
470 // Otherwise, nextDirection at corner 2 opposes corner 4.
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000471 int turn = firstDirection ^ (corners - 1);
caryclark@google.comf1316942011-07-26 19:54:45 +0000472 int directionCycle = 3 == corners ? 0 : nextDirection ^ turn;
473 if ((directionCycle ^ turn) != nextDirection) {
474 return false; // direction didn't follow cycle
475 }
476 break;
477 }
478 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000479 case kConic_Verb:
caryclark@google.comf1316942011-07-26 19:54:45 +0000480 case kCubic_Verb:
481 return false; // quadratic, cubic not allowed
482 case kMove_Verb:
483 last = *pts++;
484 closedOrMoved = true;
485 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000486 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000487 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000488 break;
caryclark@google.comf1316942011-07-26 19:54:45 +0000489 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000490 *currVerb += 1;
caryclark@google.comf1316942011-07-26 19:54:45 +0000491 lastDirection = nextDirection;
492 }
493 // Success if 4 corners and first point equals last
caryclark@google.combfe90372012-11-21 13:56:20 +0000494 bool result = 4 == corners && (first == last || autoClose);
495 if (savePts) {
496 *ptsPtr = savePts;
497 }
caryclark@google.comf68154a2012-11-21 15:18:06 +0000498 if (result && isClosed) {
499 *isClosed = autoClose;
500 }
501 if (result && direction) {
sugoi@google.com12b4e272012-12-06 20:13:11 +0000502 *direction = firstDirection == ((lastDirection + 1) & 3) ? kCCW_Direction : kCW_Direction;
caryclark@google.comf68154a2012-11-21 15:18:06 +0000503 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000504 return result;
505}
506
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000507bool SkPath::isRect(SkRect* rect) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000508 SkDEBUGCODE(this->validate();)
509 int currVerb = 0;
510 const SkPoint* pts = fPathRef->points();
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000511 bool result = isRectContour(false, &currVerb, &pts, NULL, NULL);
512 if (result && rect) {
513 *rect = getBounds();
caryclark@google.comf1316942011-07-26 19:54:45 +0000514 }
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000515 return result;
516}
skia.committer@gmail.com020b25b2013-06-22 07:00:58 +0000517
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000518bool SkPath::isRect(bool* isClosed, Direction* direction) const {
519 SkDEBUGCODE(this->validate();)
520 int currVerb = 0;
521 const SkPoint* pts = fPathRef->points();
522 return isRectContour(false, &currVerb, &pts, isClosed, direction);
caryclark@google.comf68154a2012-11-21 15:18:06 +0000523}
524
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000525bool SkPath::isNestedRects(SkRect rects[2], Direction dirs[2]) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000526 SkDEBUGCODE(this->validate();)
527 int currVerb = 0;
528 const SkPoint* pts = fPathRef->points();
529 const SkPoint* first = pts;
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000530 Direction testDirs[2];
531 if (!isRectContour(true, &currVerb, &pts, NULL, &testDirs[0])) {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000532 return false;
533 }
534 const SkPoint* last = pts;
535 SkRect testRects[2];
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000536 if (isRectContour(false, &currVerb, &pts, NULL, &testDirs[1])) {
scroggo@google.com614f9e32013-05-09 18:05:32 +0000537 testRects[0].set(first, SkToS32(last - first));
538 testRects[1].set(last, SkToS32(pts - last));
caryclark@google.com56f233a2012-11-19 13:06:06 +0000539 if (testRects[0].contains(testRects[1])) {
540 if (rects) {
541 rects[0] = testRects[0];
542 rects[1] = testRects[1];
543 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000544 if (dirs) {
545 dirs[0] = testDirs[0];
546 dirs[1] = testDirs[1];
547 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000548 return true;
549 }
550 if (testRects[1].contains(testRects[0])) {
551 if (rects) {
552 rects[0] = testRects[1];
553 rects[1] = testRects[0];
554 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000555 if (dirs) {
556 dirs[0] = testDirs[1];
557 dirs[1] = testDirs[0];
558 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000559 return true;
560 }
561 }
562 return false;
563}
564
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000565int SkPath::countPoints() const {
566 return fPathRef->countPoints();
567}
568
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000569int SkPath::getPoints(SkPoint dst[], int max) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000570 SkDEBUGCODE(this->validate();)
571
572 SkASSERT(max >= 0);
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000573 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000574 int count = SkMin32(max, fPathRef->countPoints());
575 memcpy(dst, fPathRef->points(), count * sizeof(SkPoint));
576 return fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000577}
578
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000579SkPoint SkPath::getPoint(int index) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000580 if ((unsigned)index < (unsigned)fPathRef->countPoints()) {
581 return fPathRef->atPoint(index);
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000582 }
583 return SkPoint::Make(0, 0);
584}
585
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000586int SkPath::countVerbs() const {
587 return fPathRef->countVerbs();
588}
589
590static inline void copy_verbs_reverse(uint8_t* inorderDst,
591 const uint8_t* reversedSrc,
592 int count) {
593 for (int i = 0; i < count; ++i) {
594 inorderDst[i] = reversedSrc[~i];
595 }
596}
597
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000598int SkPath::getVerbs(uint8_t dst[], int max) const {
599 SkDEBUGCODE(this->validate();)
600
601 SkASSERT(max >= 0);
602 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000603 int count = SkMin32(max, fPathRef->countVerbs());
604 copy_verbs_reverse(dst, fPathRef->verbs(), count);
605 return fPathRef->countVerbs();
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000606}
607
reed@google.com294dd7b2011-10-11 11:58:32 +0000608bool SkPath::getLastPt(SkPoint* lastPt) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000609 SkDEBUGCODE(this->validate();)
610
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000611 int count = fPathRef->countPoints();
reed@google.com294dd7b2011-10-11 11:58:32 +0000612 if (count > 0) {
613 if (lastPt) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000614 *lastPt = fPathRef->atPoint(count - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000615 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000616 return true;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000617 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000618 if (lastPt) {
619 lastPt->set(0, 0);
620 }
621 return false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000622}
623
624void SkPath::setLastPt(SkScalar x, SkScalar y) {
625 SkDEBUGCODE(this->validate();)
626
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000627 int count = fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000628 if (count == 0) {
629 this->moveTo(x, y);
630 } else {
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000631 fIsOval = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000632 SkPathRef::Editor ed(&fPathRef);
633 ed.atPoint(count-1)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000634 }
635}
636
reed@google.com04863fa2011-05-15 04:08:24 +0000637void SkPath::setConvexity(Convexity c) {
638 if (fConvexity != c) {
639 fConvexity = c;
reed@google.com04863fa2011-05-15 04:08:24 +0000640 }
641}
642
reed@android.com8a1c16f2008-12-17 15:59:43 +0000643//////////////////////////////////////////////////////////////////////////////
644// Construction methods
645
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000646#define DIRTY_AFTER_EDIT \
647 do { \
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000648 fConvexity = kUnknown_Convexity; \
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000649 fDirection = kUnknown_Direction; \
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000650 fIsOval = false; \
reed@google.comb54455e2011-05-16 14:16:04 +0000651 } while (0)
652
reed@android.com8a1c16f2008-12-17 15:59:43 +0000653void SkPath::incReserve(U16CPU inc) {
654 SkDEBUGCODE(this->validate();)
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000655 SkPathRef::Editor(&fPathRef, inc, inc);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000656 SkDEBUGCODE(this->validate();)
657}
658
659void SkPath::moveTo(SkScalar x, SkScalar y) {
660 SkDEBUGCODE(this->validate();)
661
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000662 SkPathRef::Editor ed(&fPathRef);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000663
reed@google.comd335d1d2012-01-12 18:17:11 +0000664 // remember our index
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000665 fLastMoveToIndex = ed.pathRef()->countPoints();
reed@google.comd335d1d2012-01-12 18:17:11 +0000666
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000667 ed.growForVerb(kMove_Verb)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000668}
669
670void SkPath::rMoveTo(SkScalar x, SkScalar y) {
671 SkPoint pt;
672 this->getLastPt(&pt);
673 this->moveTo(pt.fX + x, pt.fY + y);
674}
675
reed@google.comd335d1d2012-01-12 18:17:11 +0000676void SkPath::injectMoveToIfNeeded() {
677 if (fLastMoveToIndex < 0) {
678 SkScalar x, y;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000679 if (fPathRef->countVerbs() == 0) {
reed@google.comd335d1d2012-01-12 18:17:11 +0000680 x = y = 0;
681 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000682 const SkPoint& pt = fPathRef->atPoint(~fLastMoveToIndex);
reed@google.comd335d1d2012-01-12 18:17:11 +0000683 x = pt.fX;
684 y = pt.fY;
685 }
686 this->moveTo(x, y);
687 }
688}
689
reed@android.com8a1c16f2008-12-17 15:59:43 +0000690void SkPath::lineTo(SkScalar x, SkScalar y) {
691 SkDEBUGCODE(this->validate();)
692
reed@google.comd335d1d2012-01-12 18:17:11 +0000693 this->injectMoveToIfNeeded();
694
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000695 SkPathRef::Editor ed(&fPathRef);
696 ed.growForVerb(kLine_Verb)->set(x, y);
reed@google.com10296cc2011-09-21 12:29:05 +0000697 fSegmentMask |= kLine_SegmentMask;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000698
reed@google.comb54455e2011-05-16 14:16:04 +0000699 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000700}
701
702void SkPath::rLineTo(SkScalar x, SkScalar y) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000703 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000704 SkPoint pt;
705 this->getLastPt(&pt);
706 this->lineTo(pt.fX + x, pt.fY + y);
707}
708
709void SkPath::quadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
710 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000711
reed@google.comd335d1d2012-01-12 18:17:11 +0000712 this->injectMoveToIfNeeded();
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000713
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000714 SkPathRef::Editor ed(&fPathRef);
715 SkPoint* pts = ed.growForVerb(kQuad_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000716 pts[0].set(x1, y1);
717 pts[1].set(x2, y2);
reed@google.com10296cc2011-09-21 12:29:05 +0000718 fSegmentMask |= kQuad_SegmentMask;
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000719
reed@google.comb54455e2011-05-16 14:16:04 +0000720 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000721}
722
723void SkPath::rQuadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000724 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000725 SkPoint pt;
726 this->getLastPt(&pt);
727 this->quadTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2);
728}
729
reed@google.com277c3f82013-05-31 15:17:50 +0000730void SkPath::conicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
731 SkScalar w) {
732 // check for <= 0 or NaN with this test
733 if (!(w > 0)) {
734 this->lineTo(x2, y2);
735 } else if (!SkScalarIsFinite(w)) {
736 this->lineTo(x1, y1);
737 this->lineTo(x2, y2);
738 } else if (SK_Scalar1 == w) {
739 this->quadTo(x1, y1, x2, y2);
740 } else {
741 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000742
reed@google.com277c3f82013-05-31 15:17:50 +0000743 this->injectMoveToIfNeeded();
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000744
reed@google.com277c3f82013-05-31 15:17:50 +0000745 SkPathRef::Editor ed(&fPathRef);
746 SkPoint* pts = ed.growForConic(w);
747 pts[0].set(x1, y1);
748 pts[1].set(x2, y2);
749 fSegmentMask |= kConic_SegmentMask;
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000750
reed@google.com277c3f82013-05-31 15:17:50 +0000751 DIRTY_AFTER_EDIT;
752 }
753}
754
755void SkPath::rConicTo(SkScalar dx1, SkScalar dy1, SkScalar dx2, SkScalar dy2,
756 SkScalar w) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000757 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@google.com277c3f82013-05-31 15:17:50 +0000758 SkPoint pt;
759 this->getLastPt(&pt);
760 this->conicTo(pt.fX + dx1, pt.fY + dy1, pt.fX + dx2, pt.fY + dy2, w);
761}
762
reed@android.com8a1c16f2008-12-17 15:59:43 +0000763void SkPath::cubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
764 SkScalar x3, SkScalar y3) {
765 SkDEBUGCODE(this->validate();)
766
reed@google.comd335d1d2012-01-12 18:17:11 +0000767 this->injectMoveToIfNeeded();
768
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000769 SkPathRef::Editor ed(&fPathRef);
770 SkPoint* pts = ed.growForVerb(kCubic_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000771 pts[0].set(x1, y1);
772 pts[1].set(x2, y2);
773 pts[2].set(x3, y3);
reed@google.com10296cc2011-09-21 12:29:05 +0000774 fSegmentMask |= kCubic_SegmentMask;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000775
reed@google.comb54455e2011-05-16 14:16:04 +0000776 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000777}
778
779void SkPath::rCubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
780 SkScalar x3, SkScalar y3) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000781 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000782 SkPoint pt;
783 this->getLastPt(&pt);
784 this->cubicTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2,
785 pt.fX + x3, pt.fY + y3);
786}
787
788void SkPath::close() {
789 SkDEBUGCODE(this->validate();)
790
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000791 int count = fPathRef->countVerbs();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000792 if (count > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000793 switch (fPathRef->atVerb(count - 1)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000794 case kLine_Verb:
795 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000796 case kConic_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +0000797 case kCubic_Verb:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000798 case kMove_Verb: {
799 SkPathRef::Editor ed(&fPathRef);
800 ed.growForVerb(kClose_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000801 break;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000802 }
reed@google.com277c3f82013-05-31 15:17:50 +0000803 case kClose_Verb:
reed@google.comfa2f2a42013-05-30 15:29:48 +0000804 // don't add a close if it's the first verb or a repeat
reed@google.com7950a9e2013-05-30 14:57:55 +0000805 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000806 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000807 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000808 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000809 }
810 }
reed@google.comd335d1d2012-01-12 18:17:11 +0000811
812 // signal that we need a moveTo to follow us (unless we're done)
813#if 0
814 if (fLastMoveToIndex >= 0) {
815 fLastMoveToIndex = ~fLastMoveToIndex;
816 }
817#else
818 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
819#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000820}
821
822///////////////////////////////////////////////////////////////////////////////
reed@google.comabf15c12011-01-18 20:35:51 +0000823
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000824static void assert_known_direction(int dir) {
825 SkASSERT(SkPath::kCW_Direction == dir || SkPath::kCCW_Direction == dir);
826}
827
reed@android.com8a1c16f2008-12-17 15:59:43 +0000828void SkPath::addRect(const SkRect& rect, Direction dir) {
829 this->addRect(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, dir);
830}
831
832void SkPath::addRect(SkScalar left, SkScalar top, SkScalar right,
833 SkScalar bottom, Direction dir) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000834 assert_known_direction(dir);
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000835 fDirection = this->hasOnlyMoveTos() ? dir : kUnknown_Direction;
836 SkAutoDisableDirectionCheck addc(this);
837
reed@android.com8a1c16f2008-12-17 15:59:43 +0000838 SkAutoPathBoundsUpdate apbu(this, left, top, right, bottom);
839
840 this->incReserve(5);
841
842 this->moveTo(left, top);
843 if (dir == kCCW_Direction) {
844 this->lineTo(left, bottom);
845 this->lineTo(right, bottom);
846 this->lineTo(right, top);
847 } else {
848 this->lineTo(right, top);
849 this->lineTo(right, bottom);
850 this->lineTo(left, bottom);
851 }
852 this->close();
853}
854
reed@google.com744faba2012-05-29 19:54:52 +0000855void SkPath::addPoly(const SkPoint pts[], int count, bool close) {
856 SkDEBUGCODE(this->validate();)
857 if (count <= 0) {
858 return;
859 }
860
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000861 SkPathRef::Editor ed(&fPathRef);
862 fLastMoveToIndex = ed.pathRef()->countPoints();
863 uint8_t* vb;
864 SkPoint* p;
bsalomon@google.com6c5418e2012-09-14 20:30:37 +0000865 // +close makes room for the extra kClose_Verb
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000866 ed.grow(count + close, count, &vb, &p);
bsalomon@google.com6c5418e2012-09-14 20:30:37 +0000867
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000868 memcpy(p, pts, count * sizeof(SkPoint));
869 vb[~0] = kMove_Verb;
reed@google.com744faba2012-05-29 19:54:52 +0000870 if (count > 1) {
871 // cast to unsigned, so if MIN_COUNT_FOR_MEMSET_TO_BE_FAST is defined to
872 // be 0, the compiler will remove the test/branch entirely.
873 if ((unsigned)count >= MIN_COUNT_FOR_MEMSET_TO_BE_FAST) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000874 memset(vb - count, kLine_Verb, count - 1);
reed@google.com744faba2012-05-29 19:54:52 +0000875 } else {
876 for (int i = 1; i < count; ++i) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000877 vb[~i] = kLine_Verb;
reed@google.com744faba2012-05-29 19:54:52 +0000878 }
879 }
880 fSegmentMask |= kLine_SegmentMask;
881 }
882 if (close) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000883 vb[~count] = kClose_Verb;
reed@google.com744faba2012-05-29 19:54:52 +0000884 }
885
reed@google.com744faba2012-05-29 19:54:52 +0000886 DIRTY_AFTER_EDIT;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000887 SkDEBUGCODE(this->validate();)
reed@google.com744faba2012-05-29 19:54:52 +0000888}
889
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000890#include "SkGeometry.h"
891
892static int build_arc_points(const SkRect& oval, SkScalar startAngle,
893 SkScalar sweepAngle,
894 SkPoint pts[kSkBuildQuadArcStorage]) {
895
896 if (0 == sweepAngle &&
897 (0 == startAngle || SkIntToScalar(360) == startAngle)) {
898 // Chrome uses this path to move into and out of ovals. If not
899 // treated as a special case the moves can distort the oval's
900 // bounding box (and break the circle special case).
901 pts[0].set(oval.fRight, oval.centerY());
902 return 1;
903 } else if (0 == oval.width() && 0 == oval.height()) {
904 // Chrome will sometimes create 0 radius round rects. Having degenerate
905 // quad segments in the path prevents the path from being recognized as
906 // a rect.
907 // TODO: optimizing the case where only one of width or height is zero
908 // should also be considered. This case, however, doesn't seem to be
909 // as common as the single point case.
910 pts[0].set(oval.fRight, oval.fTop);
911 return 1;
912 }
913
914 SkVector start, stop;
915
916 start.fY = SkScalarSinCos(SkDegreesToRadians(startAngle), &start.fX);
917 stop.fY = SkScalarSinCos(SkDegreesToRadians(startAngle + sweepAngle),
918 &stop.fX);
919
920 /* If the sweep angle is nearly (but less than) 360, then due to precision
921 loss in radians-conversion and/or sin/cos, we may end up with coincident
922 vectors, which will fool SkBuildQuadArc into doing nothing (bad) instead
923 of drawing a nearly complete circle (good).
924 e.g. canvas.drawArc(0, 359.99, ...)
925 -vs- canvas.drawArc(0, 359.9, ...)
926 We try to detect this edge case, and tweak the stop vector
927 */
928 if (start == stop) {
929 SkScalar sw = SkScalarAbs(sweepAngle);
930 if (sw < SkIntToScalar(360) && sw > SkIntToScalar(359)) {
931 SkScalar stopRad = SkDegreesToRadians(startAngle + sweepAngle);
932 // make a guess at a tiny angle (in radians) to tweak by
933 SkScalar deltaRad = SkScalarCopySign(SK_Scalar1/512, sweepAngle);
934 // not sure how much will be enough, so we use a loop
935 do {
936 stopRad -= deltaRad;
937 stop.fY = SkScalarSinCos(stopRad, &stop.fX);
938 } while (start == stop);
939 }
940 }
941
942 SkMatrix matrix;
943
944 matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height()));
945 matrix.postTranslate(oval.centerX(), oval.centerY());
946
947 return SkBuildQuadArc(start, stop,
948 sweepAngle > 0 ? kCW_SkRotationDirection :
949 kCCW_SkRotationDirection,
950 &matrix, pts);
951}
952
reed@android.com8a1c16f2008-12-17 15:59:43 +0000953static void add_corner_arc(SkPath* path, const SkRect& rect,
954 SkScalar rx, SkScalar ry, int startAngle,
robertphillips@google.com12367192013-10-20 13:11:16 +0000955 SkPath::Direction dir, bool forceMoveTo) {
skia.committer@gmail.com7a03d862012-12-18 02:03:03 +0000956 // These two asserts are not sufficient, since really we want to know
957 // that the pair of radii (e.g. left and right, or top and bottom) sum
958 // to <= dimension, but we don't have that data here, so we just have
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +0000959 // these conservative asserts.
960 SkASSERT(0 <= rx && rx <= rect.width());
961 SkASSERT(0 <= ry && ry <= rect.height());
reed@google.comabf15c12011-01-18 20:35:51 +0000962
reed@android.com8a1c16f2008-12-17 15:59:43 +0000963 SkRect r;
964 r.set(-rx, -ry, rx, ry);
965
966 switch (startAngle) {
967 case 0:
968 r.offset(rect.fRight - r.fRight, rect.fBottom - r.fBottom);
969 break;
970 case 90:
971 r.offset(rect.fLeft - r.fLeft, rect.fBottom - r.fBottom);
972 break;
973 case 180: r.offset(rect.fLeft - r.fLeft, rect.fTop - r.fTop); break;
974 case 270: r.offset(rect.fRight - r.fRight, rect.fTop - r.fTop); break;
tomhudson@google.com0c00f212011-12-28 14:59:50 +0000975 default: SkDEBUGFAIL("unexpected startAngle in add_corner_arc");
reed@android.com8a1c16f2008-12-17 15:59:43 +0000976 }
reed@google.comabf15c12011-01-18 20:35:51 +0000977
reed@android.com8a1c16f2008-12-17 15:59:43 +0000978 SkScalar start = SkIntToScalar(startAngle);
979 SkScalar sweep = SkIntToScalar(90);
980 if (SkPath::kCCW_Direction == dir) {
981 start += sweep;
982 sweep = -sweep;
983 }
reed@google.comabf15c12011-01-18 20:35:51 +0000984
robertphillips@google.com12367192013-10-20 13:11:16 +0000985 path->arcTo(r, start, sweep, forceMoveTo);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000986}
987
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +0000988void SkPath::addRoundRect(const SkRect& rect, const SkScalar radii[],
reed@android.com8a1c16f2008-12-17 15:59:43 +0000989 Direction dir) {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +0000990 SkRRect rrect;
991 rrect.setRectRadii(rect, (const SkVector*) radii);
992 this->addRRect(rrect, dir);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000993}
994
reed@google.com4ed0fb72012-12-12 20:48:18 +0000995void SkPath::addRRect(const SkRRect& rrect, Direction dir) {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +0000996 assert_known_direction(dir);
997
998 if (rrect.isEmpty()) {
999 return;
1000 }
1001
reed@google.com4ed0fb72012-12-12 20:48:18 +00001002 const SkRect& bounds = rrect.getBounds();
1003
1004 if (rrect.isRect()) {
1005 this->addRect(bounds, dir);
1006 } else if (rrect.isOval()) {
1007 this->addOval(bounds, dir);
1008 } else if (rrect.isSimple()) {
1009 const SkVector& rad = rrect.getSimpleRadii();
1010 this->addRoundRect(bounds, rad.x(), rad.y(), dir);
1011 } else {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001012 SkAutoPathBoundsUpdate apbu(this, bounds);
1013
1014 if (kCW_Direction == dir) {
robertphillips@google.com12367192013-10-20 13:11:16 +00001015 add_corner_arc(this, bounds, rrect.fRadii[0].fX, rrect.fRadii[0].fY, 180, dir, true);
1016 add_corner_arc(this, bounds, rrect.fRadii[1].fX, rrect.fRadii[1].fY, 270, dir, false);
1017 add_corner_arc(this, bounds, rrect.fRadii[2].fX, rrect.fRadii[2].fY, 0, dir, false);
1018 add_corner_arc(this, bounds, rrect.fRadii[3].fX, rrect.fRadii[3].fY, 90, dir, false);
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001019 } else {
robertphillips@google.com12367192013-10-20 13:11:16 +00001020 add_corner_arc(this, bounds, rrect.fRadii[0].fX, rrect.fRadii[0].fY, 180, dir, true);
1021 add_corner_arc(this, bounds, rrect.fRadii[3].fX, rrect.fRadii[3].fY, 90, dir, false);
1022 add_corner_arc(this, bounds, rrect.fRadii[2].fX, rrect.fRadii[2].fY, 0, dir, false);
1023 add_corner_arc(this, bounds, rrect.fRadii[1].fX, rrect.fRadii[1].fY, 270, dir, false);
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001024 }
1025 this->close();
reed@google.com4ed0fb72012-12-12 20:48:18 +00001026 }
1027}
1028
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001029bool SkPath::hasOnlyMoveTos() const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001030 int count = fPathRef->countVerbs();
1031 const uint8_t* verbs = const_cast<const SkPathRef*>(fPathRef.get())->verbsMemBegin();
1032 for (int i = 0; i < count; ++i) {
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001033 if (*verbs == kLine_Verb ||
1034 *verbs == kQuad_Verb ||
1035 *verbs == kCubic_Verb) {
1036 return false;
1037 }
1038 ++verbs;
1039 }
1040 return true;
1041}
1042
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001043#ifdef SK_IGNORE_QUAD_RR_CORNERS_OPT
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001044#define CUBIC_ARC_FACTOR ((SK_ScalarSqrt2 - SK_Scalar1) * 4 / 3)
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001045#endif
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001046
1047void SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
1048 Direction dir) {
1049 assert_known_direction(dir);
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001050
humper@google.com75e3ca12013-04-08 21:44:11 +00001051 if (rx < 0 || ry < 0) {
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001052 SkErrorInternals::SetError( kInvalidArgument_SkError,
humper@google.com75e3ca12013-04-08 21:44:11 +00001053 "I got %f and %f as radii to SkPath::AddRoundRect, "
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001054 "but negative radii are not allowed.",
humper@google.com75e3ca12013-04-08 21:44:11 +00001055 SkScalarToDouble(rx), SkScalarToDouble(ry) );
1056 return;
1057 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001058
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001059 SkScalar w = rect.width();
1060 SkScalar halfW = SkScalarHalf(w);
1061 SkScalar h = rect.height();
1062 SkScalar halfH = SkScalarHalf(h);
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001063
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001064 if (halfW <= 0 || halfH <= 0) {
1065 return;
1066 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001067
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001068 bool skip_hori = rx >= halfW;
1069 bool skip_vert = ry >= halfH;
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001070
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001071 if (skip_hori && skip_vert) {
1072 this->addOval(rect, dir);
1073 return;
1074 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001075
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001076 fDirection = this->hasOnlyMoveTos() ? dir : kUnknown_Direction;
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001077
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001078 SkAutoPathBoundsUpdate apbu(this, rect);
1079 SkAutoDisableDirectionCheck(this);
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001080
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001081 if (skip_hori) {
1082 rx = halfW;
1083 } else if (skip_vert) {
1084 ry = halfH;
1085 }
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001086#ifdef SK_IGNORE_QUAD_RR_CORNERS_OPT
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001087 SkScalar sx = SkScalarMul(rx, CUBIC_ARC_FACTOR);
1088 SkScalar sy = SkScalarMul(ry, CUBIC_ARC_FACTOR);
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001089
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001090 this->incReserve(17);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001091#else
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00001092// The mid point of the quadratic arc approximation is half way between the two
1093// control points. The float epsilon adjustment moves the on curve point out by
1094// two bits, distributing the convex test error between the round rect approximation
1095// and the convex cross product sign equality test.
1096 SkScalar midPtX = rx * (SK_Scalar1 + SK_ScalarTanPIOver8 + FLT_EPSILON * 4) / 2;
1097 SkScalar midPtY = ry * (SK_Scalar1 + SK_ScalarTanPIOver8 + FLT_EPSILON * 4) / 2;
robertphillips@google.com12367192013-10-20 13:11:16 +00001098
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00001099 SkScalar offPtX = rx * SK_ScalarTanPIOver8;
1100 SkScalar offPtY = ry * SK_ScalarTanPIOver8;
robertphillips@google.com12367192013-10-20 13:11:16 +00001101
1102 this->incReserve(21);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001103#endif
robertphillips@google.com12367192013-10-20 13:11:16 +00001104 this->moveTo(rect.fRight - rx, rect.fTop); // top-right
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001105 if (dir == kCCW_Direction) {
1106 if (!skip_hori) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001107 this->lineTo(rect.fLeft + rx, rect.fTop); // top
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001108 }
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001109#ifdef SK_IGNORE_QUAD_RR_CORNERS_OPT
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001110 this->cubicTo(rect.fLeft + rx - sx, rect.fTop,
1111 rect.fLeft, rect.fTop + ry - sy,
1112 rect.fLeft, rect.fTop + ry); // top-left
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001113#else
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00001114 this->quadTo(rect.fLeft + rx - offPtX, rect.fTop,
robertphillips@google.com12367192013-10-20 13:11:16 +00001115 rect.fLeft + rx - midPtX, rect.fTop + ry - midPtY);
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00001116 this->quadTo(rect.fLeft, rect.fTop + ry - offPtY,
robertphillips@google.com12367192013-10-20 13:11:16 +00001117 rect.fLeft, rect.fTop + ry);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001118#endif
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001119 if (!skip_vert) {
1120 this->lineTo(rect.fLeft, rect.fBottom - ry); // left
1121 }
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001122#ifdef SK_IGNORE_QUAD_RR_CORNERS_OPT
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001123 this->cubicTo(rect.fLeft, rect.fBottom - ry + sy,
1124 rect.fLeft + rx - sx, rect.fBottom,
1125 rect.fLeft + rx, rect.fBottom); // bot-left
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001126#else
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00001127 this->quadTo(rect.fLeft, rect.fBottom - ry + offPtY,
robertphillips@google.com12367192013-10-20 13:11:16 +00001128 rect.fLeft + rx - midPtX, rect.fBottom - ry + midPtY);
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00001129 this->quadTo(rect.fLeft + rx - offPtX, rect.fBottom,
robertphillips@google.com12367192013-10-20 13:11:16 +00001130 rect.fLeft + rx, rect.fBottom);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001131#endif
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001132 if (!skip_hori) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001133 this->lineTo(rect.fRight - rx, rect.fBottom); // bottom
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001134 }
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001135#ifdef SK_IGNORE_QUAD_RR_CORNERS_OPT
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001136 this->cubicTo(rect.fRight - rx + sx, rect.fBottom,
1137 rect.fRight, rect.fBottom - ry + sy,
1138 rect.fRight, rect.fBottom - ry); // bot-right
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001139#else
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00001140 this->quadTo(rect.fRight - rx + offPtX, rect.fBottom,
robertphillips@google.com12367192013-10-20 13:11:16 +00001141 rect.fRight - rx + midPtX, rect.fBottom - ry + midPtY);
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00001142 this->quadTo(rect.fRight, rect.fBottom - ry + offPtY,
robertphillips@google.com12367192013-10-20 13:11:16 +00001143 rect.fRight, rect.fBottom - ry);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001144#endif
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001145 if (!skip_vert) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001146 this->lineTo(rect.fRight, rect.fTop + ry); // right
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001147 }
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001148#ifdef SK_IGNORE_QUAD_RR_CORNERS_OPT
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001149 this->cubicTo(rect.fRight, rect.fTop + ry - sy,
1150 rect.fRight - rx + sx, rect.fTop,
1151 rect.fRight - rx, rect.fTop); // top-right
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001152#else
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00001153 this->quadTo(rect.fRight, rect.fTop + ry - offPtY,
robertphillips@google.com12367192013-10-20 13:11:16 +00001154 rect.fRight - rx + midPtX, rect.fTop + ry - midPtY);
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00001155 this->quadTo(rect.fRight - rx + offPtX, rect.fTop,
robertphillips@google.com12367192013-10-20 13:11:16 +00001156 rect.fRight - rx, rect.fTop);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001157#endif
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001158 } else {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001159#ifdef SK_IGNORE_QUAD_RR_CORNERS_OPT
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001160 this->cubicTo(rect.fRight - rx + sx, rect.fTop,
1161 rect.fRight, rect.fTop + ry - sy,
1162 rect.fRight, rect.fTop + ry); // top-right
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001163#else
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00001164 this->quadTo(rect.fRight - rx + offPtX, rect.fTop,
robertphillips@google.com12367192013-10-20 13:11:16 +00001165 rect.fRight - rx + midPtX, rect.fTop + ry - midPtY);
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00001166 this->quadTo(rect.fRight, rect.fTop + ry - offPtY,
robertphillips@google.com12367192013-10-20 13:11:16 +00001167 rect.fRight, rect.fTop + ry);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001168#endif
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001169 if (!skip_vert) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001170 this->lineTo(rect.fRight, rect.fBottom - ry); // right
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001171 }
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001172#ifdef SK_IGNORE_QUAD_RR_CORNERS_OPT
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001173 this->cubicTo(rect.fRight, rect.fBottom - ry + sy,
1174 rect.fRight - rx + sx, rect.fBottom,
1175 rect.fRight - rx, rect.fBottom); // bot-right
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001176#else
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00001177 this->quadTo(rect.fRight, rect.fBottom - ry + offPtY,
robertphillips@google.com12367192013-10-20 13:11:16 +00001178 rect.fRight - rx + midPtX, rect.fBottom - ry + midPtY);
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00001179 this->quadTo(rect.fRight - rx + offPtX, rect.fBottom,
robertphillips@google.com12367192013-10-20 13:11:16 +00001180 rect.fRight - rx, rect.fBottom);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001181#endif
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001182 if (!skip_hori) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001183 this->lineTo(rect.fLeft + rx, rect.fBottom); // bottom
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001184 }
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001185#ifdef SK_IGNORE_QUAD_RR_CORNERS_OPT
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001186 this->cubicTo(rect.fLeft + rx - sx, rect.fBottom,
1187 rect.fLeft, rect.fBottom - ry + sy,
1188 rect.fLeft, rect.fBottom - ry); // bot-left
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001189#else
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00001190 this->quadTo(rect.fLeft + rx - offPtX, rect.fBottom,
robertphillips@google.com12367192013-10-20 13:11:16 +00001191 rect.fLeft + rx - midPtX, rect.fBottom - ry + midPtY);
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00001192 this->quadTo(rect.fLeft, rect.fBottom - ry + offPtY,
robertphillips@google.com12367192013-10-20 13:11:16 +00001193 rect.fLeft, rect.fBottom - ry);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001194#endif
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001195 if (!skip_vert) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001196 this->lineTo(rect.fLeft, rect.fTop + ry); // left
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001197 }
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001198#ifdef SK_IGNORE_QUAD_RR_CORNERS_OPT
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001199 this->cubicTo(rect.fLeft, rect.fTop + ry - sy,
1200 rect.fLeft + rx - sx, rect.fTop,
1201 rect.fLeft + rx, rect.fTop); // top-left
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001202#else
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00001203 this->quadTo(rect.fLeft, rect.fTop + ry - offPtY,
robertphillips@google.com12367192013-10-20 13:11:16 +00001204 rect.fLeft + rx - midPtX, rect.fTop + ry - midPtY);
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00001205 this->quadTo(rect.fLeft + rx - offPtX, rect.fTop,
robertphillips@google.com12367192013-10-20 13:11:16 +00001206 rect.fLeft + rx, rect.fTop);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001207#endif
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001208 if (!skip_hori) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001209 this->lineTo(rect.fRight - rx, rect.fTop); // top
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001210 }
1211 }
1212 this->close();
1213}
1214
reed@android.com8a1c16f2008-12-17 15:59:43 +00001215void SkPath::addOval(const SkRect& oval, Direction dir) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +00001216 assert_known_direction(dir);
1217
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001218 /* If addOval() is called after previous moveTo(),
1219 this path is still marked as an oval. This is used to
1220 fit into WebKit's calling sequences.
1221 We can't simply check isEmpty() in this case, as additional
1222 moveTo() would mark the path non empty.
1223 */
1224 fIsOval = hasOnlyMoveTos();
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001225 if (fIsOval) {
1226 fDirection = dir;
1227 } else {
1228 fDirection = kUnknown_Direction;
1229 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001230
1231 SkAutoDisableOvalCheck adoc(this);
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001232 SkAutoDisableDirectionCheck addc(this);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001233
reed@android.com8a1c16f2008-12-17 15:59:43 +00001234 SkAutoPathBoundsUpdate apbu(this, oval);
1235
1236 SkScalar cx = oval.centerX();
1237 SkScalar cy = oval.centerY();
1238 SkScalar rx = SkScalarHalf(oval.width());
1239 SkScalar ry = SkScalarHalf(oval.height());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001240
reed@android.com8a1c16f2008-12-17 15:59:43 +00001241 SkScalar sx = SkScalarMul(rx, SK_ScalarTanPIOver8);
1242 SkScalar sy = SkScalarMul(ry, SK_ScalarTanPIOver8);
1243 SkScalar mx = SkScalarMul(rx, SK_ScalarRoot2Over2);
1244 SkScalar my = SkScalarMul(ry, SK_ScalarRoot2Over2);
1245
1246 /*
1247 To handle imprecision in computing the center and radii, we revert to
1248 the provided bounds when we can (i.e. use oval.fLeft instead of cx-rx)
1249 to ensure that we don't exceed the oval's bounds *ever*, since we want
1250 to use oval for our fast-bounds, rather than have to recompute it.
1251 */
1252 const SkScalar L = oval.fLeft; // cx - rx
1253 const SkScalar T = oval.fTop; // cy - ry
1254 const SkScalar R = oval.fRight; // cx + rx
1255 const SkScalar B = oval.fBottom; // cy + ry
1256
1257 this->incReserve(17); // 8 quads + close
1258 this->moveTo(R, cy);
1259 if (dir == kCCW_Direction) {
1260 this->quadTo( R, cy - sy, cx + mx, cy - my);
1261 this->quadTo(cx + sx, T, cx , T);
1262 this->quadTo(cx - sx, T, cx - mx, cy - my);
1263 this->quadTo( L, cy - sy, L, cy );
1264 this->quadTo( L, cy + sy, cx - mx, cy + my);
1265 this->quadTo(cx - sx, B, cx , B);
1266 this->quadTo(cx + sx, B, cx + mx, cy + my);
1267 this->quadTo( R, cy + sy, R, cy );
1268 } else {
1269 this->quadTo( R, cy + sy, cx + mx, cy + my);
1270 this->quadTo(cx + sx, B, cx , B);
1271 this->quadTo(cx - sx, B, cx - mx, cy + my);
1272 this->quadTo( L, cy + sy, L, cy );
1273 this->quadTo( L, cy - sy, cx - mx, cy - my);
1274 this->quadTo(cx - sx, T, cx , T);
1275 this->quadTo(cx + sx, T, cx + mx, cy - my);
1276 this->quadTo( R, cy - sy, R, cy );
1277 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001278 this->close();
1279}
1280
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001281bool SkPath::isOval(SkRect* rect) const {
1282 if (fIsOval && rect) {
1283 *rect = getBounds();
1284 }
1285
1286 return fIsOval;
1287}
1288
reed@android.com8a1c16f2008-12-17 15:59:43 +00001289void SkPath::addCircle(SkScalar x, SkScalar y, SkScalar r, Direction dir) {
1290 if (r > 0) {
1291 SkRect rect;
1292 rect.set(x - r, y - r, x + r, y + r);
1293 this->addOval(rect, dir);
1294 }
1295}
1296
reed@android.com8a1c16f2008-12-17 15:59:43 +00001297void SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
1298 bool forceMoveTo) {
1299 if (oval.width() < 0 || oval.height() < 0) {
1300 return;
1301 }
1302
1303 SkPoint pts[kSkBuildQuadArcStorage];
1304 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
1305 SkASSERT((count & 1) == 1);
1306
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001307 if (fPathRef->countVerbs() == 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001308 forceMoveTo = true;
1309 }
1310 this->incReserve(count);
1311 forceMoveTo ? this->moveTo(pts[0]) : this->lineTo(pts[0]);
1312 for (int i = 1; i < count; i += 2) {
1313 this->quadTo(pts[i], pts[i+1]);
1314 }
1315}
1316
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001317void SkPath::addArc(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001318 if (oval.isEmpty() || 0 == sweepAngle) {
1319 return;
1320 }
1321
1322 const SkScalar kFullCircleAngle = SkIntToScalar(360);
1323
1324 if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
1325 this->addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction);
1326 return;
1327 }
1328
1329 SkPoint pts[kSkBuildQuadArcStorage];
1330 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
1331
1332 this->incReserve(count);
1333 this->moveTo(pts[0]);
1334 for (int i = 1; i < count; i += 2) {
1335 this->quadTo(pts[i], pts[i+1]);
1336 }
1337}
1338
1339/*
1340 Need to handle the case when the angle is sharp, and our computed end-points
1341 for the arc go behind pt1 and/or p2...
1342*/
1343void SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
1344 SkScalar radius) {
1345 SkVector before, after;
reed@google.comabf15c12011-01-18 20:35:51 +00001346
reed@android.com8a1c16f2008-12-17 15:59:43 +00001347 // need to know our prev pt so we can construct tangent vectors
1348 {
1349 SkPoint start;
1350 this->getLastPt(&start);
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001351 // Handle degenerate cases by adding a line to the first point and
1352 // bailing out.
1353 if ((x1 == start.fX && y1 == start.fY) ||
1354 (x1 == x2 && y1 == y2) ||
1355 radius == 0) {
1356 this->lineTo(x1, y1);
1357 return;
1358 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001359 before.setNormalize(x1 - start.fX, y1 - start.fY);
1360 after.setNormalize(x2 - x1, y2 - y1);
1361 }
reed@google.comabf15c12011-01-18 20:35:51 +00001362
reed@android.com8a1c16f2008-12-17 15:59:43 +00001363 SkScalar cosh = SkPoint::DotProduct(before, after);
1364 SkScalar sinh = SkPoint::CrossProduct(before, after);
1365
1366 if (SkScalarNearlyZero(sinh)) { // angle is too tight
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001367 this->lineTo(x1, y1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001368 return;
1369 }
reed@google.comabf15c12011-01-18 20:35:51 +00001370
reed@android.com8a1c16f2008-12-17 15:59:43 +00001371 SkScalar dist = SkScalarMulDiv(radius, SK_Scalar1 - cosh, sinh);
1372 if (dist < 0) {
1373 dist = -dist;
1374 }
1375
1376 SkScalar xx = x1 - SkScalarMul(dist, before.fX);
1377 SkScalar yy = y1 - SkScalarMul(dist, before.fY);
1378 SkRotationDirection arcDir;
1379
1380 // now turn before/after into normals
1381 if (sinh > 0) {
1382 before.rotateCCW();
1383 after.rotateCCW();
1384 arcDir = kCW_SkRotationDirection;
1385 } else {
1386 before.rotateCW();
1387 after.rotateCW();
1388 arcDir = kCCW_SkRotationDirection;
1389 }
1390
1391 SkMatrix matrix;
1392 SkPoint pts[kSkBuildQuadArcStorage];
reed@google.comabf15c12011-01-18 20:35:51 +00001393
reed@android.com8a1c16f2008-12-17 15:59:43 +00001394 matrix.setScale(radius, radius);
1395 matrix.postTranslate(xx - SkScalarMul(radius, before.fX),
1396 yy - SkScalarMul(radius, before.fY));
reed@google.comabf15c12011-01-18 20:35:51 +00001397
reed@android.com8a1c16f2008-12-17 15:59:43 +00001398 int count = SkBuildQuadArc(before, after, arcDir, &matrix, pts);
reed@google.comabf15c12011-01-18 20:35:51 +00001399
reed@android.com8a1c16f2008-12-17 15:59:43 +00001400 this->incReserve(count);
1401 // [xx,yy] == pts[0]
1402 this->lineTo(xx, yy);
1403 for (int i = 1; i < count; i += 2) {
1404 this->quadTo(pts[i], pts[i+1]);
1405 }
1406}
1407
1408///////////////////////////////////////////////////////////////////////////////
1409
1410void SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy) {
1411 SkMatrix matrix;
1412
1413 matrix.setTranslate(dx, dy);
1414 this->addPath(path, matrix);
1415}
1416
1417void SkPath::addPath(const SkPath& path, const SkMatrix& matrix) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001418 SkPathRef::Editor(&fPathRef, path.countVerbs(), path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001419
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001420 fIsOval = false;
1421
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001422 RawIter iter(path);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001423 SkPoint pts[4];
1424 Verb verb;
1425
1426 SkMatrix::MapPtsProc proc = matrix.getMapPtsProc();
1427
1428 while ((verb = iter.next(pts)) != kDone_Verb) {
1429 switch (verb) {
1430 case kMove_Verb:
1431 proc(matrix, &pts[0], &pts[0], 1);
1432 this->moveTo(pts[0]);
1433 break;
1434 case kLine_Verb:
1435 proc(matrix, &pts[1], &pts[1], 1);
1436 this->lineTo(pts[1]);
1437 break;
1438 case kQuad_Verb:
1439 proc(matrix, &pts[1], &pts[1], 2);
1440 this->quadTo(pts[1], pts[2]);
1441 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001442 case kConic_Verb:
1443 proc(matrix, &pts[1], &pts[1], 2);
1444 this->conicTo(pts[1], pts[2], iter.conicWeight());
1445 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001446 case kCubic_Verb:
1447 proc(matrix, &pts[1], &pts[1], 3);
1448 this->cubicTo(pts[1], pts[2], pts[3]);
1449 break;
1450 case kClose_Verb:
1451 this->close();
1452 break;
1453 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001454 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001455 }
1456 }
1457}
1458
1459///////////////////////////////////////////////////////////////////////////////
1460
reed@google.com277c3f82013-05-31 15:17:50 +00001461static int pts_in_verb(unsigned verb) {
1462 static const uint8_t gPtsInVerb[] = {
1463 1, // kMove
1464 1, // kLine
1465 2, // kQuad
1466 2, // kConic
1467 3, // kCubic
1468 0, // kClose
1469 0 // kDone
1470 };
1471
1472 SkASSERT(verb < SK_ARRAY_COUNT(gPtsInVerb));
1473 return gPtsInVerb[verb];
1474}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001475
1476// ignore the initial moveto, and stop when the 1st contour ends
1477void SkPath::pathTo(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001478 int i, vcount = path.fPathRef->countVerbs();
1479 // exit early if the path is empty, or just has a moveTo.
1480 if (vcount < 2) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001481 return;
1482 }
1483
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001484 SkPathRef::Editor(&fPathRef, vcount, path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001485
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001486 fIsOval = false;
1487
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001488 const uint8_t* verbs = path.fPathRef->verbs();
1489 // skip the initial moveTo
1490 const SkPoint* pts = path.fPathRef->points() + 1;
reed@google.com277c3f82013-05-31 15:17:50 +00001491 const SkScalar* conicWeight = path.fPathRef->conicWeights();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001492
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001493 SkASSERT(verbs[~0] == kMove_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001494 for (i = 1; i < vcount; i++) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001495 switch (verbs[~i]) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001496 case kLine_Verb:
1497 this->lineTo(pts[0].fX, pts[0].fY);
1498 break;
1499 case kQuad_Verb:
1500 this->quadTo(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY);
1501 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001502 case kConic_Verb:
1503 this->conicTo(pts[0], pts[1], *conicWeight++);
1504 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001505 case kCubic_Verb:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001506 this->cubicTo(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY, pts[2].fX, pts[2].fY);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001507 break;
1508 case kClose_Verb:
1509 return;
1510 }
reed@google.com277c3f82013-05-31 15:17:50 +00001511 pts += pts_in_verb(verbs[~i]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001512 }
1513}
1514
1515// ignore the last point of the 1st contour
1516void SkPath::reversePathTo(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001517 int i, vcount = path.fPathRef->countVerbs();
1518 // exit early if the path is empty, or just has a moveTo.
1519 if (vcount < 2) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001520 return;
1521 }
1522
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001523 SkPathRef::Editor(&fPathRef, vcount, path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001524
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001525 fIsOval = false;
1526
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001527 const uint8_t* verbs = path.fPathRef->verbs();
1528 const SkPoint* pts = path.fPathRef->points();
reed@google.com277c3f82013-05-31 15:17:50 +00001529 const SkScalar* conicWeights = path.fPathRef->conicWeights();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001530
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001531 SkASSERT(verbs[~0] == kMove_Verb);
1532 for (i = 1; i < vcount; ++i) {
reed@google.com277c3f82013-05-31 15:17:50 +00001533 unsigned v = verbs[~i];
1534 int n = pts_in_verb(v);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001535 if (n == 0) {
1536 break;
1537 }
1538 pts += n;
reed@google.com277c3f82013-05-31 15:17:50 +00001539 conicWeights += (SkPath::kConic_Verb == v);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001540 }
1541
1542 while (--i > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001543 switch (verbs[~i]) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001544 case kLine_Verb:
1545 this->lineTo(pts[-1].fX, pts[-1].fY);
1546 break;
1547 case kQuad_Verb:
1548 this->quadTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY);
1549 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001550 case kConic_Verb:
1551 this->conicTo(pts[-1], pts[-2], *--conicWeights);
1552 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001553 case kCubic_Verb:
1554 this->cubicTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY,
1555 pts[-3].fX, pts[-3].fY);
1556 break;
1557 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001558 SkDEBUGFAIL("bad verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001559 break;
1560 }
reed@google.com277c3f82013-05-31 15:17:50 +00001561 pts -= pts_in_verb(verbs[~i]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001562 }
1563}
1564
reed@google.com63d73742012-01-10 15:33:12 +00001565void SkPath::reverseAddPath(const SkPath& src) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001566 SkPathRef::Editor ed(&fPathRef, src.fPathRef->countPoints(), src.fPathRef->countVerbs());
reed@google.com63d73742012-01-10 15:33:12 +00001567
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001568 const SkPoint* pts = src.fPathRef->pointsEnd();
1569 // we will iterator through src's verbs backwards
1570 const uint8_t* verbs = src.fPathRef->verbsMemBegin(); // points at the last verb
1571 const uint8_t* verbsEnd = src.fPathRef->verbs(); // points just past the first verb
reed@google.com277c3f82013-05-31 15:17:50 +00001572 const SkScalar* conicWeights = src.fPathRef->conicWeightsEnd();
reed@google.com63d73742012-01-10 15:33:12 +00001573
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001574 fIsOval = false;
1575
reed@google.com63d73742012-01-10 15:33:12 +00001576 bool needMove = true;
1577 bool needClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001578 while (verbs < verbsEnd) {
1579 uint8_t v = *(verbs++);
reed@google.com277c3f82013-05-31 15:17:50 +00001580 int n = pts_in_verb(v);
reed@google.com63d73742012-01-10 15:33:12 +00001581
1582 if (needMove) {
1583 --pts;
1584 this->moveTo(pts->fX, pts->fY);
1585 needMove = false;
1586 }
1587 pts -= n;
1588 switch (v) {
1589 case kMove_Verb:
1590 if (needClose) {
1591 this->close();
1592 needClose = false;
1593 }
1594 needMove = true;
1595 pts += 1; // so we see the point in "if (needMove)" above
1596 break;
1597 case kLine_Verb:
1598 this->lineTo(pts[0]);
1599 break;
1600 case kQuad_Verb:
1601 this->quadTo(pts[1], pts[0]);
1602 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001603 case kConic_Verb:
1604 this->conicTo(pts[1], pts[0], *--conicWeights);
1605 break;
reed@google.com63d73742012-01-10 15:33:12 +00001606 case kCubic_Verb:
1607 this->cubicTo(pts[2], pts[1], pts[0]);
1608 break;
1609 case kClose_Verb:
1610 needClose = true;
1611 break;
1612 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00001613 SkDEBUGFAIL("unexpected verb");
reed@google.com63d73742012-01-10 15:33:12 +00001614 }
1615 }
1616}
1617
reed@android.com8a1c16f2008-12-17 15:59:43 +00001618///////////////////////////////////////////////////////////////////////////////
1619
1620void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
1621 SkMatrix matrix;
1622
1623 matrix.setTranslate(dx, dy);
1624 this->transform(matrix, dst);
1625}
1626
1627#include "SkGeometry.h"
1628
1629static void subdivide_quad_to(SkPath* path, const SkPoint pts[3],
1630 int level = 2) {
1631 if (--level >= 0) {
1632 SkPoint tmp[5];
1633
1634 SkChopQuadAtHalf(pts, tmp);
1635 subdivide_quad_to(path, &tmp[0], level);
1636 subdivide_quad_to(path, &tmp[2], level);
1637 } else {
1638 path->quadTo(pts[1], pts[2]);
1639 }
1640}
1641
1642static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
1643 int level = 2) {
1644 if (--level >= 0) {
1645 SkPoint tmp[7];
1646
1647 SkChopCubicAtHalf(pts, tmp);
1648 subdivide_cubic_to(path, &tmp[0], level);
1649 subdivide_cubic_to(path, &tmp[3], level);
1650 } else {
1651 path->cubicTo(pts[1], pts[2], pts[3]);
1652 }
1653}
1654
1655void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
1656 SkDEBUGCODE(this->validate();)
1657 if (dst == NULL) {
1658 dst = (SkPath*)this;
1659 }
1660
tomhudson@google.com8d430182011-06-06 19:11:19 +00001661 if (matrix.hasPerspective()) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001662 SkPath tmp;
1663 tmp.fFillType = fFillType;
1664
1665 SkPath::Iter iter(*this, false);
1666 SkPoint pts[4];
1667 SkPath::Verb verb;
1668
reed@google.com4a3b7142012-05-16 17:16:46 +00001669 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001670 switch (verb) {
1671 case kMove_Verb:
1672 tmp.moveTo(pts[0]);
1673 break;
1674 case kLine_Verb:
1675 tmp.lineTo(pts[1]);
1676 break;
1677 case kQuad_Verb:
1678 subdivide_quad_to(&tmp, pts);
1679 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001680 case kConic_Verb:
mtklein@google.com330313a2013-08-22 15:37:26 +00001681 SkDEBUGFAIL("TODO: compute new weight");
reed@google.com277c3f82013-05-31 15:17:50 +00001682 tmp.conicTo(pts[1], pts[2], iter.conicWeight());
1683 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001684 case kCubic_Verb:
1685 subdivide_cubic_to(&tmp, pts);
1686 break;
1687 case kClose_Verb:
1688 tmp.close();
1689 break;
1690 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001691 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001692 break;
1693 }
1694 }
1695
1696 dst->swap(tmp);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001697 SkPathRef::Editor ed(&dst->fPathRef);
1698 matrix.mapPoints(ed.points(), ed.pathRef()->countPoints());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001699 dst->fDirection = kUnknown_Direction;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001700 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001701 SkPathRef::CreateTransformedCopy(&dst->fPathRef, *fPathRef.get(), matrix);
1702
reed@android.com8a1c16f2008-12-17 15:59:43 +00001703 if (this != dst) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001704 dst->fFillType = fFillType;
reed@google.comb3b0a5b2011-09-21 12:58:51 +00001705 dst->fSegmentMask = fSegmentMask;
reed@google.com2a6f8ab2011-10-25 18:41:23 +00001706 dst->fConvexity = fConvexity;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001707 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001708
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001709 if (kUnknown_Direction == fDirection) {
1710 dst->fDirection = kUnknown_Direction;
1711 } else {
1712 SkScalar det2x2 =
1713 SkScalarMul(matrix.get(SkMatrix::kMScaleX), matrix.get(SkMatrix::kMScaleY)) -
1714 SkScalarMul(matrix.get(SkMatrix::kMSkewX), matrix.get(SkMatrix::kMSkewY));
1715 if (det2x2 < 0) {
1716 dst->fDirection = SkPath::OppositeDirection(static_cast<Direction>(fDirection));
1717 } else if (det2x2 > 0) {
1718 dst->fDirection = fDirection;
1719 } else {
1720 dst->fDirection = kUnknown_Direction;
1721 }
1722 }
1723
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001724 // It's an oval only if it stays a rect.
1725 dst->fIsOval = fIsOval && matrix.rectStaysRect();
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001726
reed@android.com8a1c16f2008-12-17 15:59:43 +00001727 SkDEBUGCODE(dst->validate();)
1728 }
1729}
1730
reed@android.com8a1c16f2008-12-17 15:59:43 +00001731///////////////////////////////////////////////////////////////////////////////
1732///////////////////////////////////////////////////////////////////////////////
1733
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001734enum SegmentState {
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001735 kEmptyContour_SegmentState, // The current contour is empty. We may be
1736 // starting processing or we may have just
1737 // closed a contour.
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001738 kAfterMove_SegmentState, // We have seen a move, but nothing else.
1739 kAfterPrimitive_SegmentState // We have seen a primitive but not yet
1740 // closed the path. Also the initial state.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001741};
1742
1743SkPath::Iter::Iter() {
1744#ifdef SK_DEBUG
1745 fPts = NULL;
reed@google.com277c3f82013-05-31 15:17:50 +00001746 fConicWeights = NULL;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001747 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001748 fForceClose = fCloseLine = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001749 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001750#endif
1751 // need to init enough to make next() harmlessly return kDone_Verb
1752 fVerbs = NULL;
1753 fVerbStop = NULL;
1754 fNeedClose = false;
1755}
1756
1757SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
1758 this->setPath(path, forceClose);
1759}
1760
1761void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001762 fPts = path.fPathRef->points();
1763 fVerbs = path.fPathRef->verbs();
1764 fVerbStop = path.fPathRef->verbsMemBegin();
reed@google.com277c3f82013-05-31 15:17:50 +00001765 fConicWeights = path.fPathRef->conicWeights() - 1; // begin one behind
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001766 fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org72785c42011-12-29 21:03:28 +00001767 fMoveTo.fX = fMoveTo.fY = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001768 fForceClose = SkToU8(forceClose);
1769 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001770 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001771}
1772
1773bool SkPath::Iter::isClosedContour() const {
1774 if (fVerbs == NULL || fVerbs == fVerbStop) {
1775 return false;
1776 }
1777 if (fForceClose) {
1778 return true;
1779 }
1780
1781 const uint8_t* verbs = fVerbs;
1782 const uint8_t* stop = fVerbStop;
reed@google.comabf15c12011-01-18 20:35:51 +00001783
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001784 if (kMove_Verb == *(verbs - 1)) {
1785 verbs -= 1; // skip the initial moveto
reed@android.com8a1c16f2008-12-17 15:59:43 +00001786 }
1787
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001788 while (verbs > stop) {
1789 // verbs points one beyond the current verb, decrement first.
1790 unsigned v = *(--verbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001791 if (kMove_Verb == v) {
1792 break;
1793 }
1794 if (kClose_Verb == v) {
1795 return true;
1796 }
1797 }
1798 return false;
1799}
1800
1801SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001802 SkASSERT(pts);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001803 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001804 // A special case: if both points are NaN, SkPoint::operation== returns
1805 // false, but the iterator expects that they are treated as the same.
1806 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001807 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1808 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001809 return kClose_Verb;
1810 }
1811
reed@google.com9e25dbf2012-05-15 17:05:38 +00001812 pts[0] = fLastPt;
1813 pts[1] = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001814 fLastPt = fMoveTo;
1815 fCloseLine = true;
1816 return kLine_Verb;
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +00001817 } else {
1818 pts[0] = fMoveTo;
1819 return kClose_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001820 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001821}
1822
reed@google.com9e25dbf2012-05-15 17:05:38 +00001823const SkPoint& SkPath::Iter::cons_moveTo() {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001824 if (fSegmentState == kAfterMove_SegmentState) {
1825 // Set the first return pt to the move pt
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001826 fSegmentState = kAfterPrimitive_SegmentState;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001827 return fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001828 } else {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001829 SkASSERT(fSegmentState == kAfterPrimitive_SegmentState);
1830 // Set the first return pt to the last pt of the previous primitive.
reed@google.com9e25dbf2012-05-15 17:05:38 +00001831 return fPts[-1];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001832 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001833}
1834
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001835void SkPath::Iter::consumeDegenerateSegments() {
1836 // We need to step over anything that will not move the current draw point
1837 // forward before the next move is seen
1838 const uint8_t* lastMoveVerb = 0;
1839 const SkPoint* lastMovePt = 0;
1840 SkPoint lastPt = fLastPt;
1841 while (fVerbs != fVerbStop) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001842 unsigned verb = *(fVerbs - 1); // fVerbs is one beyond the current verb
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001843 switch (verb) {
1844 case kMove_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001845 // Keep a record of this most recent move
1846 lastMoveVerb = fVerbs;
1847 lastMovePt = fPts;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001848 lastPt = fPts[0];
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001849 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001850 fPts++;
1851 break;
1852
1853 case kClose_Verb:
schenney@chromium.org7e963602012-06-13 17:05:43 +00001854 // A close when we are in a segment is always valid except when it
1855 // follows a move which follows a segment.
1856 if (fSegmentState == kAfterPrimitive_SegmentState && !lastMoveVerb) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001857 return;
1858 }
1859 // A close at any other time must be ignored
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001860 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001861 break;
1862
1863 case kLine_Verb:
1864 if (!IsLineDegenerate(lastPt, fPts[0])) {
1865 if (lastMoveVerb) {
1866 fVerbs = lastMoveVerb;
1867 fPts = lastMovePt;
1868 return;
1869 }
1870 return;
1871 }
1872 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001873 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001874 fPts++;
1875 break;
1876
reed@google.com277c3f82013-05-31 15:17:50 +00001877 case kConic_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001878 case kQuad_Verb:
1879 if (!IsQuadDegenerate(lastPt, fPts[0], fPts[1])) {
1880 if (lastMoveVerb) {
1881 fVerbs = lastMoveVerb;
1882 fPts = lastMovePt;
1883 return;
1884 }
1885 return;
1886 }
1887 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001888 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001889 fPts += 2;
reed@google.com277c3f82013-05-31 15:17:50 +00001890 fConicWeights += (kConic_Verb == verb);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001891 break;
1892
1893 case kCubic_Verb:
1894 if (!IsCubicDegenerate(lastPt, fPts[0], fPts[1], fPts[2])) {
1895 if (lastMoveVerb) {
1896 fVerbs = lastMoveVerb;
1897 fPts = lastMovePt;
1898 return;
1899 }
1900 return;
1901 }
1902 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001903 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001904 fPts += 3;
1905 break;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001906
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001907 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001908 SkDEBUGFAIL("Should never see kDone_Verb");
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001909 }
1910 }
1911}
1912
reed@google.com4a3b7142012-05-16 17:16:46 +00001913SkPath::Verb SkPath::Iter::doNext(SkPoint ptsParam[4]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001914 SkASSERT(ptsParam);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001915
reed@android.com8a1c16f2008-12-17 15:59:43 +00001916 if (fVerbs == fVerbStop) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001917 // Close the curve if requested and if there is some curve to close
1918 if (fNeedClose && fSegmentState == kAfterPrimitive_SegmentState) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001919 if (kLine_Verb == this->autoClose(ptsParam)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001920 return kLine_Verb;
1921 }
1922 fNeedClose = false;
1923 return kClose_Verb;
1924 }
1925 return kDone_Verb;
1926 }
1927
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001928 // fVerbs is one beyond the current verb, decrement first
1929 unsigned verb = *(--fVerbs);
reed@google.com9e25dbf2012-05-15 17:05:38 +00001930 const SkPoint* SK_RESTRICT srcPts = fPts;
1931 SkPoint* SK_RESTRICT pts = ptsParam;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001932
1933 switch (verb) {
1934 case kMove_Verb:
1935 if (fNeedClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001936 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001937 verb = this->autoClose(pts);
1938 if (verb == kClose_Verb) {
1939 fNeedClose = false;
1940 }
1941 return (Verb)verb;
1942 }
1943 if (fVerbs == fVerbStop) { // might be a trailing moveto
1944 return kDone_Verb;
1945 }
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001946 fMoveTo = *srcPts;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001947 pts[0] = *srcPts;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001948 srcPts += 1;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001949 fSegmentState = kAfterMove_SegmentState;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001950 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001951 fNeedClose = fForceClose;
1952 break;
1953 case kLine_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001954 pts[0] = this->cons_moveTo();
1955 pts[1] = srcPts[0];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001956 fLastPt = srcPts[0];
1957 fCloseLine = false;
1958 srcPts += 1;
1959 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001960 case kConic_Verb:
1961 fConicWeights += 1;
1962 // fall-through
reed@android.com8a1c16f2008-12-17 15:59:43 +00001963 case kQuad_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001964 pts[0] = this->cons_moveTo();
1965 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001966 fLastPt = srcPts[1];
1967 srcPts += 2;
1968 break;
1969 case kCubic_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001970 pts[0] = this->cons_moveTo();
1971 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001972 fLastPt = srcPts[2];
1973 srcPts += 3;
1974 break;
1975 case kClose_Verb:
1976 verb = this->autoClose(pts);
1977 if (verb == kLine_Verb) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001978 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001979 } else {
1980 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001981 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001982 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001983 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001984 break;
1985 }
1986 fPts = srcPts;
1987 return (Verb)verb;
1988}
1989
1990///////////////////////////////////////////////////////////////////////////////
1991
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001992SkPath::RawIter::RawIter() {
1993#ifdef SK_DEBUG
1994 fPts = NULL;
reed@google.com277c3f82013-05-31 15:17:50 +00001995 fConicWeights = NULL;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001996 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
1997#endif
1998 // need to init enough to make next() harmlessly return kDone_Verb
1999 fVerbs = NULL;
2000 fVerbStop = NULL;
2001}
2002
2003SkPath::RawIter::RawIter(const SkPath& path) {
2004 this->setPath(path);
2005}
2006
2007void SkPath::RawIter::setPath(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002008 fPts = path.fPathRef->points();
2009 fVerbs = path.fPathRef->verbs();
2010 fVerbStop = path.fPathRef->verbsMemBegin();
reed@google.com277c3f82013-05-31 15:17:50 +00002011 fConicWeights = path.fPathRef->conicWeights() - 1; // begin one behind
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002012 fMoveTo.fX = fMoveTo.fY = 0;
2013 fLastPt.fX = fLastPt.fY = 0;
2014}
2015
2016SkPath::Verb SkPath::RawIter::next(SkPoint pts[4]) {
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002017 SkASSERT(NULL != pts);
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002018 if (fVerbs == fVerbStop) {
2019 return kDone_Verb;
2020 }
2021
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002022 // fVerbs points one beyond next verb so decrement first.
2023 unsigned verb = *(--fVerbs);
2024 const SkPoint* srcPts = fPts;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002025
2026 switch (verb) {
2027 case kMove_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002028 pts[0] = *srcPts;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002029 fMoveTo = srcPts[0];
2030 fLastPt = fMoveTo;
2031 srcPts += 1;
2032 break;
2033 case kLine_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002034 pts[0] = fLastPt;
2035 pts[1] = srcPts[0];
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002036 fLastPt = srcPts[0];
2037 srcPts += 1;
2038 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002039 case kConic_Verb:
2040 fConicWeights += 1;
2041 // fall-through
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002042 case kQuad_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002043 pts[0] = fLastPt;
2044 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002045 fLastPt = srcPts[1];
2046 srcPts += 2;
2047 break;
2048 case kCubic_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002049 pts[0] = fLastPt;
2050 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002051 fLastPt = srcPts[2];
2052 srcPts += 3;
2053 break;
2054 case kClose_Verb:
2055 fLastPt = fMoveTo;
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002056 pts[0] = fMoveTo;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002057 break;
2058 }
2059 fPts = srcPts;
2060 return (Verb)verb;
2061}
2062
2063///////////////////////////////////////////////////////////////////////////////
2064
reed@android.com8a1c16f2008-12-17 15:59:43 +00002065/*
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002066 Format in compressed buffer: [ptCount, verbCount, pts[], verbs[]]
reed@android.com8a1c16f2008-12-17 15:59:43 +00002067*/
2068
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002069uint32_t SkPath::writeToMemory(void* storage) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002070 SkDEBUGCODE(this->validate();)
2071
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002072 if (NULL == storage) {
robertphillips@google.comca0c8382013-09-26 12:18:23 +00002073 const int byteCount = sizeof(int32_t) + fPathRef->writeSize();
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002074 return SkAlign4(byteCount);
2075 }
2076
2077 SkWBuffer buffer(storage);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002078
robertphillips@google.comca0c8382013-09-26 12:18:23 +00002079 int32_t packed = ((fIsOval & 1) << kIsOval_SerializationShift) |
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002080 (fConvexity << kConvexity_SerializationShift) |
2081 (fFillType << kFillType_SerializationShift) |
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002082 (fSegmentMask << kSegmentMask_SerializationShift) |
robertphillips@google.comca0c8382013-09-26 12:18:23 +00002083 (fDirection << kDirection_SerializationShift)
2084#ifndef DELETE_THIS_CODE_WHEN_SKPS_ARE_REBUILT_AT_V14_AND_ALL_OTHER_INSTANCES_TOO
2085 | (0x1 << kNewFormat_SerializationShift);
2086#endif
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002087
robertphillips@google.com2972bb52012-08-07 17:32:51 +00002088 buffer.write32(packed);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002089
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002090 fPathRef->writeToBuffer(&buffer);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002091
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002092 buffer.padToAlign4();
scroggo@google.com614f9e32013-05-09 18:05:32 +00002093 return SkToU32(buffer.pos());
reed@android.com8a1c16f2008-12-17 15:59:43 +00002094}
2095
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002096uint32_t SkPath::readFromMemory(const void* storage) {
2097 SkRBuffer buffer(storage);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002098
reed@google.com98b11f12011-09-21 18:40:27 +00002099 uint32_t packed = buffer.readS32();
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002100 fIsOval = (packed >> kIsOval_SerializationShift) & 1;
2101 fConvexity = (packed >> kConvexity_SerializationShift) & 0xFF;
2102 fFillType = (packed >> kFillType_SerializationShift) & 0xFF;
reed@google.com277c3f82013-05-31 15:17:50 +00002103 fSegmentMask = (packed >> kSegmentMask_SerializationShift) & 0xF;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002104 fDirection = (packed >> kDirection_SerializationShift) & 0x3;
robertphillips@google.comca0c8382013-09-26 12:18:23 +00002105#ifndef DELETE_THIS_CODE_WHEN_SKPS_ARE_REBUILT_AT_V14_AND_ALL_OTHER_INSTANCES_TOO
2106 bool newFormat = (packed >> kNewFormat_SerializationShift) & 1;
2107#endif
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002108
robertphillips@google.comca0c8382013-09-26 12:18:23 +00002109 fPathRef.reset(SkPathRef::CreateFromBuffer(&buffer
2110#ifndef DELETE_THIS_CODE_WHEN_SKPS_ARE_REBUILT_AT_V14_AND_ALL_OTHER_INSTANCES_TOO
2111 , newFormat, packed)
2112#endif
2113 );
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002114
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002115 buffer.skipToAlign4();
reed@google.comabf15c12011-01-18 20:35:51 +00002116
reed@android.com8a1c16f2008-12-17 15:59:43 +00002117 SkDEBUGCODE(this->validate();)
scroggo@google.com614f9e32013-05-09 18:05:32 +00002118 return SkToU32(buffer.pos());
reed@android.com8a1c16f2008-12-17 15:59:43 +00002119}
2120
2121///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00002122
reed@google.com51bbe752013-01-17 22:07:50 +00002123#include "SkString.h"
2124
2125static void append_scalar(SkString* str, SkScalar value) {
2126 SkString tmp;
2127 tmp.printf("%g", value);
2128 if (tmp.contains('.')) {
2129 tmp.appendUnichar('f');
2130 }
2131 str->append(tmp);
2132}
2133
2134static void append_params(SkString* str, const char label[], const SkPoint pts[],
reed@google.com277c3f82013-05-31 15:17:50 +00002135 int count, SkScalar conicWeight = -1) {
reed@google.com51bbe752013-01-17 22:07:50 +00002136 str->append(label);
2137 str->append("(");
skia.committer@gmail.com15dd3002013-01-18 07:07:28 +00002138
reed@google.com51bbe752013-01-17 22:07:50 +00002139 const SkScalar* values = &pts[0].fX;
2140 count *= 2;
2141
2142 for (int i = 0; i < count; ++i) {
2143 append_scalar(str, values[i]);
2144 if (i < count - 1) {
2145 str->append(", ");
2146 }
2147 }
reed@google.com277c3f82013-05-31 15:17:50 +00002148 if (conicWeight >= 0) {
2149 str->append(", ");
2150 append_scalar(str, conicWeight);
2151 }
reed@google.com51bbe752013-01-17 22:07:50 +00002152 str->append(");\n");
2153}
2154
reed@android.com8a1c16f2008-12-17 15:59:43 +00002155void SkPath::dump(bool forceClose, const char title[]) const {
2156 Iter iter(*this, forceClose);
2157 SkPoint pts[4];
2158 Verb verb;
2159
2160 SkDebugf("path: forceClose=%s %s\n", forceClose ? "true" : "false",
2161 title ? title : "");
2162
reed@google.com51bbe752013-01-17 22:07:50 +00002163 SkString builder;
2164
reed@google.com4a3b7142012-05-16 17:16:46 +00002165 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002166 switch (verb) {
2167 case kMove_Verb:
reed@google.com51bbe752013-01-17 22:07:50 +00002168 append_params(&builder, "path.moveTo", &pts[0], 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002169 break;
2170 case kLine_Verb:
reed@google.com51bbe752013-01-17 22:07:50 +00002171 append_params(&builder, "path.lineTo", &pts[1], 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002172 break;
2173 case kQuad_Verb:
reed@google.com51bbe752013-01-17 22:07:50 +00002174 append_params(&builder, "path.quadTo", &pts[1], 2);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002175 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002176 case kConic_Verb:
2177 append_params(&builder, "path.conicTo", &pts[1], 2, iter.conicWeight());
2178 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002179 case kCubic_Verb:
reed@google.com51bbe752013-01-17 22:07:50 +00002180 append_params(&builder, "path.cubicTo", &pts[1], 3);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002181 break;
2182 case kClose_Verb:
reed@google.comf919b722013-01-18 17:53:36 +00002183 builder.append("path.close();\n");
reed@android.com8a1c16f2008-12-17 15:59:43 +00002184 break;
2185 default:
2186 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
2187 verb = kDone_Verb; // stop the loop
2188 break;
2189 }
2190 }
reed@google.com51bbe752013-01-17 22:07:50 +00002191 SkDebugf("%s\n", builder.c_str());
reed@android.com8a1c16f2008-12-17 15:59:43 +00002192}
2193
reed@android.come522ca52009-11-23 20:10:41 +00002194void SkPath::dump() const {
2195 this->dump(false);
2196}
2197
2198#ifdef SK_DEBUG
2199void SkPath::validate() const {
2200 SkASSERT(this != NULL);
2201 SkASSERT((fFillType & ~3) == 0);
reed@google.comabf15c12011-01-18 20:35:51 +00002202
djsollen@google.com077348c2012-10-22 20:23:32 +00002203#ifdef SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002204 if (!fBoundsIsDirty) {
2205 SkRect bounds;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002206
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002207 bool isFinite = compute_pt_bounds(&bounds, *fPathRef.get());
robertphillips@google.com5d8d1862012-08-15 14:36:41 +00002208 SkASSERT(SkToBool(fIsFinite) == isFinite);
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002209
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002210 if (fPathRef->countPoints() <= 1) {
reed@android.come522ca52009-11-23 20:10:41 +00002211 // if we're empty, fBounds may be empty but translated, so we can't
2212 // necessarily compare to bounds directly
2213 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
2214 // be [2, 2, 2, 2]
2215 SkASSERT(bounds.isEmpty());
2216 SkASSERT(fBounds.isEmpty());
2217 } else {
reed@google.comeac52bd2011-11-14 18:13:59 +00002218 if (bounds.isEmpty()) {
2219 SkASSERT(fBounds.isEmpty());
2220 } else {
reed@google.com3563c9e2011-11-14 19:34:57 +00002221 if (!fBounds.isEmpty()) {
2222 SkASSERT(fBounds.contains(bounds));
2223 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002224 }
reed@android.come522ca52009-11-23 20:10:41 +00002225 }
2226 }
reed@google.com10296cc2011-09-21 12:29:05 +00002227
2228 uint32_t mask = 0;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002229 const uint8_t* verbs = const_cast<const SkPathRef*>(fPathRef.get())->verbs();
2230 for (int i = 0; i < fPathRef->countVerbs(); i++) {
2231 switch (verbs[~i]) {
reed@google.com10296cc2011-09-21 12:29:05 +00002232 case kLine_Verb:
2233 mask |= kLine_SegmentMask;
2234 break;
2235 case kQuad_Verb:
2236 mask |= kQuad_SegmentMask;
2237 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002238 case kConic_Verb:
2239 mask |= kConic_SegmentMask;
2240 break;
reed@google.com10296cc2011-09-21 12:29:05 +00002241 case kCubic_Verb:
2242 mask |= kCubic_SegmentMask;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002243 case kMove_Verb: // these verbs aren't included in the segment mask.
2244 case kClose_Verb:
2245 break;
2246 case kDone_Verb:
2247 SkDEBUGFAIL("Done verb shouldn't be recorded.");
2248 break;
2249 default:
2250 SkDEBUGFAIL("Unknown Verb");
2251 break;
reed@google.com10296cc2011-09-21 12:29:05 +00002252 }
2253 }
2254 SkASSERT(mask == fSegmentMask);
djsollen@google.com077348c2012-10-22 20:23:32 +00002255#endif // SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002256}
djsollen@google.com077348c2012-10-22 20:23:32 +00002257#endif // SK_DEBUG
reed@android.come522ca52009-11-23 20:10:41 +00002258
reed@google.com04863fa2011-05-15 04:08:24 +00002259///////////////////////////////////////////////////////////////////////////////
2260
reed@google.com0b7b9822011-05-16 12:29:27 +00002261static int sign(SkScalar x) { return x < 0; }
2262#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00002263
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002264static bool AlmostEqual(SkScalar compA, SkScalar compB) {
2265 // The error epsilon was empirically derived; worse case round rects
2266 // with a mid point outset by 2x float epsilon in tests had an error
2267 // of 12.
2268 const int epsilon = 16;
2269 if (!SkScalarIsFinite(compA) || !SkScalarIsFinite(compB)) {
2270 return false;
2271 }
2272 if (sk_float_abs(compA) <= FLT_EPSILON && sk_float_abs(compB) <= FLT_EPSILON) {
2273 return true;
2274 }
2275 int aBits = SkFloatAs2sCompliment(compA);
2276 int bBits = SkFloatAs2sCompliment(compB);
2277 return aBits < bBits + epsilon && bBits < aBits + epsilon;
reed@google.com04863fa2011-05-15 04:08:24 +00002278}
2279
2280// only valid for a single contour
2281struct Convexicator {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002282 Convexicator()
2283 : fPtCount(0)
2284 , fConvexity(SkPath::kConvex_Convexity)
2285 , fDirection(SkPath::kUnknown_Direction) {
reed@google.com04863fa2011-05-15 04:08:24 +00002286 fSign = 0;
2287 // warnings
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002288 fLastPt.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002289 fCurrPt.set(0, 0);
2290 fVec0.set(0, 0);
2291 fVec1.set(0, 0);
2292 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00002293
2294 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00002295 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00002296 }
2297
2298 SkPath::Convexity getConvexity() const { return fConvexity; }
2299
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002300 /** The direction returned is only valid if the path is determined convex */
2301 SkPath::Direction getDirection() const { return fDirection; }
2302
reed@google.com04863fa2011-05-15 04:08:24 +00002303 void addPt(const SkPoint& pt) {
2304 if (SkPath::kConcave_Convexity == fConvexity) {
2305 return;
2306 }
2307
2308 if (0 == fPtCount) {
2309 fCurrPt = pt;
2310 ++fPtCount;
2311 } else {
2312 SkVector vec = pt - fCurrPt;
2313 if (vec.fX || vec.fY) {
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002314 fLastPt = fCurrPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002315 fCurrPt = pt;
2316 if (++fPtCount == 2) {
2317 fFirstVec = fVec1 = vec;
2318 } else {
2319 SkASSERT(fPtCount > 2);
2320 this->addVec(vec);
2321 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002322
reed@google.com85b6e392011-05-15 20:25:17 +00002323 int sx = sign(vec.fX);
2324 int sy = sign(vec.fY);
2325 fDx += (sx != fSx);
2326 fDy += (sy != fSy);
2327 fSx = sx;
2328 fSy = sy;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002329
reed@google.com85b6e392011-05-15 20:25:17 +00002330 if (fDx > 3 || fDy > 3) {
2331 fConvexity = SkPath::kConcave_Convexity;
2332 }
reed@google.com04863fa2011-05-15 04:08:24 +00002333 }
2334 }
2335 }
2336
2337 void close() {
2338 if (fPtCount > 2) {
2339 this->addVec(fFirstVec);
2340 }
2341 }
2342
2343private:
2344 void addVec(const SkVector& vec) {
2345 SkASSERT(vec.fX || vec.fY);
2346 fVec0 = fVec1;
2347 fVec1 = vec;
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002348 SkScalar cross = SkPoint::CrossProduct(fVec0, fVec1);
2349 SkScalar smallest = SkTMin(fCurrPt.fX, SkTMin(fCurrPt.fY, SkTMin(fLastPt.fX, fLastPt.fY)));
2350 SkScalar largest = SkTMax(fCurrPt.fX, SkTMax(fCurrPt.fY, SkTMax(fLastPt.fX, fLastPt.fY)));
2351 largest = SkTMax(largest, -smallest);
2352 int sign = AlmostEqual(largest, largest + cross) ? 0 : SkScalarSignAsInt(cross);
reed@google.com04863fa2011-05-15 04:08:24 +00002353 if (0 == fSign) {
2354 fSign = sign;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002355 if (1 == sign) {
2356 fDirection = SkPath::kCW_Direction;
2357 } else if (-1 == sign) {
2358 fDirection = SkPath::kCCW_Direction;
2359 }
reed@google.com04863fa2011-05-15 04:08:24 +00002360 } else if (sign) {
reed@google.comb54455e2011-05-16 14:16:04 +00002361 if (fSign != sign) {
reed@google.com04863fa2011-05-15 04:08:24 +00002362 fConvexity = SkPath::kConcave_Convexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002363 fDirection = SkPath::kUnknown_Direction;
reed@google.com04863fa2011-05-15 04:08:24 +00002364 }
2365 }
2366 }
2367
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002368 SkPoint fLastPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002369 SkPoint fCurrPt;
2370 SkVector fVec0, fVec1, fFirstVec;
2371 int fPtCount; // non-degenerate points
2372 int fSign;
2373 SkPath::Convexity fConvexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002374 SkPath::Direction fDirection;
reed@google.com0b7b9822011-05-16 12:29:27 +00002375 int fDx, fDy, fSx, fSy;
reed@google.com04863fa2011-05-15 04:08:24 +00002376};
2377
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002378SkPath::Convexity SkPath::internalGetConvexity() const {
2379 SkASSERT(kUnknown_Convexity == fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002380 SkPoint pts[4];
2381 SkPath::Verb verb;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002382 SkPath::Iter iter(*this, true);
reed@google.com04863fa2011-05-15 04:08:24 +00002383
2384 int contourCount = 0;
2385 int count;
2386 Convexicator state;
2387
2388 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
2389 switch (verb) {
2390 case kMove_Verb:
2391 if (++contourCount > 1) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002392 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002393 return kConcave_Convexity;
2394 }
2395 pts[1] = pts[0];
2396 count = 1;
2397 break;
2398 case kLine_Verb: count = 1; break;
2399 case kQuad_Verb: count = 2; break;
reed@google.com277c3f82013-05-31 15:17:50 +00002400 case kConic_Verb: count = 2; break;
reed@google.com04863fa2011-05-15 04:08:24 +00002401 case kCubic_Verb: count = 3; break;
2402 case kClose_Verb:
2403 state.close();
2404 count = 0;
2405 break;
2406 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00002407 SkDEBUGFAIL("bad verb");
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002408 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002409 return kConcave_Convexity;
2410 }
2411
2412 for (int i = 1; i <= count; i++) {
2413 state.addPt(pts[i]);
2414 }
2415 // early exit
2416 if (kConcave_Convexity == state.getConvexity()) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002417 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002418 return kConcave_Convexity;
2419 }
2420 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002421 fConvexity = state.getConvexity();
2422 if (kConvex_Convexity == fConvexity && kUnknown_Direction == fDirection) {
2423 fDirection = state.getDirection();
2424 }
2425 return static_cast<Convexity>(fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002426}
reed@google.com69a99432012-01-10 18:00:10 +00002427
2428///////////////////////////////////////////////////////////////////////////////
2429
2430class ContourIter {
2431public:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002432 ContourIter(const SkPathRef& pathRef);
reed@google.com69a99432012-01-10 18:00:10 +00002433
2434 bool done() const { return fDone; }
2435 // if !done() then these may be called
2436 int count() const { return fCurrPtCount; }
2437 const SkPoint* pts() const { return fCurrPt; }
2438 void next();
2439
2440private:
2441 int fCurrPtCount;
2442 const SkPoint* fCurrPt;
2443 const uint8_t* fCurrVerb;
2444 const uint8_t* fStopVerbs;
reed@google.com277c3f82013-05-31 15:17:50 +00002445 const SkScalar* fCurrConicWeight;
reed@google.com69a99432012-01-10 18:00:10 +00002446 bool fDone;
reed@google.comd1ab9322012-01-10 18:40:03 +00002447 SkDEBUGCODE(int fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002448};
2449
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002450ContourIter::ContourIter(const SkPathRef& pathRef) {
2451 fStopVerbs = pathRef.verbsMemBegin();
reed@google.com69a99432012-01-10 18:00:10 +00002452 fDone = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002453 fCurrPt = pathRef.points();
2454 fCurrVerb = pathRef.verbs();
reed@google.com277c3f82013-05-31 15:17:50 +00002455 fCurrConicWeight = pathRef.conicWeights();
reed@google.com69a99432012-01-10 18:00:10 +00002456 fCurrPtCount = 0;
reed@google.comd1ab9322012-01-10 18:40:03 +00002457 SkDEBUGCODE(fContourCounter = 0;)
reed@google.com69a99432012-01-10 18:00:10 +00002458 this->next();
2459}
2460
2461void ContourIter::next() {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002462 if (fCurrVerb <= fStopVerbs) {
reed@google.com69a99432012-01-10 18:00:10 +00002463 fDone = true;
2464 }
2465 if (fDone) {
2466 return;
2467 }
2468
2469 // skip pts of prev contour
2470 fCurrPt += fCurrPtCount;
2471
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002472 SkASSERT(SkPath::kMove_Verb == fCurrVerb[~0]);
reed@google.com69a99432012-01-10 18:00:10 +00002473 int ptCount = 1; // moveTo
2474 const uint8_t* verbs = fCurrVerb;
2475
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002476 for (--verbs; verbs > fStopVerbs; --verbs) {
2477 switch (verbs[~0]) {
reed@google.com69a99432012-01-10 18:00:10 +00002478 case SkPath::kMove_Verb:
reed@google.com69a99432012-01-10 18:00:10 +00002479 goto CONTOUR_END;
2480 case SkPath::kLine_Verb:
2481 ptCount += 1;
2482 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002483 case SkPath::kConic_Verb:
2484 fCurrConicWeight += 1;
2485 // fall-through
reed@google.com69a99432012-01-10 18:00:10 +00002486 case SkPath::kQuad_Verb:
2487 ptCount += 2;
2488 break;
2489 case SkPath::kCubic_Verb:
2490 ptCount += 3;
2491 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002492 case SkPath::kClose_Verb:
2493 break;
2494 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00002495 SkDEBUGFAIL("unexpected verb");
reed@google.com69a99432012-01-10 18:00:10 +00002496 break;
2497 }
2498 }
2499CONTOUR_END:
2500 fCurrPtCount = ptCount;
2501 fCurrVerb = verbs;
reed@google.comd1ab9322012-01-10 18:40:03 +00002502 SkDEBUGCODE(++fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002503}
2504
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002505// returns cross product of (p1 - p0) and (p2 - p0)
reed@google.com69a99432012-01-10 18:00:10 +00002506static SkScalar cross_prod(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002507 SkScalar cross = SkPoint::CrossProduct(p1 - p0, p2 - p0);
2508 // We may get 0 when the above subtracts underflow. We expect this to be
2509 // very rare and lazily promote to double.
2510 if (0 == cross) {
2511 double p0x = SkScalarToDouble(p0.fX);
2512 double p0y = SkScalarToDouble(p0.fY);
2513
2514 double p1x = SkScalarToDouble(p1.fX);
2515 double p1y = SkScalarToDouble(p1.fY);
2516
2517 double p2x = SkScalarToDouble(p2.fX);
2518 double p2y = SkScalarToDouble(p2.fY);
2519
2520 cross = SkDoubleToScalar((p1x - p0x) * (p2y - p0y) -
2521 (p1y - p0y) * (p2x - p0x));
2522
2523 }
2524 return cross;
reed@google.com69a99432012-01-10 18:00:10 +00002525}
2526
reed@google.comc1ea60a2012-01-31 15:15:36 +00002527// Returns the first pt with the maximum Y coordinate
reed@google.com69a99432012-01-10 18:00:10 +00002528static int find_max_y(const SkPoint pts[], int count) {
2529 SkASSERT(count > 0);
2530 SkScalar max = pts[0].fY;
reed@google.comc1ea60a2012-01-31 15:15:36 +00002531 int firstIndex = 0;
reed@google.com69a99432012-01-10 18:00:10 +00002532 for (int i = 1; i < count; ++i) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002533 SkScalar y = pts[i].fY;
2534 if (y > max) {
2535 max = y;
2536 firstIndex = i;
reed@google.com69a99432012-01-10 18:00:10 +00002537 }
2538 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002539 return firstIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002540}
2541
reed@google.comcabaf1d2012-01-11 21:03:05 +00002542static int find_diff_pt(const SkPoint pts[], int index, int n, int inc) {
2543 int i = index;
2544 for (;;) {
2545 i = (i + inc) % n;
2546 if (i == index) { // we wrapped around, so abort
2547 break;
2548 }
2549 if (pts[index] != pts[i]) { // found a different point, success!
2550 break;
2551 }
2552 }
2553 return i;
2554}
2555
reed@google.comc1ea60a2012-01-31 15:15:36 +00002556/**
2557 * Starting at index, and moving forward (incrementing), find the xmin and
2558 * xmax of the contiguous points that have the same Y.
2559 */
2560static int find_min_max_x_at_y(const SkPoint pts[], int index, int n,
2561 int* maxIndexPtr) {
2562 const SkScalar y = pts[index].fY;
2563 SkScalar min = pts[index].fX;
2564 SkScalar max = min;
2565 int minIndex = index;
2566 int maxIndex = index;
2567 for (int i = index + 1; i < n; ++i) {
2568 if (pts[i].fY != y) {
2569 break;
2570 }
2571 SkScalar x = pts[i].fX;
2572 if (x < min) {
2573 min = x;
2574 minIndex = i;
2575 } else if (x > max) {
2576 max = x;
2577 maxIndex = i;
2578 }
2579 }
2580 *maxIndexPtr = maxIndex;
2581 return minIndex;
2582}
2583
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002584static void crossToDir(SkScalar cross, SkPath::Direction* dir) {
reed@google.comac8543f2012-01-30 20:51:25 +00002585 if (dir) {
2586 *dir = cross > 0 ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
2587 }
reed@google.comac8543f2012-01-30 20:51:25 +00002588}
2589
reed@google.comc1ea60a2012-01-31 15:15:36 +00002590#if 0
2591#include "SkString.h"
2592#include "../utils/SkParsePath.h"
2593static void dumpPath(const SkPath& path) {
2594 SkString str;
2595 SkParsePath::ToSVGString(path, &str);
2596 SkDebugf("%s\n", str.c_str());
2597}
2598#endif
2599
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002600namespace {
2601// for use with convex_dir_test
2602double mul(double a, double b) { return a * b; }
2603SkScalar mul(SkScalar a, SkScalar b) { return SkScalarMul(a, b); }
2604double toDouble(SkScalar a) { return SkScalarToDouble(a); }
2605SkScalar toScalar(SkScalar a) { return a; }
2606
2607// determines the winding direction of a convex polygon with the precision
2608// of T. CAST_SCALAR casts an SkScalar to T.
2609template <typename T, T (CAST_SCALAR)(SkScalar)>
2610bool convex_dir_test(int n, const SkPoint pts[], SkPath::Direction* dir) {
2611 // we find the first three points that form a non-degenerate
2612 // triangle. If there are no such points then the path is
2613 // degenerate. The first is always point 0. Now we find the second
2614 // point.
2615 int i = 0;
2616 enum { kX = 0, kY = 1 };
2617 T v0[2];
2618 while (1) {
2619 v0[kX] = CAST_SCALAR(pts[i].fX) - CAST_SCALAR(pts[0].fX);
2620 v0[kY] = CAST_SCALAR(pts[i].fY) - CAST_SCALAR(pts[0].fY);
2621 if (v0[kX] || v0[kY]) {
2622 break;
2623 }
2624 if (++i == n - 1) {
2625 return false;
2626 }
2627 }
2628 // now find a third point that is not colinear with the first two
2629 // points and check the orientation of the triangle (which will be
2630 // the same as the orientation of the path).
2631 for (++i; i < n; ++i) {
2632 T v1[2];
2633 v1[kX] = CAST_SCALAR(pts[i].fX) - CAST_SCALAR(pts[0].fX);
2634 v1[kY] = CAST_SCALAR(pts[i].fY) - CAST_SCALAR(pts[0].fY);
2635 T cross = mul(v0[kX], v1[kY]) - mul(v0[kY], v1[kX]);
2636 if (0 != cross) {
2637 *dir = cross > 0 ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
2638 return true;
2639 }
2640 }
2641 return false;
2642}
2643}
2644
reed@google.comac8543f2012-01-30 20:51:25 +00002645/*
2646 * We loop through all contours, and keep the computed cross-product of the
2647 * contour that contained the global y-max. If we just look at the first
2648 * contour, we may find one that is wound the opposite way (correctly) since
2649 * it is the interior of a hole (e.g. 'o'). Thus we must find the contour
2650 * that is outer most (or at least has the global y-max) before we can consider
2651 * its cross product.
2652 */
reed@google.com69a99432012-01-10 18:00:10 +00002653bool SkPath::cheapComputeDirection(Direction* dir) const {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002654// dumpPath(*this);
reed@google.com69a99432012-01-10 18:00:10 +00002655 // don't want to pay the cost for computing this if it
2656 // is unknown, so we don't call isConvex()
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002657
2658 if (kUnknown_Direction != fDirection) {
2659 *dir = static_cast<Direction>(fDirection);
2660 return true;
2661 }
reed@google.com69a99432012-01-10 18:00:10 +00002662 const Convexity conv = this->getConvexityOrUnknown();
2663
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002664 ContourIter iter(*fPathRef.get());
reed@google.com69a99432012-01-10 18:00:10 +00002665
reed@google.comac8543f2012-01-30 20:51:25 +00002666 // initialize with our logical y-min
2667 SkScalar ymax = this->getBounds().fTop;
2668 SkScalar ymaxCross = 0;
2669
reed@google.com69a99432012-01-10 18:00:10 +00002670 for (; !iter.done(); iter.next()) {
2671 int n = iter.count();
reed@google.comcabaf1d2012-01-11 21:03:05 +00002672 if (n < 3) {
2673 continue;
2674 }
djsollen@google.come63793a2012-03-21 15:39:03 +00002675
reed@google.comcabaf1d2012-01-11 21:03:05 +00002676 const SkPoint* pts = iter.pts();
reed@google.com69a99432012-01-10 18:00:10 +00002677 SkScalar cross = 0;
2678 if (kConvex_Convexity == conv) {
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002679 // We try first at scalar precision, and then again at double
2680 // precision. This is because the vectors computed between distant
2681 // points may lose too much precision.
2682 if (convex_dir_test<SkScalar, toScalar>(n, pts, dir)) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002683 fDirection = *dir;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002684 return true;
2685 }
2686 if (convex_dir_test<double, toDouble>(n, pts, dir)) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002687 fDirection = *dir;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002688 return true;
2689 } else {
2690 return false;
reed@google.com69a99432012-01-10 18:00:10 +00002691 }
2692 } else {
reed@google.comcabaf1d2012-01-11 21:03:05 +00002693 int index = find_max_y(pts, n);
reed@google.comac8543f2012-01-30 20:51:25 +00002694 if (pts[index].fY < ymax) {
2695 continue;
2696 }
2697
reed@google.comc1ea60a2012-01-31 15:15:36 +00002698 // If there is more than 1 distinct point at the y-max, we take the
2699 // x-min and x-max of them and just subtract to compute the dir.
2700 if (pts[(index + 1) % n].fY == pts[index].fY) {
2701 int maxIndex;
2702 int minIndex = find_min_max_x_at_y(pts, index, n, &maxIndex);
reed@google.com3e44e4d2012-01-31 15:25:22 +00002703 if (minIndex == maxIndex) {
2704 goto TRY_CROSSPROD;
2705 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002706 SkASSERT(pts[minIndex].fY == pts[index].fY);
2707 SkASSERT(pts[maxIndex].fY == pts[index].fY);
2708 SkASSERT(pts[minIndex].fX <= pts[maxIndex].fX);
2709 // we just subtract the indices, and let that auto-convert to
2710 // SkScalar, since we just want - or + to signal the direction.
2711 cross = minIndex - maxIndex;
2712 } else {
reed@google.com3e44e4d2012-01-31 15:25:22 +00002713 TRY_CROSSPROD:
reed@google.comc1ea60a2012-01-31 15:15:36 +00002714 // Find a next and prev index to use for the cross-product test,
2715 // but we try to find pts that form non-zero vectors from pts[index]
2716 //
2717 // Its possible that we can't find two non-degenerate vectors, so
2718 // we have to guard our search (e.g. all the pts could be in the
2719 // same place).
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002720
reed@google.comc1ea60a2012-01-31 15:15:36 +00002721 // we pass n - 1 instead of -1 so we don't foul up % operator by
2722 // passing it a negative LH argument.
2723 int prev = find_diff_pt(pts, index, n, n - 1);
2724 if (prev == index) {
2725 // completely degenerate, skip to next contour
2726 continue;
2727 }
2728 int next = find_diff_pt(pts, index, n, 1);
2729 SkASSERT(next != index);
2730 cross = cross_prod(pts[prev], pts[index], pts[next]);
skia.committer@gmail.comfbb0ed92012-11-13 21:46:06 +00002731 // if we get a zero and the points are horizontal, then we look at the spread in
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002732 // x-direction. We really should continue to walk away from the degeneracy until
2733 // there is a divergence.
2734 if (0 == cross && pts[prev].fY == pts[index].fY && pts[next].fY == pts[index].fY) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002735 // construct the subtract so we get the correct Direction below
2736 cross = pts[index].fX - pts[next].fX;
2737 }
reed@google.com188bfcf2012-01-17 18:26:38 +00002738 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002739
reed@google.comac8543f2012-01-30 20:51:25 +00002740 if (cross) {
2741 // record our best guess so far
2742 ymax = pts[index].fY;
2743 ymaxCross = cross;
reed@google.com69a99432012-01-10 18:00:10 +00002744 }
reed@google.com69a99432012-01-10 18:00:10 +00002745 }
2746 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002747 if (ymaxCross) {
2748 crossToDir(ymaxCross, dir);
2749 fDirection = *dir;
2750 return true;
2751 } else {
2752 return false;
2753 }
reed@google.comac8543f2012-01-30 20:51:25 +00002754}
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002755
2756///////////////////////////////////////////////////////////////////////////////
2757
2758static SkScalar eval_cubic_coeff(SkScalar A, SkScalar B, SkScalar C,
2759 SkScalar D, SkScalar t) {
2760 return SkScalarMulAdd(SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C), t, D);
2761}
2762
2763static SkScalar eval_cubic_pts(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2764 SkScalar t) {
2765 SkScalar A = c3 + 3*(c1 - c2) - c0;
2766 SkScalar B = 3*(c2 - c1 - c1 + c0);
2767 SkScalar C = 3*(c1 - c0);
2768 SkScalar D = c0;
2769 return eval_cubic_coeff(A, B, C, D, t);
2770}
2771
2772/* Given 4 cubic points (either Xs or Ys), and a target X or Y, compute the
2773 t value such that cubic(t) = target
2774 */
2775static bool chopMonoCubicAt(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2776 SkScalar target, SkScalar* t) {
2777 // SkASSERT(c0 <= c1 && c1 <= c2 && c2 <= c3);
2778 SkASSERT(c0 < target && target < c3);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002779
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002780 SkScalar D = c0 - target;
2781 SkScalar A = c3 + 3*(c1 - c2) - c0;
2782 SkScalar B = 3*(c2 - c1 - c1 + c0);
2783 SkScalar C = 3*(c1 - c0);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002784
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002785 const SkScalar TOLERANCE = SK_Scalar1 / 4096;
2786 SkScalar minT = 0;
2787 SkScalar maxT = SK_Scalar1;
2788 SkScalar mid;
2789 int i;
2790 for (i = 0; i < 16; i++) {
2791 mid = SkScalarAve(minT, maxT);
2792 SkScalar delta = eval_cubic_coeff(A, B, C, D, mid);
2793 if (delta < 0) {
2794 minT = mid;
2795 delta = -delta;
2796 } else {
2797 maxT = mid;
2798 }
2799 if (delta < TOLERANCE) {
2800 break;
2801 }
2802 }
2803 *t = mid;
2804 return true;
2805}
2806
2807template <size_t N> static void find_minmax(const SkPoint pts[],
2808 SkScalar* minPtr, SkScalar* maxPtr) {
2809 SkScalar min, max;
2810 min = max = pts[0].fX;
2811 for (size_t i = 1; i < N; ++i) {
2812 min = SkMinScalar(min, pts[i].fX);
2813 max = SkMaxScalar(max, pts[i].fX);
2814 }
2815 *minPtr = min;
2816 *maxPtr = max;
2817}
2818
2819static int winding_mono_cubic(const SkPoint pts[], SkScalar x, SkScalar y) {
2820 SkPoint storage[4];
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002821
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002822 int dir = 1;
2823 if (pts[0].fY > pts[3].fY) {
2824 storage[0] = pts[3];
2825 storage[1] = pts[2];
2826 storage[2] = pts[1];
2827 storage[3] = pts[0];
2828 pts = storage;
2829 dir = -1;
2830 }
2831 if (y < pts[0].fY || y >= pts[3].fY) {
2832 return 0;
2833 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002834
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002835 // quickreject or quickaccept
2836 SkScalar min, max;
2837 find_minmax<4>(pts, &min, &max);
2838 if (x < min) {
2839 return 0;
2840 }
2841 if (x > max) {
2842 return dir;
2843 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002844
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002845 // compute the actual x(t) value
2846 SkScalar t, xt;
2847 if (chopMonoCubicAt(pts[0].fY, pts[1].fY, pts[2].fY, pts[3].fY, y, &t)) {
2848 xt = eval_cubic_pts(pts[0].fX, pts[1].fX, pts[2].fX, pts[3].fX, t);
2849 } else {
2850 SkScalar mid = SkScalarAve(pts[0].fY, pts[3].fY);
2851 xt = y < mid ? pts[0].fX : pts[3].fX;
2852 }
2853 return xt < x ? dir : 0;
2854}
2855
2856static int winding_cubic(const SkPoint pts[], SkScalar x, SkScalar y) {
2857 SkPoint dst[10];
2858 int n = SkChopCubicAtYExtrema(pts, dst);
2859 int w = 0;
2860 for (int i = 0; i <= n; ++i) {
2861 w += winding_mono_cubic(&dst[i * 3], x, y);
2862 }
2863 return w;
2864}
2865
2866static int winding_mono_quad(const SkPoint pts[], SkScalar x, SkScalar y) {
2867 SkScalar y0 = pts[0].fY;
2868 SkScalar y2 = pts[2].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002869
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002870 int dir = 1;
2871 if (y0 > y2) {
2872 SkTSwap(y0, y2);
2873 dir = -1;
2874 }
2875 if (y < y0 || y >= y2) {
2876 return 0;
2877 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002878
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002879 // bounds check on X (not required. is it faster?)
2880#if 0
2881 if (pts[0].fX > x && pts[1].fX > x && pts[2].fX > x) {
2882 return 0;
2883 }
2884#endif
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002885
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002886 SkScalar roots[2];
2887 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
2888 2 * (pts[1].fY - pts[0].fY),
2889 pts[0].fY - y,
2890 roots);
2891 SkASSERT(n <= 1);
2892 SkScalar xt;
2893 if (0 == n) {
2894 SkScalar mid = SkScalarAve(y0, y2);
2895 // Need [0] and [2] if dir == 1
2896 // and [2] and [0] if dir == -1
2897 xt = y < mid ? pts[1 - dir].fX : pts[dir - 1].fX;
2898 } else {
2899 SkScalar t = roots[0];
2900 SkScalar C = pts[0].fX;
2901 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
2902 SkScalar B = 2 * (pts[1].fX - C);
2903 xt = SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C);
2904 }
2905 return xt < x ? dir : 0;
2906}
2907
2908static bool is_mono_quad(SkScalar y0, SkScalar y1, SkScalar y2) {
2909 // return SkScalarSignAsInt(y0 - y1) + SkScalarSignAsInt(y1 - y2) != 0;
2910 if (y0 == y1) {
2911 return true;
2912 }
2913 if (y0 < y1) {
2914 return y1 <= y2;
2915 } else {
2916 return y1 >= y2;
2917 }
2918}
2919
2920static int winding_quad(const SkPoint pts[], SkScalar x, SkScalar y) {
2921 SkPoint dst[5];
2922 int n = 0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002923
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002924 if (!is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY)) {
2925 n = SkChopQuadAtYExtrema(pts, dst);
2926 pts = dst;
2927 }
2928 int w = winding_mono_quad(pts, x, y);
2929 if (n > 0) {
2930 w += winding_mono_quad(&pts[2], x, y);
2931 }
2932 return w;
2933}
2934
2935static int winding_line(const SkPoint pts[], SkScalar x, SkScalar y) {
2936 SkScalar x0 = pts[0].fX;
2937 SkScalar y0 = pts[0].fY;
2938 SkScalar x1 = pts[1].fX;
2939 SkScalar y1 = pts[1].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002940
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002941 SkScalar dy = y1 - y0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002942
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002943 int dir = 1;
2944 if (y0 > y1) {
2945 SkTSwap(y0, y1);
2946 dir = -1;
2947 }
2948 if (y < y0 || y >= y1) {
2949 return 0;
2950 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002951
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002952 SkScalar cross = SkScalarMul(x1 - x0, y - pts[0].fY) -
2953 SkScalarMul(dy, x - pts[0].fX);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002954
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002955 if (SkScalarSignAsInt(cross) == dir) {
2956 dir = 0;
2957 }
2958 return dir;
2959}
2960
reed@google.com4db592c2013-10-30 17:39:43 +00002961static bool contains_inclusive(const SkRect& r, SkScalar x, SkScalar y) {
2962 return r.fLeft <= x && x <= r.fRight && r.fTop <= y && y <= r.fBottom;
2963}
2964
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002965bool SkPath::contains(SkScalar x, SkScalar y) const {
2966 bool isInverse = this->isInverseFillType();
2967 if (this->isEmpty()) {
2968 return isInverse;
2969 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002970
reed@google.com4db592c2013-10-30 17:39:43 +00002971 if (!contains_inclusive(this->getBounds(), x, y)) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002972 return isInverse;
2973 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002974
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002975 SkPath::Iter iter(*this, true);
2976 bool done = false;
2977 int w = 0;
2978 do {
2979 SkPoint pts[4];
2980 switch (iter.next(pts, false)) {
2981 case SkPath::kMove_Verb:
2982 case SkPath::kClose_Verb:
2983 break;
2984 case SkPath::kLine_Verb:
2985 w += winding_line(pts, x, y);
2986 break;
2987 case SkPath::kQuad_Verb:
2988 w += winding_quad(pts, x, y);
2989 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002990 case SkPath::kConic_Verb:
2991 SkASSERT(0);
2992 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002993 case SkPath::kCubic_Verb:
2994 w += winding_cubic(pts, x, y);
2995 break;
2996 case SkPath::kDone_Verb:
2997 done = true;
2998 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002999 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003000 } while (!done);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003001
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003002 switch (this->getFillType()) {
3003 case SkPath::kEvenOdd_FillType:
3004 case SkPath::kInverseEvenOdd_FillType:
3005 w &= 1;
3006 break;
reed@google.come9bb6232012-07-11 18:56:10 +00003007 default:
3008 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003009 }
3010 return SkToBool(w);
3011}