blob: 0b1444ca11a37f25891e4754ad278777b8938c02 [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
10#include "SkPath.h"
djsollen@google.com94e75ee2012-06-08 18:30:46 +000011#include "SkBuffer.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000012#include "SkMath.h"
13
bsalomon@google.com65a87cc2012-08-14 13:15:44 +000014SK_DEFINE_INST_COUNT(SkPath);
15
reed@google.com744faba2012-05-29 19:54:52 +000016// This value is just made-up for now. When count is 4, calling memset was much
17// slower than just writing the loop. This seems odd, and hopefully in the
18// future this we appear to have been a fluke...
19#define MIN_COUNT_FOR_MEMSET_TO_BE_FAST 16
20
reed@android.com8a1c16f2008-12-17 15:59:43 +000021////////////////////////////////////////////////////////////////////////////
22
reed@google.com3563c9e2011-11-14 19:34:57 +000023/**
24 * Path.bounds is defined to be the bounds of all the control points.
25 * If we called bounds.join(r) we would skip r if r was empty, which breaks
26 * our promise. Hence we have a custom joiner that doesn't look at emptiness
27 */
28static void joinNoEmptyChecks(SkRect* dst, const SkRect& src) {
29 dst->fLeft = SkMinScalar(dst->fLeft, src.fLeft);
30 dst->fTop = SkMinScalar(dst->fTop, src.fTop);
31 dst->fRight = SkMaxScalar(dst->fRight, src.fRight);
32 dst->fBottom = SkMaxScalar(dst->fBottom, src.fBottom);
33}
34
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000035static bool is_degenerate(const SkPath& path) {
36 SkPath::Iter iter(path, false);
37 SkPoint pts[4];
38 return SkPath::kDone_Verb == iter.next(pts);
39}
40
bsalomon@google.com6aa29652012-04-18 13:29:52 +000041class SkAutoDisableOvalCheck {
42public:
43 SkAutoDisableOvalCheck(SkPath* path) : fPath(path) {
44 fSaved = fPath->fIsOval;
45 }
46
47 ~SkAutoDisableOvalCheck() {
48 fPath->fIsOval = fSaved;
49 }
50
51private:
52 SkPath* fPath;
53 bool fSaved;
54};
55
reed@android.com8a1c16f2008-12-17 15:59:43 +000056/* This guy's constructor/destructor bracket a path editing operation. It is
57 used when we know the bounds of the amount we are going to add to the path
58 (usually a new contour, but not required).
reed@google.comabf15c12011-01-18 20:35:51 +000059
reed@android.com8a1c16f2008-12-17 15:59:43 +000060 It captures some state about the path up front (i.e. if it already has a
61 cached bounds), and the if it can, it updates the cache bounds explicitly,
reed@android.comd252db02009-04-01 18:31:44 +000062 avoiding the need to revisit all of the points in getBounds().
reed@google.comabf15c12011-01-18 20:35:51 +000063
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000064 It also notes if the path was originally degenerate, and if so, sets
65 isConvex to true. Thus it can only be used if the contour being added is
66 convex.
reed@android.com8a1c16f2008-12-17 15:59:43 +000067 */
68class SkAutoPathBoundsUpdate {
69public:
70 SkAutoPathBoundsUpdate(SkPath* path, const SkRect& r) : fRect(r) {
71 this->init(path);
72 }
73
74 SkAutoPathBoundsUpdate(SkPath* path, SkScalar left, SkScalar top,
75 SkScalar right, SkScalar bottom) {
76 fRect.set(left, top, right, bottom);
77 this->init(path);
78 }
reed@google.comabf15c12011-01-18 20:35:51 +000079
reed@android.com8a1c16f2008-12-17 15:59:43 +000080 ~SkAutoPathBoundsUpdate() {
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000081 fPath->setIsConvex(fDegenerate);
reed@android.com8a1c16f2008-12-17 15:59:43 +000082 if (fEmpty) {
reed@android.comd252db02009-04-01 18:31:44 +000083 fPath->fBounds = fRect;
84 fPath->fBoundsIsDirty = false;
reed@google.com0bb18bb2012-07-26 15:20:36 +000085 fPath->fIsFinite = fPath->fBounds.isFinite();
reed@android.com8a1c16f2008-12-17 15:59:43 +000086 } else if (!fDirty) {
reed@google.com3563c9e2011-11-14 19:34:57 +000087 joinNoEmptyChecks(&fPath->fBounds, fRect);
reed@android.comd252db02009-04-01 18:31:44 +000088 fPath->fBoundsIsDirty = false;
reed@google.com0bb18bb2012-07-26 15:20:36 +000089 fPath->fIsFinite = fPath->fBounds.isFinite();
reed@android.com8a1c16f2008-12-17 15:59:43 +000090 }
91 }
reed@google.comabf15c12011-01-18 20:35:51 +000092
reed@android.com8a1c16f2008-12-17 15:59:43 +000093private:
reed@android.com6b82d1a2009-06-03 02:35:01 +000094 SkPath* fPath;
95 SkRect fRect;
96 bool fDirty;
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000097 bool fDegenerate;
reed@android.com6b82d1a2009-06-03 02:35:01 +000098 bool fEmpty;
reed@google.comabf15c12011-01-18 20:35:51 +000099
reed@android.com8a1c16f2008-12-17 15:59:43 +0000100 // returns true if we should proceed
reed@android.com6b82d1a2009-06-03 02:35:01 +0000101 void init(SkPath* path) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000102 fPath = path;
reed@android.com63debae2009-12-16 17:25:43 +0000103 fDirty = SkToBool(path->fBoundsIsDirty);
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +0000104 fDegenerate = is_degenerate(*path);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000105 fEmpty = path->isEmpty();
reed@android.com6c14b432009-03-23 20:11:11 +0000106 // Cannot use fRect for our bounds unless we know it is sorted
reed@android.com49f0ff22009-03-19 21:52:42 +0000107 fRect.sort();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000108 }
109};
110
reed@google.com0bb18bb2012-07-26 15:20:36 +0000111// Return true if the computed bounds are finite.
112static bool compute_pt_bounds(SkRect* bounds, const SkTDArray<SkPoint>& pts) {
113 int count = pts.count();
114 if (count <= 1) { // we ignore just 1 point (moveto)
115 bounds->setEmpty();
116 return count ? pts.begin()->isFinite() : true;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000117 } else {
reed@google.com0bb18bb2012-07-26 15:20:36 +0000118 return bounds->setBoundsCheck(pts.begin(), pts.count());
reed@android.com8a1c16f2008-12-17 15:59:43 +0000119 }
120}
121
122////////////////////////////////////////////////////////////////////////////
123
124/*
125 Stores the verbs and points as they are given to us, with exceptions:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000126 - we only record "Close" if it was immediately preceeded by Move | Line | Quad | Cubic
reed@android.com8a1c16f2008-12-17 15:59:43 +0000127 - we insert a Move(0,0) if Line | Quad | Cubic is our first command
128
129 The iterator does more cleanup, especially if forceClose == true
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000130 1. If we encounter degenerate segments, remove them
131 2. if we encounter Close, return a cons'd up Line() first (if the curr-pt != start-pt)
132 3. if we encounter Move without a preceeding Close, and forceClose is true, goto #2
133 4. if we encounter Line | Quad | Cubic after Close, cons up a Move
reed@android.com8a1c16f2008-12-17 15:59:43 +0000134*/
135
136////////////////////////////////////////////////////////////////////////////
137
reed@google.comd335d1d2012-01-12 18:17:11 +0000138// flag to require a moveTo if we begin with something else, like lineTo etc.
139#define INITIAL_LASTMOVETOINDEX_VALUE ~0
140
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000141SkPath::SkPath()
bsalomon@google.com2ec72802011-09-21 21:46:03 +0000142 : fFillType(kWinding_FillType)
143 , fBoundsIsDirty(true) {
reed@google.com04863fa2011-05-15 04:08:24 +0000144 fConvexity = kUnknown_Convexity;
reed@google.com10296cc2011-09-21 12:29:05 +0000145 fSegmentMask = 0;
reed@google.comd335d1d2012-01-12 18:17:11 +0000146 fLastMoveToIndex = INITIAL_LASTMOVETOINDEX_VALUE;
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000147 fIsOval = false;
reed@google.com0bb18bb2012-07-26 15:20:36 +0000148 fIsFinite = false; // gets computed when we know our bounds
djsollen@google.com56c69772011-11-08 19:00:26 +0000149#ifdef SK_BUILD_FOR_ANDROID
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000150 fGenerationID = 0;
djsollen@google.come63793a2012-03-21 15:39:03 +0000151 fSourcePath = NULL;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000152#endif
reed@android.com6b82d1a2009-06-03 02:35:01 +0000153}
reed@android.com8a1c16f2008-12-17 15:59:43 +0000154
155SkPath::SkPath(const SkPath& src) {
156 SkDEBUGCODE(src.validate();)
157 *this = src;
djsollen@google.com56c69772011-11-08 19:00:26 +0000158#ifdef SK_BUILD_FOR_ANDROID
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000159 // the assignment operator above increments the ID so correct for that here
djsollen@google.come63793a2012-03-21 15:39:03 +0000160 fGenerationID = src.fGenerationID;
161 fSourcePath = NULL;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000162#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000163}
164
165SkPath::~SkPath() {
166 SkDEBUGCODE(this->validate();)
167}
168
169SkPath& SkPath::operator=(const SkPath& src) {
170 SkDEBUGCODE(src.validate();)
171
172 if (this != &src) {
reed@android.comd252db02009-04-01 18:31:44 +0000173 fBounds = src.fBounds;
174 fPts = src.fPts;
175 fVerbs = src.fVerbs;
176 fFillType = src.fFillType;
177 fBoundsIsDirty = src.fBoundsIsDirty;
reed@google.com04863fa2011-05-15 04:08:24 +0000178 fConvexity = src.fConvexity;
reed@google.com0bb18bb2012-07-26 15:20:36 +0000179 fIsFinite = src.fIsFinite;
reed@google.com10296cc2011-09-21 12:29:05 +0000180 fSegmentMask = src.fSegmentMask;
reed@google.comd335d1d2012-01-12 18:17:11 +0000181 fLastMoveToIndex = src.fLastMoveToIndex;
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000182 fIsOval = src.fIsOval;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000183 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000184 }
185 SkDEBUGCODE(this->validate();)
186 return *this;
187}
188
reed@android.com3abec1d2009-03-02 05:36:20 +0000189bool operator==(const SkPath& a, const SkPath& b) {
reed@android.com6b82d1a2009-06-03 02:35:01 +0000190 // note: don't need to look at isConvex or bounds, since just comparing the
191 // raw data is sufficient.
reed@google.com10296cc2011-09-21 12:29:05 +0000192
193 // We explicitly check fSegmentMask as a quick-reject. We could skip it,
194 // since it is only a cache of info in the fVerbs, but its a fast way to
195 // notice a difference
196
reed@android.com3abec1d2009-03-02 05:36:20 +0000197 return &a == &b ||
reed@google.com10296cc2011-09-21 12:29:05 +0000198 (a.fFillType == b.fFillType && a.fSegmentMask == b.fSegmentMask &&
199 a.fVerbs == b.fVerbs && a.fPts == b.fPts);
reed@android.com3abec1d2009-03-02 05:36:20 +0000200}
201
reed@android.com8a1c16f2008-12-17 15:59:43 +0000202void SkPath::swap(SkPath& other) {
203 SkASSERT(&other != NULL);
204
205 if (this != &other) {
reed@android.comd252db02009-04-01 18:31:44 +0000206 SkTSwap<SkRect>(fBounds, other.fBounds);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000207 fPts.swap(other.fPts);
208 fVerbs.swap(other.fVerbs);
209 SkTSwap<uint8_t>(fFillType, other.fFillType);
reed@android.comd252db02009-04-01 18:31:44 +0000210 SkTSwap<uint8_t>(fBoundsIsDirty, other.fBoundsIsDirty);
reed@google.com04863fa2011-05-15 04:08:24 +0000211 SkTSwap<uint8_t>(fConvexity, other.fConvexity);
reed@google.com10296cc2011-09-21 12:29:05 +0000212 SkTSwap<uint8_t>(fSegmentMask, other.fSegmentMask);
reed@google.comd335d1d2012-01-12 18:17:11 +0000213 SkTSwap<int>(fLastMoveToIndex, other.fLastMoveToIndex);
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000214 SkTSwap<SkBool8>(fIsOval, other.fIsOval);
reed@google.com0bb18bb2012-07-26 15:20:36 +0000215 SkTSwap<SkBool8>(fIsFinite, other.fIsFinite);
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000216 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000217 }
218}
219
djsollen@google.com56c69772011-11-08 19:00:26 +0000220#ifdef SK_BUILD_FOR_ANDROID
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000221uint32_t SkPath::getGenerationID() const {
222 return fGenerationID;
223}
djsollen@google.come63793a2012-03-21 15:39:03 +0000224
225const SkPath* SkPath::getSourcePath() const {
226 return fSourcePath;
227}
228
229void SkPath::setSourcePath(const SkPath* path) {
230 fSourcePath = path;
231}
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000232#endif
233
reed@android.com8a1c16f2008-12-17 15:59:43 +0000234void SkPath::reset() {
235 SkDEBUGCODE(this->validate();)
236
237 fPts.reset();
238 fVerbs.reset();
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000239 GEN_ID_INC;
reed@android.comd252db02009-04-01 18:31:44 +0000240 fBoundsIsDirty = true;
reed@google.com04863fa2011-05-15 04:08:24 +0000241 fConvexity = kUnknown_Convexity;
reed@google.com10296cc2011-09-21 12:29:05 +0000242 fSegmentMask = 0;
reed@google.comd335d1d2012-01-12 18:17:11 +0000243 fLastMoveToIndex = INITIAL_LASTMOVETOINDEX_VALUE;
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000244 fIsOval = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000245}
246
247void SkPath::rewind() {
248 SkDEBUGCODE(this->validate();)
249
250 fPts.rewind();
251 fVerbs.rewind();
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000252 GEN_ID_INC;
reed@google.com04863fa2011-05-15 04:08:24 +0000253 fConvexity = kUnknown_Convexity;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000254 fBoundsIsDirty = true;
reed@google.com10296cc2011-09-21 12:29:05 +0000255 fSegmentMask = 0;
reed@google.comd335d1d2012-01-12 18:17:11 +0000256 fLastMoveToIndex = INITIAL_LASTMOVETOINDEX_VALUE;
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000257 fIsOval = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000258}
259
260bool SkPath::isEmpty() const {
261 SkDEBUGCODE(this->validate();)
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000262 return 0 == fVerbs.count();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000263}
264
reed@google.com7e6c4d12012-05-10 14:05:43 +0000265bool SkPath::isLine(SkPoint line[2]) const {
266 int verbCount = fVerbs.count();
267 int ptCount = fPts.count();
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000268
reed@google.com7e6c4d12012-05-10 14:05:43 +0000269 if (2 == verbCount && 2 == ptCount) {
270 const uint8_t* verbs = fVerbs.begin();
271 if (kMove_Verb == verbs[0] && kLine_Verb == verbs[1]) {
272 if (line) {
273 const SkPoint* pts = fPts.begin();
274 line[0] = pts[0];
275 line[1] = pts[1];
276 }
277 return true;
278 }
279 }
280 return false;
281}
282
caryclark@google.comf1316942011-07-26 19:54:45 +0000283/*
284 Determines if path is a rect by keeping track of changes in direction
285 and looking for a loop either clockwise or counterclockwise.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000286
caryclark@google.comf1316942011-07-26 19:54:45 +0000287 The direction is computed such that:
288 0: vertical up
289 1: horizontal right
290 2: vertical down
291 3: horizontal left
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000292
caryclark@google.comf1316942011-07-26 19:54:45 +0000293A rectangle cycles up/right/down/left or up/left/down/right.
294
295The test fails if:
296 The path is closed, and followed by a line.
297 A second move creates a new endpoint.
298 A diagonal line is parsed.
299 There's more than four changes of direction.
300 There's a discontinuity on the line (e.g., a move in the middle)
301 The line reverses direction.
302 The rectangle doesn't complete a cycle.
303 The path contains a quadratic or cubic.
304 The path contains fewer than four points.
305 The final point isn't equal to the first point.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000306
caryclark@google.comf1316942011-07-26 19:54:45 +0000307It's OK if the path has:
308 Several colinear line segments composing a rectangle side.
309 Single points on the rectangle side.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000310
caryclark@google.comf1316942011-07-26 19:54:45 +0000311The direction takes advantage of the corners found since opposite sides
312must travel in opposite directions.
313
314FIXME: Allow colinear quads and cubics to be treated like lines.
315FIXME: If the API passes fill-only, return true if the filled stroke
316 is a rectangle, though the caller failed to close the path.
317 */
318bool SkPath::isRect(SkRect* rect) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000319 SkDEBUGCODE(this->validate();)
reed@google.comabf15c12011-01-18 20:35:51 +0000320
caryclark@google.comf1316942011-07-26 19:54:45 +0000321 int corners = 0;
322 SkPoint first, last;
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000323 first.set(0, 0);
324 last.set(0, 0);
325 int firstDirection = 0;
326 int lastDirection = 0;
327 int nextDirection = 0;
328 bool closedOrMoved = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000329 bool autoClose = false;
330 const uint8_t* verbs = fVerbs.begin();
331 const uint8_t* verbStop = fVerbs.end();
332 const SkPoint* pts = fPts.begin();
333 while (verbs != verbStop) {
334 switch (*verbs++) {
335 case kClose_Verb:
336 pts = fPts.begin();
337 autoClose = true;
338 case kLine_Verb: {
339 SkScalar left = last.fX;
340 SkScalar top = last.fY;
341 SkScalar right = pts->fX;
342 SkScalar bottom = pts->fY;
343 ++pts;
344 if (left != right && top != bottom) {
345 return false; // diagonal
346 }
347 if (left == right && top == bottom) {
348 break; // single point on side OK
349 }
350 nextDirection = (left != right) << 0 |
351 (left < right || top < bottom) << 1;
352 if (0 == corners) {
353 firstDirection = nextDirection;
354 first = last;
355 last = pts[-1];
356 corners = 1;
357 closedOrMoved = false;
358 break;
359 }
360 if (closedOrMoved) {
361 return false; // closed followed by a line
362 }
363 closedOrMoved = autoClose;
364 if (lastDirection != nextDirection) {
365 if (++corners > 4) {
366 return false; // too many direction changes
367 }
368 }
369 last = pts[-1];
370 if (lastDirection == nextDirection) {
371 break; // colinear segment
372 }
373 // Possible values for corners are 2, 3, and 4.
374 // When corners == 3, nextDirection opposes firstDirection.
375 // Otherwise, nextDirection at corner 2 opposes corner 4.
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000376 int turn = firstDirection ^ (corners - 1);
caryclark@google.comf1316942011-07-26 19:54:45 +0000377 int directionCycle = 3 == corners ? 0 : nextDirection ^ turn;
378 if ((directionCycle ^ turn) != nextDirection) {
379 return false; // direction didn't follow cycle
380 }
381 break;
382 }
383 case kQuad_Verb:
384 case kCubic_Verb:
385 return false; // quadratic, cubic not allowed
386 case kMove_Verb:
387 last = *pts++;
388 closedOrMoved = true;
389 break;
390 }
391 lastDirection = nextDirection;
392 }
393 // Success if 4 corners and first point equals last
394 bool result = 4 == corners && first == last;
395 if (result && rect) {
396 *rect = getBounds();
397 }
398 return result;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000399}
400
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000401int SkPath::getPoints(SkPoint dst[], int max) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000402 SkDEBUGCODE(this->validate();)
403
404 SkASSERT(max >= 0);
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000405 SkASSERT(!max || dst);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000406 int count = fPts.count();
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000407 fPts.copyRange(dst, 0, max);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000408 return count;
409}
410
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000411SkPoint SkPath::getPoint(int index) const {
412 if ((unsigned)index < (unsigned)fPts.count()) {
413 return fPts[index];
414 }
415 return SkPoint::Make(0, 0);
416}
417
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000418int SkPath::getVerbs(uint8_t dst[], int max) const {
419 SkDEBUGCODE(this->validate();)
420
421 SkASSERT(max >= 0);
422 SkASSERT(!max || dst);
423 fVerbs.copyRange(dst, 0, max);
424 return fVerbs.count();
425}
426
reed@google.com294dd7b2011-10-11 11:58:32 +0000427bool SkPath::getLastPt(SkPoint* lastPt) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000428 SkDEBUGCODE(this->validate();)
429
reed@google.com294dd7b2011-10-11 11:58:32 +0000430 int count = fPts.count();
431 if (count > 0) {
432 if (lastPt) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000433 *lastPt = fPts[count - 1];
434 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000435 return true;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000436 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000437 if (lastPt) {
438 lastPt->set(0, 0);
439 }
440 return false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000441}
442
443void SkPath::setLastPt(SkScalar x, SkScalar y) {
444 SkDEBUGCODE(this->validate();)
445
446 int count = fPts.count();
447 if (count == 0) {
448 this->moveTo(x, y);
449 } else {
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000450 fIsOval = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000451 fPts[count - 1].set(x, y);
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000452 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000453 }
454}
455
reed@android.comd252db02009-04-01 18:31:44 +0000456void SkPath::computeBounds() const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000457 SkDEBUGCODE(this->validate();)
reed@android.comd252db02009-04-01 18:31:44 +0000458 SkASSERT(fBoundsIsDirty);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000459
reed@android.comd252db02009-04-01 18:31:44 +0000460 fBoundsIsDirty = false;
reed@google.com0bb18bb2012-07-26 15:20:36 +0000461 fIsFinite = compute_pt_bounds(&fBounds, fPts);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000462}
463
reed@google.com04863fa2011-05-15 04:08:24 +0000464void SkPath::setConvexity(Convexity c) {
465 if (fConvexity != c) {
466 fConvexity = c;
467 GEN_ID_INC;
468 }
469}
470
reed@android.com8a1c16f2008-12-17 15:59:43 +0000471//////////////////////////////////////////////////////////////////////////////
472// Construction methods
473
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000474#define DIRTY_AFTER_EDIT \
475 do { \
476 fBoundsIsDirty = true; \
477 fConvexity = kUnknown_Convexity; \
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000478 fIsOval = false; \
reed@google.comb54455e2011-05-16 14:16:04 +0000479 } while (0)
480
bsalomon@google.comf3bf18b2012-02-02 15:00:19 +0000481#define DIRTY_AFTER_EDIT_NO_CONVEXITY_CHANGE \
482 do { \
483 fBoundsIsDirty = true; \
484 } while (0)
485
reed@android.com8a1c16f2008-12-17 15:59:43 +0000486void SkPath::incReserve(U16CPU inc) {
487 SkDEBUGCODE(this->validate();)
488
489 fVerbs.setReserve(fVerbs.count() + inc);
490 fPts.setReserve(fPts.count() + inc);
491
492 SkDEBUGCODE(this->validate();)
493}
494
495void SkPath::moveTo(SkScalar x, SkScalar y) {
496 SkDEBUGCODE(this->validate();)
497
reed@android.com8a1c16f2008-12-17 15:59:43 +0000498 SkPoint* pt;
499
reed@google.comd335d1d2012-01-12 18:17:11 +0000500 // remember our index
501 fLastMoveToIndex = fPts.count();
502
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000503 pt = fPts.append();
504 *fVerbs.append() = kMove_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000505 pt->set(x, y);
506
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000507 GEN_ID_INC;
bsalomon@google.comf3bf18b2012-02-02 15:00:19 +0000508 DIRTY_AFTER_EDIT_NO_CONVEXITY_CHANGE;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000509}
510
511void SkPath::rMoveTo(SkScalar x, SkScalar y) {
512 SkPoint pt;
513 this->getLastPt(&pt);
514 this->moveTo(pt.fX + x, pt.fY + y);
515}
516
reed@google.comd335d1d2012-01-12 18:17:11 +0000517void SkPath::injectMoveToIfNeeded() {
518 if (fLastMoveToIndex < 0) {
519 SkScalar x, y;
520 if (fVerbs.count() == 0) {
521 x = y = 0;
522 } else {
523 const SkPoint& pt = fPts[~fLastMoveToIndex];
524 x = pt.fX;
525 y = pt.fY;
526 }
527 this->moveTo(x, y);
528 }
529}
530
reed@android.com8a1c16f2008-12-17 15:59:43 +0000531void SkPath::lineTo(SkScalar x, SkScalar y) {
532 SkDEBUGCODE(this->validate();)
533
reed@google.comd335d1d2012-01-12 18:17:11 +0000534 this->injectMoveToIfNeeded();
535
reed@android.com8a1c16f2008-12-17 15:59:43 +0000536 fPts.append()->set(x, y);
537 *fVerbs.append() = kLine_Verb;
reed@google.com10296cc2011-09-21 12:29:05 +0000538 fSegmentMask |= kLine_SegmentMask;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000539
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000540 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000541 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000542}
543
544void SkPath::rLineTo(SkScalar x, SkScalar y) {
545 SkPoint pt;
546 this->getLastPt(&pt);
547 this->lineTo(pt.fX + x, pt.fY + y);
548}
549
550void SkPath::quadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
551 SkDEBUGCODE(this->validate();)
552
reed@google.comd335d1d2012-01-12 18:17:11 +0000553 this->injectMoveToIfNeeded();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000554
555 SkPoint* pts = fPts.append(2);
556 pts[0].set(x1, y1);
557 pts[1].set(x2, y2);
558 *fVerbs.append() = kQuad_Verb;
reed@google.com10296cc2011-09-21 12:29:05 +0000559 fSegmentMask |= kQuad_SegmentMask;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000560
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000561 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000562 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000563}
564
565void SkPath::rQuadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
566 SkPoint pt;
567 this->getLastPt(&pt);
568 this->quadTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2);
569}
570
571void SkPath::cubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
572 SkScalar x3, SkScalar y3) {
573 SkDEBUGCODE(this->validate();)
574
reed@google.comd335d1d2012-01-12 18:17:11 +0000575 this->injectMoveToIfNeeded();
576
reed@android.com8a1c16f2008-12-17 15:59:43 +0000577 SkPoint* pts = fPts.append(3);
578 pts[0].set(x1, y1);
579 pts[1].set(x2, y2);
580 pts[2].set(x3, y3);
581 *fVerbs.append() = kCubic_Verb;
reed@google.com10296cc2011-09-21 12:29:05 +0000582 fSegmentMask |= kCubic_SegmentMask;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000583
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000584 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000585 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000586}
587
588void SkPath::rCubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
589 SkScalar x3, SkScalar y3) {
590 SkPoint pt;
591 this->getLastPt(&pt);
592 this->cubicTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2,
593 pt.fX + x3, pt.fY + y3);
594}
595
596void SkPath::close() {
597 SkDEBUGCODE(this->validate();)
598
599 int count = fVerbs.count();
600 if (count > 0) {
601 switch (fVerbs[count - 1]) {
602 case kLine_Verb:
603 case kQuad_Verb:
604 case kCubic_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000605 case kMove_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +0000606 *fVerbs.append() = kClose_Verb;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000607 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000608 break;
609 default:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000610 // don't add a close if it's the first verb or a repeat
reed@android.com8a1c16f2008-12-17 15:59:43 +0000611 break;
612 }
613 }
reed@google.comd335d1d2012-01-12 18:17:11 +0000614
615 // signal that we need a moveTo to follow us (unless we're done)
616#if 0
617 if (fLastMoveToIndex >= 0) {
618 fLastMoveToIndex = ~fLastMoveToIndex;
619 }
620#else
621 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
622#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000623}
624
625///////////////////////////////////////////////////////////////////////////////
reed@google.comabf15c12011-01-18 20:35:51 +0000626
reed@android.com8a1c16f2008-12-17 15:59:43 +0000627void SkPath::addRect(const SkRect& rect, Direction dir) {
628 this->addRect(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, dir);
629}
630
631void SkPath::addRect(SkScalar left, SkScalar top, SkScalar right,
632 SkScalar bottom, Direction dir) {
633 SkAutoPathBoundsUpdate apbu(this, left, top, right, bottom);
634
635 this->incReserve(5);
636
637 this->moveTo(left, top);
638 if (dir == kCCW_Direction) {
639 this->lineTo(left, bottom);
640 this->lineTo(right, bottom);
641 this->lineTo(right, top);
642 } else {
643 this->lineTo(right, top);
644 this->lineTo(right, bottom);
645 this->lineTo(left, bottom);
646 }
647 this->close();
648}
649
reed@google.com744faba2012-05-29 19:54:52 +0000650void SkPath::addPoly(const SkPoint pts[], int count, bool close) {
651 SkDEBUGCODE(this->validate();)
652 if (count <= 0) {
653 return;
654 }
655
656 fLastMoveToIndex = fPts.count();
657 fPts.append(count, pts);
658
659 // +close makes room for the extra kClose_Verb
660 uint8_t* vb = fVerbs.append(count + close);
661 vb[0] = kMove_Verb;
662
663 if (count > 1) {
664 // cast to unsigned, so if MIN_COUNT_FOR_MEMSET_TO_BE_FAST is defined to
665 // be 0, the compiler will remove the test/branch entirely.
666 if ((unsigned)count >= MIN_COUNT_FOR_MEMSET_TO_BE_FAST) {
667 memset(&vb[1], kLine_Verb, count - 1);
668 } else {
669 for (int i = 1; i < count; ++i) {
670 vb[i] = kLine_Verb;
671 }
672 }
673 fSegmentMask |= kLine_SegmentMask;
674 }
675 if (close) {
676 vb[count] = kClose_Verb;
677 }
678
679 GEN_ID_INC;
680 DIRTY_AFTER_EDIT;
681}
682
reed@android.com8a1c16f2008-12-17 15:59:43 +0000683#define CUBIC_ARC_FACTOR ((SK_ScalarSqrt2 - SK_Scalar1) * 4 / 3)
684
685void SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
686 Direction dir) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000687 SkScalar w = rect.width();
688 SkScalar halfW = SkScalarHalf(w);
689 SkScalar h = rect.height();
690 SkScalar halfH = SkScalarHalf(h);
691
692 if (halfW <= 0 || halfH <= 0) {
693 return;
694 }
695
reed@google.comabf15c12011-01-18 20:35:51 +0000696 bool skip_hori = rx >= halfW;
697 bool skip_vert = ry >= halfH;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000698
699 if (skip_hori && skip_vert) {
700 this->addOval(rect, dir);
701 return;
702 }
reed@google.comabf15c12011-01-18 20:35:51 +0000703
704 SkAutoPathBoundsUpdate apbu(this, rect);
705
reed@android.com8a1c16f2008-12-17 15:59:43 +0000706 if (skip_hori) {
707 rx = halfW;
708 } else if (skip_vert) {
709 ry = halfH;
710 }
711
712 SkScalar sx = SkScalarMul(rx, CUBIC_ARC_FACTOR);
713 SkScalar sy = SkScalarMul(ry, CUBIC_ARC_FACTOR);
714
715 this->incReserve(17);
716 this->moveTo(rect.fRight - rx, rect.fTop);
717 if (dir == kCCW_Direction) {
718 if (!skip_hori) {
719 this->lineTo(rect.fLeft + rx, rect.fTop); // top
720 }
721 this->cubicTo(rect.fLeft + rx - sx, rect.fTop,
722 rect.fLeft, rect.fTop + ry - sy,
723 rect.fLeft, rect.fTop + ry); // top-left
724 if (!skip_vert) {
725 this->lineTo(rect.fLeft, rect.fBottom - ry); // left
726 }
727 this->cubicTo(rect.fLeft, rect.fBottom - ry + sy,
728 rect.fLeft + rx - sx, rect.fBottom,
729 rect.fLeft + rx, rect.fBottom); // bot-left
730 if (!skip_hori) {
731 this->lineTo(rect.fRight - rx, rect.fBottom); // bottom
732 }
733 this->cubicTo(rect.fRight - rx + sx, rect.fBottom,
734 rect.fRight, rect.fBottom - ry + sy,
735 rect.fRight, rect.fBottom - ry); // bot-right
736 if (!skip_vert) {
737 this->lineTo(rect.fRight, rect.fTop + ry);
738 }
739 this->cubicTo(rect.fRight, rect.fTop + ry - sy,
740 rect.fRight - rx + sx, rect.fTop,
741 rect.fRight - rx, rect.fTop); // top-right
742 } else {
743 this->cubicTo(rect.fRight - rx + sx, rect.fTop,
744 rect.fRight, rect.fTop + ry - sy,
745 rect.fRight, rect.fTop + ry); // top-right
746 if (!skip_vert) {
747 this->lineTo(rect.fRight, rect.fBottom - ry);
748 }
749 this->cubicTo(rect.fRight, rect.fBottom - ry + sy,
750 rect.fRight - rx + sx, rect.fBottom,
751 rect.fRight - rx, rect.fBottom); // bot-right
752 if (!skip_hori) {
753 this->lineTo(rect.fLeft + rx, rect.fBottom); // bottom
754 }
755 this->cubicTo(rect.fLeft + rx - sx, rect.fBottom,
756 rect.fLeft, rect.fBottom - ry + sy,
757 rect.fLeft, rect.fBottom - ry); // bot-left
758 if (!skip_vert) {
759 this->lineTo(rect.fLeft, rect.fTop + ry); // left
760 }
761 this->cubicTo(rect.fLeft, rect.fTop + ry - sy,
762 rect.fLeft + rx - sx, rect.fTop,
763 rect.fLeft + rx, rect.fTop); // top-left
764 if (!skip_hori) {
765 this->lineTo(rect.fRight - rx, rect.fTop); // top
766 }
767 }
768 this->close();
769}
770
771static void add_corner_arc(SkPath* path, const SkRect& rect,
772 SkScalar rx, SkScalar ry, int startAngle,
773 SkPath::Direction dir, bool forceMoveTo) {
774 rx = SkMinScalar(SkScalarHalf(rect.width()), rx);
775 ry = SkMinScalar(SkScalarHalf(rect.height()), ry);
reed@google.comabf15c12011-01-18 20:35:51 +0000776
reed@android.com8a1c16f2008-12-17 15:59:43 +0000777 SkRect r;
778 r.set(-rx, -ry, rx, ry);
779
780 switch (startAngle) {
781 case 0:
782 r.offset(rect.fRight - r.fRight, rect.fBottom - r.fBottom);
783 break;
784 case 90:
785 r.offset(rect.fLeft - r.fLeft, rect.fBottom - r.fBottom);
786 break;
787 case 180: r.offset(rect.fLeft - r.fLeft, rect.fTop - r.fTop); break;
788 case 270: r.offset(rect.fRight - r.fRight, rect.fTop - r.fTop); break;
tomhudson@google.com0c00f212011-12-28 14:59:50 +0000789 default: SkDEBUGFAIL("unexpected startAngle in add_corner_arc");
reed@android.com8a1c16f2008-12-17 15:59:43 +0000790 }
reed@google.comabf15c12011-01-18 20:35:51 +0000791
reed@android.com8a1c16f2008-12-17 15:59:43 +0000792 SkScalar start = SkIntToScalar(startAngle);
793 SkScalar sweep = SkIntToScalar(90);
794 if (SkPath::kCCW_Direction == dir) {
795 start += sweep;
796 sweep = -sweep;
797 }
reed@google.comabf15c12011-01-18 20:35:51 +0000798
reed@android.com8a1c16f2008-12-17 15:59:43 +0000799 path->arcTo(r, start, sweep, forceMoveTo);
800}
801
802void SkPath::addRoundRect(const SkRect& rect, const SkScalar rad[],
803 Direction dir) {
reed@google.com44b2c732011-01-18 20:55:57 +0000804 // abort before we invoke SkAutoPathBoundsUpdate()
805 if (rect.isEmpty()) {
806 return;
807 }
808
reed@android.com8a1c16f2008-12-17 15:59:43 +0000809 SkAutoPathBoundsUpdate apbu(this, rect);
810
811 if (kCW_Direction == dir) {
812 add_corner_arc(this, rect, rad[0], rad[1], 180, dir, true);
813 add_corner_arc(this, rect, rad[2], rad[3], 270, dir, false);
814 add_corner_arc(this, rect, rad[4], rad[5], 0, dir, false);
815 add_corner_arc(this, rect, rad[6], rad[7], 90, dir, false);
816 } else {
817 add_corner_arc(this, rect, rad[0], rad[1], 180, dir, true);
818 add_corner_arc(this, rect, rad[6], rad[7], 90, dir, false);
819 add_corner_arc(this, rect, rad[4], rad[5], 0, dir, false);
820 add_corner_arc(this, rect, rad[2], rad[3], 270, dir, false);
821 }
822 this->close();
823}
824
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000825bool SkPath::hasOnlyMoveTos() const {
826 const uint8_t* verbs = fVerbs.begin();
827 const uint8_t* verbStop = fVerbs.end();
828 while (verbs != verbStop) {
829 if (*verbs == kLine_Verb ||
830 *verbs == kQuad_Verb ||
831 *verbs == kCubic_Verb) {
832 return false;
833 }
834 ++verbs;
835 }
836 return true;
837}
838
reed@android.com8a1c16f2008-12-17 15:59:43 +0000839void SkPath::addOval(const SkRect& oval, Direction dir) {
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000840 /* If addOval() is called after previous moveTo(),
841 this path is still marked as an oval. This is used to
842 fit into WebKit's calling sequences.
843 We can't simply check isEmpty() in this case, as additional
844 moveTo() would mark the path non empty.
845 */
846 fIsOval = hasOnlyMoveTos();
847
848 SkAutoDisableOvalCheck adoc(this);
849
reed@android.com8a1c16f2008-12-17 15:59:43 +0000850 SkAutoPathBoundsUpdate apbu(this, oval);
851
852 SkScalar cx = oval.centerX();
853 SkScalar cy = oval.centerY();
854 SkScalar rx = SkScalarHalf(oval.width());
855 SkScalar ry = SkScalarHalf(oval.height());
856#if 0 // these seem faster than using quads (1/2 the number of edges)
857 SkScalar sx = SkScalarMul(rx, CUBIC_ARC_FACTOR);
858 SkScalar sy = SkScalarMul(ry, CUBIC_ARC_FACTOR);
859
860 this->incReserve(13);
861 this->moveTo(cx + rx, cy);
862 if (dir == kCCW_Direction) {
863 this->cubicTo(cx + rx, cy - sy, cx + sx, cy - ry, cx, cy - ry);
864 this->cubicTo(cx - sx, cy - ry, cx - rx, cy - sy, cx - rx, cy);
865 this->cubicTo(cx - rx, cy + sy, cx - sx, cy + ry, cx, cy + ry);
866 this->cubicTo(cx + sx, cy + ry, cx + rx, cy + sy, cx + rx, cy);
867 } else {
868 this->cubicTo(cx + rx, cy + sy, cx + sx, cy + ry, cx, cy + ry);
869 this->cubicTo(cx - sx, cy + ry, cx - rx, cy + sy, cx - rx, cy);
870 this->cubicTo(cx - rx, cy - sy, cx - sx, cy - ry, cx, cy - ry);
871 this->cubicTo(cx + sx, cy - ry, cx + rx, cy - sy, cx + rx, cy);
872 }
873#else
874 SkScalar sx = SkScalarMul(rx, SK_ScalarTanPIOver8);
875 SkScalar sy = SkScalarMul(ry, SK_ScalarTanPIOver8);
876 SkScalar mx = SkScalarMul(rx, SK_ScalarRoot2Over2);
877 SkScalar my = SkScalarMul(ry, SK_ScalarRoot2Over2);
878
879 /*
880 To handle imprecision in computing the center and radii, we revert to
881 the provided bounds when we can (i.e. use oval.fLeft instead of cx-rx)
882 to ensure that we don't exceed the oval's bounds *ever*, since we want
883 to use oval for our fast-bounds, rather than have to recompute it.
884 */
885 const SkScalar L = oval.fLeft; // cx - rx
886 const SkScalar T = oval.fTop; // cy - ry
887 const SkScalar R = oval.fRight; // cx + rx
888 const SkScalar B = oval.fBottom; // cy + ry
889
890 this->incReserve(17); // 8 quads + close
891 this->moveTo(R, cy);
892 if (dir == kCCW_Direction) {
893 this->quadTo( R, cy - sy, cx + mx, cy - my);
894 this->quadTo(cx + sx, T, cx , T);
895 this->quadTo(cx - sx, T, cx - mx, cy - my);
896 this->quadTo( L, cy - sy, L, cy );
897 this->quadTo( L, cy + sy, cx - mx, cy + my);
898 this->quadTo(cx - sx, B, cx , B);
899 this->quadTo(cx + sx, B, cx + mx, cy + my);
900 this->quadTo( R, cy + sy, R, cy );
901 } else {
902 this->quadTo( R, cy + sy, cx + mx, cy + my);
903 this->quadTo(cx + sx, B, cx , B);
904 this->quadTo(cx - sx, B, cx - mx, cy + my);
905 this->quadTo( L, cy + sy, L, cy );
906 this->quadTo( L, cy - sy, cx - mx, cy - my);
907 this->quadTo(cx - sx, T, cx , T);
908 this->quadTo(cx + sx, T, cx + mx, cy - my);
909 this->quadTo( R, cy - sy, R, cy );
910 }
911#endif
912 this->close();
913}
914
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000915bool SkPath::isOval(SkRect* rect) const {
916 if (fIsOval && rect) {
917 *rect = getBounds();
918 }
919
920 return fIsOval;
921}
922
reed@android.com8a1c16f2008-12-17 15:59:43 +0000923void SkPath::addCircle(SkScalar x, SkScalar y, SkScalar r, Direction dir) {
924 if (r > 0) {
925 SkRect rect;
926 rect.set(x - r, y - r, x + r, y + r);
927 this->addOval(rect, dir);
928 }
929}
930
931#include "SkGeometry.h"
932
933static int build_arc_points(const SkRect& oval, SkScalar startAngle,
934 SkScalar sweepAngle,
935 SkPoint pts[kSkBuildQuadArcStorage]) {
robertphillips@google.com17bb4582012-08-20 17:24:16 +0000936
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000937 if (0 == sweepAngle &&
robertphillips@google.com17bb4582012-08-20 17:24:16 +0000938 (0 == startAngle || SkIntToScalar(360) == startAngle)) {
939 // Chrome uses this path to move into and out of ovals. If not
940 // treated as a special case the moves can distort the oval's
941 // bounding box (and break the circle special case).
942 pts[0].set(oval.fRight, oval.centerY());
943 return 1;
944 }
945
reed@android.com8a1c16f2008-12-17 15:59:43 +0000946 SkVector start, stop;
947
948 start.fY = SkScalarSinCos(SkDegreesToRadians(startAngle), &start.fX);
949 stop.fY = SkScalarSinCos(SkDegreesToRadians(startAngle + sweepAngle),
950 &stop.fX);
reed@android.comeebf5cb2010-02-09 18:30:59 +0000951
952 /* If the sweep angle is nearly (but less than) 360, then due to precision
953 loss in radians-conversion and/or sin/cos, we may end up with coincident
954 vectors, which will fool SkBuildQuadArc into doing nothing (bad) instead
955 of drawing a nearly complete circle (good).
956 e.g. canvas.drawArc(0, 359.99, ...)
957 -vs- canvas.drawArc(0, 359.9, ...)
958 We try to detect this edge case, and tweak the stop vector
959 */
960 if (start == stop) {
961 SkScalar sw = SkScalarAbs(sweepAngle);
962 if (sw < SkIntToScalar(360) && sw > SkIntToScalar(359)) {
963 SkScalar stopRad = SkDegreesToRadians(startAngle + sweepAngle);
964 // make a guess at a tiny angle (in radians) to tweak by
965 SkScalar deltaRad = SkScalarCopySign(SK_Scalar1/512, sweepAngle);
966 // not sure how much will be enough, so we use a loop
967 do {
968 stopRad -= deltaRad;
969 stop.fY = SkScalarSinCos(stopRad, &stop.fX);
970 } while (start == stop);
971 }
972 }
973
reed@android.com8a1c16f2008-12-17 15:59:43 +0000974 SkMatrix matrix;
reed@google.comabf15c12011-01-18 20:35:51 +0000975
reed@android.com8a1c16f2008-12-17 15:59:43 +0000976 matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height()));
977 matrix.postTranslate(oval.centerX(), oval.centerY());
reed@google.comabf15c12011-01-18 20:35:51 +0000978
reed@android.com8a1c16f2008-12-17 15:59:43 +0000979 return SkBuildQuadArc(start, stop,
980 sweepAngle > 0 ? kCW_SkRotationDirection : kCCW_SkRotationDirection,
981 &matrix, pts);
982}
983
984void SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
985 bool forceMoveTo) {
986 if (oval.width() < 0 || oval.height() < 0) {
987 return;
988 }
989
990 SkPoint pts[kSkBuildQuadArcStorage];
991 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
992 SkASSERT((count & 1) == 1);
993
994 if (fVerbs.count() == 0) {
995 forceMoveTo = true;
996 }
997 this->incReserve(count);
998 forceMoveTo ? this->moveTo(pts[0]) : this->lineTo(pts[0]);
999 for (int i = 1; i < count; i += 2) {
1000 this->quadTo(pts[i], pts[i+1]);
1001 }
1002}
1003
1004void SkPath::addArc(const SkRect& oval, SkScalar startAngle,
1005 SkScalar sweepAngle) {
1006 if (oval.isEmpty() || 0 == sweepAngle) {
1007 return;
1008 }
1009
1010 const SkScalar kFullCircleAngle = SkIntToScalar(360);
1011
1012 if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
1013 this->addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction);
1014 return;
1015 }
1016
1017 SkPoint pts[kSkBuildQuadArcStorage];
1018 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
1019
1020 this->incReserve(count);
1021 this->moveTo(pts[0]);
1022 for (int i = 1; i < count; i += 2) {
1023 this->quadTo(pts[i], pts[i+1]);
1024 }
1025}
1026
1027/*
1028 Need to handle the case when the angle is sharp, and our computed end-points
1029 for the arc go behind pt1 and/or p2...
1030*/
1031void SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
1032 SkScalar radius) {
1033 SkVector before, after;
reed@google.comabf15c12011-01-18 20:35:51 +00001034
reed@android.com8a1c16f2008-12-17 15:59:43 +00001035 // need to know our prev pt so we can construct tangent vectors
1036 {
1037 SkPoint start;
1038 this->getLastPt(&start);
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001039 // Handle degenerate cases by adding a line to the first point and
1040 // bailing out.
1041 if ((x1 == start.fX && y1 == start.fY) ||
1042 (x1 == x2 && y1 == y2) ||
1043 radius == 0) {
1044 this->lineTo(x1, y1);
1045 return;
1046 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001047 before.setNormalize(x1 - start.fX, y1 - start.fY);
1048 after.setNormalize(x2 - x1, y2 - y1);
1049 }
reed@google.comabf15c12011-01-18 20:35:51 +00001050
reed@android.com8a1c16f2008-12-17 15:59:43 +00001051 SkScalar cosh = SkPoint::DotProduct(before, after);
1052 SkScalar sinh = SkPoint::CrossProduct(before, after);
1053
1054 if (SkScalarNearlyZero(sinh)) { // angle is too tight
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001055 this->lineTo(x1, y1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001056 return;
1057 }
reed@google.comabf15c12011-01-18 20:35:51 +00001058
reed@android.com8a1c16f2008-12-17 15:59:43 +00001059 SkScalar dist = SkScalarMulDiv(radius, SK_Scalar1 - cosh, sinh);
1060 if (dist < 0) {
1061 dist = -dist;
1062 }
1063
1064 SkScalar xx = x1 - SkScalarMul(dist, before.fX);
1065 SkScalar yy = y1 - SkScalarMul(dist, before.fY);
1066 SkRotationDirection arcDir;
1067
1068 // now turn before/after into normals
1069 if (sinh > 0) {
1070 before.rotateCCW();
1071 after.rotateCCW();
1072 arcDir = kCW_SkRotationDirection;
1073 } else {
1074 before.rotateCW();
1075 after.rotateCW();
1076 arcDir = kCCW_SkRotationDirection;
1077 }
1078
1079 SkMatrix matrix;
1080 SkPoint pts[kSkBuildQuadArcStorage];
reed@google.comabf15c12011-01-18 20:35:51 +00001081
reed@android.com8a1c16f2008-12-17 15:59:43 +00001082 matrix.setScale(radius, radius);
1083 matrix.postTranslate(xx - SkScalarMul(radius, before.fX),
1084 yy - SkScalarMul(radius, before.fY));
reed@google.comabf15c12011-01-18 20:35:51 +00001085
reed@android.com8a1c16f2008-12-17 15:59:43 +00001086 int count = SkBuildQuadArc(before, after, arcDir, &matrix, pts);
reed@google.comabf15c12011-01-18 20:35:51 +00001087
reed@android.com8a1c16f2008-12-17 15:59:43 +00001088 this->incReserve(count);
1089 // [xx,yy] == pts[0]
1090 this->lineTo(xx, yy);
1091 for (int i = 1; i < count; i += 2) {
1092 this->quadTo(pts[i], pts[i+1]);
1093 }
1094}
1095
1096///////////////////////////////////////////////////////////////////////////////
1097
1098void SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy) {
1099 SkMatrix matrix;
1100
1101 matrix.setTranslate(dx, dy);
1102 this->addPath(path, matrix);
1103}
1104
1105void SkPath::addPath(const SkPath& path, const SkMatrix& matrix) {
1106 this->incReserve(path.fPts.count());
1107
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001108 fIsOval = false;
1109
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001110 RawIter iter(path);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001111 SkPoint pts[4];
1112 Verb verb;
1113
1114 SkMatrix::MapPtsProc proc = matrix.getMapPtsProc();
1115
1116 while ((verb = iter.next(pts)) != kDone_Verb) {
1117 switch (verb) {
1118 case kMove_Verb:
1119 proc(matrix, &pts[0], &pts[0], 1);
1120 this->moveTo(pts[0]);
1121 break;
1122 case kLine_Verb:
1123 proc(matrix, &pts[1], &pts[1], 1);
1124 this->lineTo(pts[1]);
1125 break;
1126 case kQuad_Verb:
1127 proc(matrix, &pts[1], &pts[1], 2);
1128 this->quadTo(pts[1], pts[2]);
1129 break;
1130 case kCubic_Verb:
1131 proc(matrix, &pts[1], &pts[1], 3);
1132 this->cubicTo(pts[1], pts[2], pts[3]);
1133 break;
1134 case kClose_Verb:
1135 this->close();
1136 break;
1137 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001138 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001139 }
1140 }
1141}
1142
1143///////////////////////////////////////////////////////////////////////////////
1144
1145static const uint8_t gPtsInVerb[] = {
1146 1, // kMove
1147 1, // kLine
1148 2, // kQuad
1149 3, // kCubic
1150 0, // kClose
1151 0 // kDone
1152};
1153
1154// ignore the initial moveto, and stop when the 1st contour ends
1155void SkPath::pathTo(const SkPath& path) {
1156 int i, vcount = path.fVerbs.count();
1157 if (vcount == 0) {
1158 return;
1159 }
1160
1161 this->incReserve(vcount);
1162
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001163 fIsOval = false;
1164
reed@android.com8a1c16f2008-12-17 15:59:43 +00001165 const uint8_t* verbs = path.fVerbs.begin();
1166 const SkPoint* pts = path.fPts.begin() + 1; // 1 for the initial moveTo
1167
1168 SkASSERT(verbs[0] == kMove_Verb);
1169 for (i = 1; i < vcount; i++) {
1170 switch (verbs[i]) {
1171 case kLine_Verb:
1172 this->lineTo(pts[0].fX, pts[0].fY);
1173 break;
1174 case kQuad_Verb:
1175 this->quadTo(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY);
1176 break;
1177 case kCubic_Verb:
1178 this->cubicTo(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY,
1179 pts[2].fX, pts[2].fY);
1180 break;
1181 case kClose_Verb:
1182 return;
1183 }
1184 pts += gPtsInVerb[verbs[i]];
1185 }
1186}
1187
1188// ignore the last point of the 1st contour
1189void SkPath::reversePathTo(const SkPath& path) {
1190 int i, vcount = path.fVerbs.count();
1191 if (vcount == 0) {
1192 return;
1193 }
1194
1195 this->incReserve(vcount);
1196
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001197 fIsOval = false;
1198
reed@android.com8a1c16f2008-12-17 15:59:43 +00001199 const uint8_t* verbs = path.fVerbs.begin();
1200 const SkPoint* pts = path.fPts.begin();
1201
1202 SkASSERT(verbs[0] == kMove_Verb);
1203 for (i = 1; i < vcount; i++) {
1204 int n = gPtsInVerb[verbs[i]];
1205 if (n == 0) {
1206 break;
1207 }
1208 pts += n;
1209 }
1210
1211 while (--i > 0) {
1212 switch (verbs[i]) {
1213 case kLine_Verb:
1214 this->lineTo(pts[-1].fX, pts[-1].fY);
1215 break;
1216 case kQuad_Verb:
1217 this->quadTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY);
1218 break;
1219 case kCubic_Verb:
1220 this->cubicTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY,
1221 pts[-3].fX, pts[-3].fY);
1222 break;
1223 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001224 SkDEBUGFAIL("bad verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001225 break;
1226 }
1227 pts -= gPtsInVerb[verbs[i]];
1228 }
1229}
1230
reed@google.com63d73742012-01-10 15:33:12 +00001231void SkPath::reverseAddPath(const SkPath& src) {
1232 this->incReserve(src.fPts.count());
1233
reed@google.com63d73742012-01-10 15:33:12 +00001234 const SkPoint* pts = src.fPts.end();
1235 const uint8_t* startVerbs = src.fVerbs.begin();
1236 const uint8_t* verbs = src.fVerbs.end();
1237
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001238 fIsOval = false;
1239
reed@google.com63d73742012-01-10 15:33:12 +00001240 bool needMove = true;
1241 bool needClose = false;
1242 while (verbs > startVerbs) {
1243 uint8_t v = *--verbs;
1244 int n = gPtsInVerb[v];
1245
1246 if (needMove) {
1247 --pts;
1248 this->moveTo(pts->fX, pts->fY);
1249 needMove = false;
1250 }
1251 pts -= n;
1252 switch (v) {
1253 case kMove_Verb:
1254 if (needClose) {
1255 this->close();
1256 needClose = false;
1257 }
1258 needMove = true;
1259 pts += 1; // so we see the point in "if (needMove)" above
1260 break;
1261 case kLine_Verb:
1262 this->lineTo(pts[0]);
1263 break;
1264 case kQuad_Verb:
1265 this->quadTo(pts[1], pts[0]);
1266 break;
1267 case kCubic_Verb:
1268 this->cubicTo(pts[2], pts[1], pts[0]);
1269 break;
1270 case kClose_Verb:
1271 needClose = true;
1272 break;
1273 default:
1274 SkASSERT(!"unexpected verb");
1275 }
1276 }
1277}
1278
reed@android.com8a1c16f2008-12-17 15:59:43 +00001279///////////////////////////////////////////////////////////////////////////////
1280
1281void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
1282 SkMatrix matrix;
1283
1284 matrix.setTranslate(dx, dy);
1285 this->transform(matrix, dst);
1286}
1287
1288#include "SkGeometry.h"
1289
1290static void subdivide_quad_to(SkPath* path, const SkPoint pts[3],
1291 int level = 2) {
1292 if (--level >= 0) {
1293 SkPoint tmp[5];
1294
1295 SkChopQuadAtHalf(pts, tmp);
1296 subdivide_quad_to(path, &tmp[0], level);
1297 subdivide_quad_to(path, &tmp[2], level);
1298 } else {
1299 path->quadTo(pts[1], pts[2]);
1300 }
1301}
1302
1303static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
1304 int level = 2) {
1305 if (--level >= 0) {
1306 SkPoint tmp[7];
1307
1308 SkChopCubicAtHalf(pts, tmp);
1309 subdivide_cubic_to(path, &tmp[0], level);
1310 subdivide_cubic_to(path, &tmp[3], level);
1311 } else {
1312 path->cubicTo(pts[1], pts[2], pts[3]);
1313 }
1314}
1315
1316void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
1317 SkDEBUGCODE(this->validate();)
1318 if (dst == NULL) {
1319 dst = (SkPath*)this;
1320 }
1321
tomhudson@google.com8d430182011-06-06 19:11:19 +00001322 if (matrix.hasPerspective()) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001323 SkPath tmp;
1324 tmp.fFillType = fFillType;
1325
1326 SkPath::Iter iter(*this, false);
1327 SkPoint pts[4];
1328 SkPath::Verb verb;
1329
reed@google.com4a3b7142012-05-16 17:16:46 +00001330 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001331 switch (verb) {
1332 case kMove_Verb:
1333 tmp.moveTo(pts[0]);
1334 break;
1335 case kLine_Verb:
1336 tmp.lineTo(pts[1]);
1337 break;
1338 case kQuad_Verb:
1339 subdivide_quad_to(&tmp, pts);
1340 break;
1341 case kCubic_Verb:
1342 subdivide_cubic_to(&tmp, pts);
1343 break;
1344 case kClose_Verb:
1345 tmp.close();
1346 break;
1347 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001348 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001349 break;
1350 }
1351 }
1352
1353 dst->swap(tmp);
1354 matrix.mapPoints(dst->fPts.begin(), dst->fPts.count());
1355 } else {
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00001356 /*
1357 * If we're not in perspective, we can transform all of the points at
1358 * once.
1359 *
1360 * Here we also want to optimize bounds, by noting if the bounds are
1361 * already known, and if so, we just transform those as well and mark
1362 * them as "known", rather than force the transformed path to have to
1363 * recompute them.
1364 *
1365 * Special gotchas if the path is effectively empty (<= 1 point) or
1366 * if it is non-finite. In those cases bounds need to stay empty,
1367 * regardless of the matrix.
1368 */
reed@android.comd252db02009-04-01 18:31:44 +00001369 if (!fBoundsIsDirty && matrix.rectStaysRect() && fPts.count() > 1) {
reed@android.comd252db02009-04-01 18:31:44 +00001370 dst->fBoundsIsDirty = false;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00001371 if (fIsFinite) {
1372 matrix.mapRect(&dst->fBounds, fBounds);
1373 if (!(dst->fIsFinite = dst->fBounds.isFinite())) {
1374 dst->fBounds.setEmpty();
1375 }
1376 } else {
1377 dst->fIsFinite = false;
1378 dst->fBounds.setEmpty();
1379 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001380 } else {
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +00001381 GEN_ID_PTR_INC(dst);
reed@android.comd252db02009-04-01 18:31:44 +00001382 dst->fBoundsIsDirty = true;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001383 }
1384
1385 if (this != dst) {
1386 dst->fVerbs = fVerbs;
1387 dst->fPts.setCount(fPts.count());
1388 dst->fFillType = fFillType;
reed@google.comb3b0a5b2011-09-21 12:58:51 +00001389 dst->fSegmentMask = fSegmentMask;
reed@google.com2a6f8ab2011-10-25 18:41:23 +00001390 dst->fConvexity = fConvexity;
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001391 dst->fIsOval = fIsOval;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001392 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001393
reed@android.com8a1c16f2008-12-17 15:59:43 +00001394 matrix.mapPoints(dst->fPts.begin(), fPts.begin(), fPts.count());
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001395
1396 if (fIsOval) {
1397 // It's an oval only if it stays a rect.
1398 dst->fIsOval = matrix.rectStaysRect();
1399 }
1400
reed@android.com8a1c16f2008-12-17 15:59:43 +00001401 SkDEBUGCODE(dst->validate();)
1402 }
1403}
1404
reed@android.com8a1c16f2008-12-17 15:59:43 +00001405///////////////////////////////////////////////////////////////////////////////
1406///////////////////////////////////////////////////////////////////////////////
1407
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001408enum SegmentState {
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001409 kEmptyContour_SegmentState, // The current contour is empty. We may be
1410 // starting processing or we may have just
1411 // closed a contour.
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001412 kAfterMove_SegmentState, // We have seen a move, but nothing else.
1413 kAfterPrimitive_SegmentState // We have seen a primitive but not yet
1414 // closed the path. Also the initial state.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001415};
1416
1417SkPath::Iter::Iter() {
1418#ifdef SK_DEBUG
1419 fPts = NULL;
1420 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001421 fForceClose = fCloseLine = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001422 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001423#endif
1424 // need to init enough to make next() harmlessly return kDone_Verb
1425 fVerbs = NULL;
1426 fVerbStop = NULL;
1427 fNeedClose = false;
1428}
1429
1430SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
1431 this->setPath(path, forceClose);
1432}
1433
1434void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
1435 fPts = path.fPts.begin();
1436 fVerbs = path.fVerbs.begin();
1437 fVerbStop = path.fVerbs.end();
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001438 fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org72785c42011-12-29 21:03:28 +00001439 fMoveTo.fX = fMoveTo.fY = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001440 fForceClose = SkToU8(forceClose);
1441 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001442 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001443}
1444
1445bool SkPath::Iter::isClosedContour() const {
1446 if (fVerbs == NULL || fVerbs == fVerbStop) {
1447 return false;
1448 }
1449 if (fForceClose) {
1450 return true;
1451 }
1452
1453 const uint8_t* verbs = fVerbs;
1454 const uint8_t* stop = fVerbStop;
reed@google.comabf15c12011-01-18 20:35:51 +00001455
reed@android.com8a1c16f2008-12-17 15:59:43 +00001456 if (kMove_Verb == *verbs) {
1457 verbs += 1; // skip the initial moveto
1458 }
1459
1460 while (verbs < stop) {
reed@google.comabf15c12011-01-18 20:35:51 +00001461 unsigned v = *verbs++;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001462 if (kMove_Verb == v) {
1463 break;
1464 }
1465 if (kClose_Verb == v) {
1466 return true;
1467 }
1468 }
1469 return false;
1470}
1471
1472SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001473 SkASSERT(pts);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001474 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001475 // A special case: if both points are NaN, SkPoint::operation== returns
1476 // false, but the iterator expects that they are treated as the same.
1477 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001478 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1479 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001480 return kClose_Verb;
1481 }
1482
reed@google.com9e25dbf2012-05-15 17:05:38 +00001483 pts[0] = fLastPt;
1484 pts[1] = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001485 fLastPt = fMoveTo;
1486 fCloseLine = true;
1487 return kLine_Verb;
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +00001488 } else {
1489 pts[0] = fMoveTo;
1490 return kClose_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001491 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001492}
1493
reed@google.com9e25dbf2012-05-15 17:05:38 +00001494const SkPoint& SkPath::Iter::cons_moveTo() {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001495 if (fSegmentState == kAfterMove_SegmentState) {
1496 // Set the first return pt to the move pt
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001497 fSegmentState = kAfterPrimitive_SegmentState;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001498 return fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001499 } else {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001500 SkASSERT(fSegmentState == kAfterPrimitive_SegmentState);
1501 // Set the first return pt to the last pt of the previous primitive.
reed@google.com9e25dbf2012-05-15 17:05:38 +00001502 return fPts[-1];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001503 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001504}
1505
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001506void SkPath::Iter::consumeDegenerateSegments() {
1507 // We need to step over anything that will not move the current draw point
1508 // forward before the next move is seen
1509 const uint8_t* lastMoveVerb = 0;
1510 const SkPoint* lastMovePt = 0;
1511 SkPoint lastPt = fLastPt;
1512 while (fVerbs != fVerbStop) {
1513 unsigned verb = *fVerbs;
1514 switch (verb) {
1515 case kMove_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001516 // Keep a record of this most recent move
1517 lastMoveVerb = fVerbs;
1518 lastMovePt = fPts;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001519 lastPt = fPts[0];
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001520 fVerbs++;
1521 fPts++;
1522 break;
1523
1524 case kClose_Verb:
schenney@chromium.org7e963602012-06-13 17:05:43 +00001525 // A close when we are in a segment is always valid except when it
1526 // follows a move which follows a segment.
1527 if (fSegmentState == kAfterPrimitive_SegmentState && !lastMoveVerb) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001528 return;
1529 }
1530 // A close at any other time must be ignored
1531 fVerbs++;
1532 break;
1533
1534 case kLine_Verb:
1535 if (!IsLineDegenerate(lastPt, fPts[0])) {
1536 if (lastMoveVerb) {
1537 fVerbs = lastMoveVerb;
1538 fPts = lastMovePt;
1539 return;
1540 }
1541 return;
1542 }
1543 // Ignore this line and continue
1544 fVerbs++;
1545 fPts++;
1546 break;
1547
1548 case kQuad_Verb:
1549 if (!IsQuadDegenerate(lastPt, fPts[0], fPts[1])) {
1550 if (lastMoveVerb) {
1551 fVerbs = lastMoveVerb;
1552 fPts = lastMovePt;
1553 return;
1554 }
1555 return;
1556 }
1557 // Ignore this line and continue
1558 fVerbs++;
1559 fPts += 2;
1560 break;
1561
1562 case kCubic_Verb:
1563 if (!IsCubicDegenerate(lastPt, fPts[0], fPts[1], fPts[2])) {
1564 if (lastMoveVerb) {
1565 fVerbs = lastMoveVerb;
1566 fPts = lastMovePt;
1567 return;
1568 }
1569 return;
1570 }
1571 // Ignore this line and continue
1572 fVerbs++;
1573 fPts += 3;
1574 break;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001575
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001576 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001577 SkDEBUGFAIL("Should never see kDone_Verb");
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001578 }
1579 }
1580}
1581
reed@google.com4a3b7142012-05-16 17:16:46 +00001582SkPath::Verb SkPath::Iter::doNext(SkPoint ptsParam[4]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001583 SkASSERT(ptsParam);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001584
reed@android.com8a1c16f2008-12-17 15:59:43 +00001585 if (fVerbs == fVerbStop) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001586 // Close the curve if requested and if there is some curve to close
1587 if (fNeedClose && fSegmentState == kAfterPrimitive_SegmentState) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001588 if (kLine_Verb == this->autoClose(ptsParam)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001589 return kLine_Verb;
1590 }
1591 fNeedClose = false;
1592 return kClose_Verb;
1593 }
1594 return kDone_Verb;
1595 }
1596
1597 unsigned verb = *fVerbs++;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001598 const SkPoint* SK_RESTRICT srcPts = fPts;
1599 SkPoint* SK_RESTRICT pts = ptsParam;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001600
1601 switch (verb) {
1602 case kMove_Verb:
1603 if (fNeedClose) {
1604 fVerbs -= 1;
1605 verb = this->autoClose(pts);
1606 if (verb == kClose_Verb) {
1607 fNeedClose = false;
1608 }
1609 return (Verb)verb;
1610 }
1611 if (fVerbs == fVerbStop) { // might be a trailing moveto
1612 return kDone_Verb;
1613 }
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001614 fMoveTo = *srcPts;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001615 pts[0] = *srcPts;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001616 srcPts += 1;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001617 fSegmentState = kAfterMove_SegmentState;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001618 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001619 fNeedClose = fForceClose;
1620 break;
1621 case kLine_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001622 pts[0] = this->cons_moveTo();
1623 pts[1] = srcPts[0];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001624 fLastPt = srcPts[0];
1625 fCloseLine = false;
1626 srcPts += 1;
1627 break;
1628 case kQuad_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001629 pts[0] = this->cons_moveTo();
1630 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001631 fLastPt = srcPts[1];
1632 srcPts += 2;
1633 break;
1634 case kCubic_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001635 pts[0] = this->cons_moveTo();
1636 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001637 fLastPt = srcPts[2];
1638 srcPts += 3;
1639 break;
1640 case kClose_Verb:
1641 verb = this->autoClose(pts);
1642 if (verb == kLine_Verb) {
1643 fVerbs -= 1;
1644 } else {
1645 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001646 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001647 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001648 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001649 break;
1650 }
1651 fPts = srcPts;
1652 return (Verb)verb;
1653}
1654
1655///////////////////////////////////////////////////////////////////////////////
1656
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001657SkPath::RawIter::RawIter() {
1658#ifdef SK_DEBUG
1659 fPts = NULL;
1660 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
1661#endif
1662 // need to init enough to make next() harmlessly return kDone_Verb
1663 fVerbs = NULL;
1664 fVerbStop = NULL;
1665}
1666
1667SkPath::RawIter::RawIter(const SkPath& path) {
1668 this->setPath(path);
1669}
1670
1671void SkPath::RawIter::setPath(const SkPath& path) {
1672 fPts = path.fPts.begin();
1673 fVerbs = path.fVerbs.begin();
1674 fVerbStop = path.fVerbs.end();
1675 fMoveTo.fX = fMoveTo.fY = 0;
1676 fLastPt.fX = fLastPt.fY = 0;
1677}
1678
1679SkPath::Verb SkPath::RawIter::next(SkPoint pts[4]) {
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001680 SkASSERT(NULL != pts);
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001681 if (fVerbs == fVerbStop) {
1682 return kDone_Verb;
1683 }
1684
1685 unsigned verb = *fVerbs++;
1686 const SkPoint* srcPts = fPts;
1687
1688 switch (verb) {
1689 case kMove_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001690 pts[0] = *srcPts;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001691 fMoveTo = srcPts[0];
1692 fLastPt = fMoveTo;
1693 srcPts += 1;
1694 break;
1695 case kLine_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001696 pts[0] = fLastPt;
1697 pts[1] = srcPts[0];
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001698 fLastPt = srcPts[0];
1699 srcPts += 1;
1700 break;
1701 case kQuad_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001702 pts[0] = fLastPt;
1703 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001704 fLastPt = srcPts[1];
1705 srcPts += 2;
1706 break;
1707 case kCubic_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001708 pts[0] = fLastPt;
1709 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001710 fLastPt = srcPts[2];
1711 srcPts += 3;
1712 break;
1713 case kClose_Verb:
1714 fLastPt = fMoveTo;
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001715 pts[0] = fMoveTo;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001716 break;
1717 }
1718 fPts = srcPts;
1719 return (Verb)verb;
1720}
1721
1722///////////////////////////////////////////////////////////////////////////////
1723
reed@android.com8a1c16f2008-12-17 15:59:43 +00001724/*
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001725 Format in compressed buffer: [ptCount, verbCount, pts[], verbs[]]
reed@android.com8a1c16f2008-12-17 15:59:43 +00001726*/
1727
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001728uint32_t SkPath::writeToMemory(void* storage) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001729 SkDEBUGCODE(this->validate();)
1730
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001731 if (NULL == storage) {
1732 const int byteCount = 3 * sizeof(int32_t)
1733 + sizeof(SkPoint) * fPts.count()
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001734 + sizeof(uint8_t) * fVerbs.count()
1735 + sizeof(SkRect);
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001736 return SkAlign4(byteCount);
1737 }
1738
1739 SkWBuffer buffer(storage);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001740 buffer.write32(fPts.count());
1741 buffer.write32(fVerbs.count());
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001742
1743 // Call getBounds() to ensure (as a side-effect) that fBounds
1744 // and fIsFinite are computed.
1745 const SkRect& bounds = this->getBounds();
1746 SkASSERT(!fBoundsIsDirty);
1747
1748 int32_t packed = ((fIsFinite & 1) << kIsFinite_SerializationShift) |
1749 ((fIsOval & 1) << kIsOval_SerializationShift) |
1750 (fConvexity << kConvexity_SerializationShift) |
1751 (fFillType << kFillType_SerializationShift) |
1752 (fSegmentMask << kSegmentMask_SerializationShift);
1753
robertphillips@google.com2972bb52012-08-07 17:32:51 +00001754 buffer.write32(packed);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001755
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001756 buffer.write(fPts.begin(), sizeof(SkPoint) * fPts.count());
1757 buffer.write(fVerbs.begin(), fVerbs.count());
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001758
1759 buffer.write(&bounds, sizeof(bounds));
1760
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001761 buffer.padToAlign4();
1762 return buffer.pos();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001763}
1764
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001765uint32_t SkPath::readFromMemory(const void* storage) {
1766 SkRBuffer buffer(storage);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001767 fPts.setCount(buffer.readS32());
1768 fVerbs.setCount(buffer.readS32());
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001769
reed@google.com98b11f12011-09-21 18:40:27 +00001770 uint32_t packed = buffer.readS32();
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001771 fIsFinite = (packed >> kIsFinite_SerializationShift) & 1;
1772 fIsOval = (packed >> kIsOval_SerializationShift) & 1;
1773 fConvexity = (packed >> kConvexity_SerializationShift) & 0xFF;
1774 fFillType = (packed >> kFillType_SerializationShift) & 0xFF;
1775 fSegmentMask = (packed >> kSegmentMask_SerializationShift) & 0xFF;
1776
reed@android.com8a1c16f2008-12-17 15:59:43 +00001777 buffer.read(fPts.begin(), sizeof(SkPoint) * fPts.count());
1778 buffer.read(fVerbs.begin(), fVerbs.count());
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001779
1780 buffer.read(&fBounds, sizeof(fBounds));
1781 fBoundsIsDirty = false;
1782
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001783 buffer.skipToAlign4();
reed@google.comabf15c12011-01-18 20:35:51 +00001784
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +00001785 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001786
1787 SkDEBUGCODE(this->validate();)
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001788 return buffer.pos();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001789}
1790
1791///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00001792
reed@android.com8a1c16f2008-12-17 15:59:43 +00001793void SkPath::dump(bool forceClose, const char title[]) const {
1794 Iter iter(*this, forceClose);
1795 SkPoint pts[4];
1796 Verb verb;
1797
1798 SkDebugf("path: forceClose=%s %s\n", forceClose ? "true" : "false",
1799 title ? title : "");
1800
reed@google.com4a3b7142012-05-16 17:16:46 +00001801 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001802 switch (verb) {
1803 case kMove_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +00001804 SkDebugf(" path: moveTo [%g %g]\n",
1805 SkScalarToFloat(pts[0].fX), SkScalarToFloat(pts[0].fY));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001806 break;
1807 case kLine_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +00001808 SkDebugf(" path: lineTo [%g %g]\n",
1809 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001810 break;
1811 case kQuad_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +00001812 SkDebugf(" path: quadTo [%g %g] [%g %g]\n",
1813 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY),
1814 SkScalarToFloat(pts[2].fX), SkScalarToFloat(pts[2].fY));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001815 break;
1816 case kCubic_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +00001817 SkDebugf(" path: cubeTo [%g %g] [%g %g] [%g %g]\n",
1818 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY),
1819 SkScalarToFloat(pts[2].fX), SkScalarToFloat(pts[2].fY),
1820 SkScalarToFloat(pts[3].fX), SkScalarToFloat(pts[3].fY));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001821 break;
1822 case kClose_Verb:
1823 SkDebugf(" path: close\n");
1824 break;
1825 default:
1826 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
1827 verb = kDone_Verb; // stop the loop
1828 break;
1829 }
1830 }
1831 SkDebugf("path: done %s\n", title ? title : "");
1832}
1833
reed@android.come522ca52009-11-23 20:10:41 +00001834void SkPath::dump() const {
1835 this->dump(false);
1836}
1837
1838#ifdef SK_DEBUG
1839void SkPath::validate() const {
1840 SkASSERT(this != NULL);
1841 SkASSERT((fFillType & ~3) == 0);
1842 fPts.validate();
1843 fVerbs.validate();
reed@google.comabf15c12011-01-18 20:35:51 +00001844
reed@android.come522ca52009-11-23 20:10:41 +00001845 if (!fBoundsIsDirty) {
1846 SkRect bounds;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00001847
1848 bool isFinite = compute_pt_bounds(&bounds, fPts);
robertphillips@google.com5d8d1862012-08-15 14:36:41 +00001849 SkASSERT(SkToBool(fIsFinite) == isFinite);
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00001850
reed@android.come522ca52009-11-23 20:10:41 +00001851 if (fPts.count() <= 1) {
1852 // if we're empty, fBounds may be empty but translated, so we can't
1853 // necessarily compare to bounds directly
1854 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
1855 // be [2, 2, 2, 2]
1856 SkASSERT(bounds.isEmpty());
1857 SkASSERT(fBounds.isEmpty());
1858 } else {
reed@google.comeac52bd2011-11-14 18:13:59 +00001859 if (bounds.isEmpty()) {
1860 SkASSERT(fBounds.isEmpty());
1861 } else {
reed@google.com3563c9e2011-11-14 19:34:57 +00001862 if (!fBounds.isEmpty()) {
1863 SkASSERT(fBounds.contains(bounds));
1864 }
reed@google.comeac52bd2011-11-14 18:13:59 +00001865 }
reed@android.come522ca52009-11-23 20:10:41 +00001866 }
1867 }
reed@google.com10296cc2011-09-21 12:29:05 +00001868
1869 uint32_t mask = 0;
1870 for (int i = 0; i < fVerbs.count(); i++) {
1871 switch (fVerbs[i]) {
1872 case kLine_Verb:
1873 mask |= kLine_SegmentMask;
1874 break;
1875 case kQuad_Verb:
1876 mask |= kQuad_SegmentMask;
1877 break;
1878 case kCubic_Verb:
1879 mask |= kCubic_SegmentMask;
1880 }
1881 }
1882 SkASSERT(mask == fSegmentMask);
reed@android.come522ca52009-11-23 20:10:41 +00001883}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001884#endif
reed@android.come522ca52009-11-23 20:10:41 +00001885
reed@google.com04863fa2011-05-15 04:08:24 +00001886///////////////////////////////////////////////////////////////////////////////
1887
reed@google.com0b7b9822011-05-16 12:29:27 +00001888static int sign(SkScalar x) { return x < 0; }
1889#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00001890
reed@google.com04863fa2011-05-15 04:08:24 +00001891static int CrossProductSign(const SkVector& a, const SkVector& b) {
bsalomon@google.com647a8042011-08-23 14:39:01 +00001892 return SkScalarSignAsInt(SkPoint::CrossProduct(a, b));
reed@google.com04863fa2011-05-15 04:08:24 +00001893}
1894
1895// only valid for a single contour
1896struct Convexicator {
reed@google.comb54455e2011-05-16 14:16:04 +00001897 Convexicator() : fPtCount(0), fConvexity(SkPath::kConvex_Convexity) {
reed@google.com04863fa2011-05-15 04:08:24 +00001898 fSign = 0;
1899 // warnings
1900 fCurrPt.set(0, 0);
1901 fVec0.set(0, 0);
1902 fVec1.set(0, 0);
1903 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00001904
1905 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00001906 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00001907 }
1908
1909 SkPath::Convexity getConvexity() const { return fConvexity; }
1910
1911 void addPt(const SkPoint& pt) {
1912 if (SkPath::kConcave_Convexity == fConvexity) {
1913 return;
1914 }
1915
1916 if (0 == fPtCount) {
1917 fCurrPt = pt;
1918 ++fPtCount;
1919 } else {
1920 SkVector vec = pt - fCurrPt;
1921 if (vec.fX || vec.fY) {
1922 fCurrPt = pt;
1923 if (++fPtCount == 2) {
1924 fFirstVec = fVec1 = vec;
1925 } else {
1926 SkASSERT(fPtCount > 2);
1927 this->addVec(vec);
1928 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00001929
reed@google.com85b6e392011-05-15 20:25:17 +00001930 int sx = sign(vec.fX);
1931 int sy = sign(vec.fY);
1932 fDx += (sx != fSx);
1933 fDy += (sy != fSy);
1934 fSx = sx;
1935 fSy = sy;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00001936
reed@google.com85b6e392011-05-15 20:25:17 +00001937 if (fDx > 3 || fDy > 3) {
1938 fConvexity = SkPath::kConcave_Convexity;
1939 }
reed@google.com04863fa2011-05-15 04:08:24 +00001940 }
1941 }
1942 }
1943
1944 void close() {
1945 if (fPtCount > 2) {
1946 this->addVec(fFirstVec);
1947 }
1948 }
1949
1950private:
1951 void addVec(const SkVector& vec) {
1952 SkASSERT(vec.fX || vec.fY);
1953 fVec0 = fVec1;
1954 fVec1 = vec;
1955 int sign = CrossProductSign(fVec0, fVec1);
1956 if (0 == fSign) {
1957 fSign = sign;
1958 } else if (sign) {
reed@google.comb54455e2011-05-16 14:16:04 +00001959 if (fSign != sign) {
reed@google.com04863fa2011-05-15 04:08:24 +00001960 fConvexity = SkPath::kConcave_Convexity;
1961 }
1962 }
1963 }
1964
1965 SkPoint fCurrPt;
1966 SkVector fVec0, fVec1, fFirstVec;
1967 int fPtCount; // non-degenerate points
1968 int fSign;
1969 SkPath::Convexity fConvexity;
reed@google.com0b7b9822011-05-16 12:29:27 +00001970 int fDx, fDy, fSx, fSy;
reed@google.com04863fa2011-05-15 04:08:24 +00001971};
1972
1973SkPath::Convexity SkPath::ComputeConvexity(const SkPath& path) {
1974 SkPoint pts[4];
1975 SkPath::Verb verb;
1976 SkPath::Iter iter(path, true);
1977
1978 int contourCount = 0;
1979 int count;
1980 Convexicator state;
1981
1982 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
1983 switch (verb) {
1984 case kMove_Verb:
1985 if (++contourCount > 1) {
1986 return kConcave_Convexity;
1987 }
1988 pts[1] = pts[0];
1989 count = 1;
1990 break;
1991 case kLine_Verb: count = 1; break;
1992 case kQuad_Verb: count = 2; break;
1993 case kCubic_Verb: count = 3; break;
1994 case kClose_Verb:
1995 state.close();
1996 count = 0;
1997 break;
1998 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001999 SkDEBUGFAIL("bad verb");
reed@google.com04863fa2011-05-15 04:08:24 +00002000 return kConcave_Convexity;
2001 }
2002
2003 for (int i = 1; i <= count; i++) {
2004 state.addPt(pts[i]);
2005 }
2006 // early exit
2007 if (kConcave_Convexity == state.getConvexity()) {
2008 return kConcave_Convexity;
2009 }
2010 }
2011 return state.getConvexity();
2012}
reed@google.com69a99432012-01-10 18:00:10 +00002013
2014///////////////////////////////////////////////////////////////////////////////
2015
2016class ContourIter {
2017public:
2018 ContourIter(const SkTDArray<uint8_t>& verbs, const SkTDArray<SkPoint>& pts);
2019
2020 bool done() const { return fDone; }
2021 // if !done() then these may be called
2022 int count() const { return fCurrPtCount; }
2023 const SkPoint* pts() const { return fCurrPt; }
2024 void next();
2025
2026private:
2027 int fCurrPtCount;
2028 const SkPoint* fCurrPt;
2029 const uint8_t* fCurrVerb;
2030 const uint8_t* fStopVerbs;
2031 bool fDone;
reed@google.comd1ab9322012-01-10 18:40:03 +00002032 SkDEBUGCODE(int fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002033};
2034
2035ContourIter::ContourIter(const SkTDArray<uint8_t>& verbs,
2036 const SkTDArray<SkPoint>& pts) {
2037 fStopVerbs = verbs.begin() + verbs.count();
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002038
reed@google.com69a99432012-01-10 18:00:10 +00002039 fDone = false;
2040 fCurrPt = pts.begin();
2041 fCurrVerb = verbs.begin();
2042 fCurrPtCount = 0;
reed@google.comd1ab9322012-01-10 18:40:03 +00002043 SkDEBUGCODE(fContourCounter = 0;)
reed@google.com69a99432012-01-10 18:00:10 +00002044 this->next();
2045}
2046
2047void ContourIter::next() {
2048 if (fCurrVerb >= fStopVerbs) {
2049 fDone = true;
2050 }
2051 if (fDone) {
2052 return;
2053 }
2054
2055 // skip pts of prev contour
2056 fCurrPt += fCurrPtCount;
2057
2058 SkASSERT(SkPath::kMove_Verb == fCurrVerb[0]);
2059 int ptCount = 1; // moveTo
2060 const uint8_t* verbs = fCurrVerb;
2061
2062 for (++verbs; verbs < fStopVerbs; ++verbs) {
2063 switch (*verbs) {
2064 case SkPath::kMove_Verb:
reed@google.com69a99432012-01-10 18:00:10 +00002065 goto CONTOUR_END;
2066 case SkPath::kLine_Verb:
2067 ptCount += 1;
2068 break;
2069 case SkPath::kQuad_Verb:
2070 ptCount += 2;
2071 break;
2072 case SkPath::kCubic_Verb:
2073 ptCount += 3;
2074 break;
2075 default: // kClose_Verb, just keep going
2076 break;
2077 }
2078 }
2079CONTOUR_END:
2080 fCurrPtCount = ptCount;
2081 fCurrVerb = verbs;
reed@google.comd1ab9322012-01-10 18:40:03 +00002082 SkDEBUGCODE(++fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002083}
2084
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002085// returns cross product of (p1 - p0) and (p2 - p0)
reed@google.com69a99432012-01-10 18:00:10 +00002086static SkScalar cross_prod(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002087 SkScalar cross = SkPoint::CrossProduct(p1 - p0, p2 - p0);
2088 // We may get 0 when the above subtracts underflow. We expect this to be
2089 // very rare and lazily promote to double.
2090 if (0 == cross) {
2091 double p0x = SkScalarToDouble(p0.fX);
2092 double p0y = SkScalarToDouble(p0.fY);
2093
2094 double p1x = SkScalarToDouble(p1.fX);
2095 double p1y = SkScalarToDouble(p1.fY);
2096
2097 double p2x = SkScalarToDouble(p2.fX);
2098 double p2y = SkScalarToDouble(p2.fY);
2099
2100 cross = SkDoubleToScalar((p1x - p0x) * (p2y - p0y) -
2101 (p1y - p0y) * (p2x - p0x));
2102
2103 }
2104 return cross;
reed@google.com69a99432012-01-10 18:00:10 +00002105}
2106
reed@google.comc1ea60a2012-01-31 15:15:36 +00002107// Returns the first pt with the maximum Y coordinate
reed@google.com69a99432012-01-10 18:00:10 +00002108static int find_max_y(const SkPoint pts[], int count) {
2109 SkASSERT(count > 0);
2110 SkScalar max = pts[0].fY;
reed@google.comc1ea60a2012-01-31 15:15:36 +00002111 int firstIndex = 0;
reed@google.com69a99432012-01-10 18:00:10 +00002112 for (int i = 1; i < count; ++i) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002113 SkScalar y = pts[i].fY;
2114 if (y > max) {
2115 max = y;
2116 firstIndex = i;
reed@google.com69a99432012-01-10 18:00:10 +00002117 }
2118 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002119 return firstIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002120}
2121
reed@google.comcabaf1d2012-01-11 21:03:05 +00002122static int find_diff_pt(const SkPoint pts[], int index, int n, int inc) {
2123 int i = index;
2124 for (;;) {
2125 i = (i + inc) % n;
2126 if (i == index) { // we wrapped around, so abort
2127 break;
2128 }
2129 if (pts[index] != pts[i]) { // found a different point, success!
2130 break;
2131 }
2132 }
2133 return i;
2134}
2135
reed@google.comc1ea60a2012-01-31 15:15:36 +00002136/**
2137 * Starting at index, and moving forward (incrementing), find the xmin and
2138 * xmax of the contiguous points that have the same Y.
2139 */
2140static int find_min_max_x_at_y(const SkPoint pts[], int index, int n,
2141 int* maxIndexPtr) {
2142 const SkScalar y = pts[index].fY;
2143 SkScalar min = pts[index].fX;
2144 SkScalar max = min;
2145 int minIndex = index;
2146 int maxIndex = index;
2147 for (int i = index + 1; i < n; ++i) {
2148 if (pts[i].fY != y) {
2149 break;
2150 }
2151 SkScalar x = pts[i].fX;
2152 if (x < min) {
2153 min = x;
2154 minIndex = i;
2155 } else if (x > max) {
2156 max = x;
2157 maxIndex = i;
2158 }
2159 }
2160 *maxIndexPtr = maxIndex;
2161 return minIndex;
2162}
2163
reed@google.comac8543f2012-01-30 20:51:25 +00002164static bool crossToDir(SkScalar cross, SkPath::Direction* dir) {
2165 if (dir) {
2166 *dir = cross > 0 ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
2167 }
2168 return true;
2169}
2170
reed@google.comc1ea60a2012-01-31 15:15:36 +00002171#if 0
2172#include "SkString.h"
2173#include "../utils/SkParsePath.h"
2174static void dumpPath(const SkPath& path) {
2175 SkString str;
2176 SkParsePath::ToSVGString(path, &str);
2177 SkDebugf("%s\n", str.c_str());
2178}
2179#endif
2180
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002181namespace {
2182// for use with convex_dir_test
2183double mul(double a, double b) { return a * b; }
2184SkScalar mul(SkScalar a, SkScalar b) { return SkScalarMul(a, b); }
2185double toDouble(SkScalar a) { return SkScalarToDouble(a); }
2186SkScalar toScalar(SkScalar a) { return a; }
2187
2188// determines the winding direction of a convex polygon with the precision
2189// of T. CAST_SCALAR casts an SkScalar to T.
2190template <typename T, T (CAST_SCALAR)(SkScalar)>
2191bool convex_dir_test(int n, const SkPoint pts[], SkPath::Direction* dir) {
2192 // we find the first three points that form a non-degenerate
2193 // triangle. If there are no such points then the path is
2194 // degenerate. The first is always point 0. Now we find the second
2195 // point.
2196 int i = 0;
2197 enum { kX = 0, kY = 1 };
2198 T v0[2];
2199 while (1) {
2200 v0[kX] = CAST_SCALAR(pts[i].fX) - CAST_SCALAR(pts[0].fX);
2201 v0[kY] = CAST_SCALAR(pts[i].fY) - CAST_SCALAR(pts[0].fY);
2202 if (v0[kX] || v0[kY]) {
2203 break;
2204 }
2205 if (++i == n - 1) {
2206 return false;
2207 }
2208 }
2209 // now find a third point that is not colinear with the first two
2210 // points and check the orientation of the triangle (which will be
2211 // the same as the orientation of the path).
2212 for (++i; i < n; ++i) {
2213 T v1[2];
2214 v1[kX] = CAST_SCALAR(pts[i].fX) - CAST_SCALAR(pts[0].fX);
2215 v1[kY] = CAST_SCALAR(pts[i].fY) - CAST_SCALAR(pts[0].fY);
2216 T cross = mul(v0[kX], v1[kY]) - mul(v0[kY], v1[kX]);
2217 if (0 != cross) {
2218 *dir = cross > 0 ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
2219 return true;
2220 }
2221 }
2222 return false;
2223}
2224}
2225
reed@google.comac8543f2012-01-30 20:51:25 +00002226/*
2227 * We loop through all contours, and keep the computed cross-product of the
2228 * contour that contained the global y-max. If we just look at the first
2229 * contour, we may find one that is wound the opposite way (correctly) since
2230 * it is the interior of a hole (e.g. 'o'). Thus we must find the contour
2231 * that is outer most (or at least has the global y-max) before we can consider
2232 * its cross product.
2233 */
reed@google.com69a99432012-01-10 18:00:10 +00002234bool SkPath::cheapComputeDirection(Direction* dir) const {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002235// dumpPath(*this);
reed@google.com69a99432012-01-10 18:00:10 +00002236 // don't want to pay the cost for computing this if it
2237 // is unknown, so we don't call isConvex()
2238 const Convexity conv = this->getConvexityOrUnknown();
2239
2240 ContourIter iter(fVerbs, fPts);
2241
reed@google.comac8543f2012-01-30 20:51:25 +00002242 // initialize with our logical y-min
2243 SkScalar ymax = this->getBounds().fTop;
2244 SkScalar ymaxCross = 0;
2245
reed@google.com69a99432012-01-10 18:00:10 +00002246 for (; !iter.done(); iter.next()) {
2247 int n = iter.count();
reed@google.comcabaf1d2012-01-11 21:03:05 +00002248 if (n < 3) {
2249 continue;
2250 }
djsollen@google.come63793a2012-03-21 15:39:03 +00002251
reed@google.comcabaf1d2012-01-11 21:03:05 +00002252 const SkPoint* pts = iter.pts();
reed@google.com69a99432012-01-10 18:00:10 +00002253 SkScalar cross = 0;
2254 if (kConvex_Convexity == conv) {
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002255 // We try first at scalar precision, and then again at double
2256 // precision. This is because the vectors computed between distant
2257 // points may lose too much precision.
2258 if (convex_dir_test<SkScalar, toScalar>(n, pts, dir)) {
2259 return true;
2260 }
2261 if (convex_dir_test<double, toDouble>(n, pts, dir)) {
2262 return true;
2263 } else {
2264 return false;
reed@google.com69a99432012-01-10 18:00:10 +00002265 }
2266 } else {
reed@google.comcabaf1d2012-01-11 21:03:05 +00002267 int index = find_max_y(pts, n);
reed@google.comac8543f2012-01-30 20:51:25 +00002268 if (pts[index].fY < ymax) {
2269 continue;
2270 }
2271
reed@google.comc1ea60a2012-01-31 15:15:36 +00002272 // If there is more than 1 distinct point at the y-max, we take the
2273 // x-min and x-max of them and just subtract to compute the dir.
2274 if (pts[(index + 1) % n].fY == pts[index].fY) {
2275 int maxIndex;
2276 int minIndex = find_min_max_x_at_y(pts, index, n, &maxIndex);
reed@google.com3e44e4d2012-01-31 15:25:22 +00002277 if (minIndex == maxIndex) {
2278 goto TRY_CROSSPROD;
2279 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002280 SkASSERT(pts[minIndex].fY == pts[index].fY);
2281 SkASSERT(pts[maxIndex].fY == pts[index].fY);
2282 SkASSERT(pts[minIndex].fX <= pts[maxIndex].fX);
2283 // we just subtract the indices, and let that auto-convert to
2284 // SkScalar, since we just want - or + to signal the direction.
2285 cross = minIndex - maxIndex;
2286 } else {
reed@google.com3e44e4d2012-01-31 15:25:22 +00002287 TRY_CROSSPROD:
reed@google.comc1ea60a2012-01-31 15:15:36 +00002288 // Find a next and prev index to use for the cross-product test,
2289 // but we try to find pts that form non-zero vectors from pts[index]
2290 //
2291 // Its possible that we can't find two non-degenerate vectors, so
2292 // we have to guard our search (e.g. all the pts could be in the
2293 // same place).
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002294
reed@google.comc1ea60a2012-01-31 15:15:36 +00002295 // we pass n - 1 instead of -1 so we don't foul up % operator by
2296 // passing it a negative LH argument.
2297 int prev = find_diff_pt(pts, index, n, n - 1);
2298 if (prev == index) {
2299 // completely degenerate, skip to next contour
2300 continue;
2301 }
2302 int next = find_diff_pt(pts, index, n, 1);
2303 SkASSERT(next != index);
2304 cross = cross_prod(pts[prev], pts[index], pts[next]);
2305 // if we get a zero, but the pts aren't on top of each other, then
2306 // we can just look at the direction
2307 if (0 == cross) {
2308 // construct the subtract so we get the correct Direction below
2309 cross = pts[index].fX - pts[next].fX;
2310 }
reed@google.com188bfcf2012-01-17 18:26:38 +00002311 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002312
reed@google.comac8543f2012-01-30 20:51:25 +00002313 if (cross) {
2314 // record our best guess so far
2315 ymax = pts[index].fY;
2316 ymaxCross = cross;
reed@google.com69a99432012-01-10 18:00:10 +00002317 }
reed@google.com69a99432012-01-10 18:00:10 +00002318 }
2319 }
reed@google.com69a99432012-01-10 18:00:10 +00002320
reed@google.comac8543f2012-01-30 20:51:25 +00002321 return ymaxCross ? crossToDir(ymaxCross, dir) : false;
2322}
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002323
2324///////////////////////////////////////////////////////////////////////////////
2325
2326static SkScalar eval_cubic_coeff(SkScalar A, SkScalar B, SkScalar C,
2327 SkScalar D, SkScalar t) {
2328 return SkScalarMulAdd(SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C), t, D);
2329}
2330
2331static SkScalar eval_cubic_pts(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2332 SkScalar t) {
2333 SkScalar A = c3 + 3*(c1 - c2) - c0;
2334 SkScalar B = 3*(c2 - c1 - c1 + c0);
2335 SkScalar C = 3*(c1 - c0);
2336 SkScalar D = c0;
2337 return eval_cubic_coeff(A, B, C, D, t);
2338}
2339
2340/* Given 4 cubic points (either Xs or Ys), and a target X or Y, compute the
2341 t value such that cubic(t) = target
2342 */
2343static bool chopMonoCubicAt(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2344 SkScalar target, SkScalar* t) {
2345 // SkASSERT(c0 <= c1 && c1 <= c2 && c2 <= c3);
2346 SkASSERT(c0 < target && target < c3);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002347
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002348 SkScalar D = c0 - target;
2349 SkScalar A = c3 + 3*(c1 - c2) - c0;
2350 SkScalar B = 3*(c2 - c1 - c1 + c0);
2351 SkScalar C = 3*(c1 - c0);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002352
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002353 const SkScalar TOLERANCE = SK_Scalar1 / 4096;
2354 SkScalar minT = 0;
2355 SkScalar maxT = SK_Scalar1;
2356 SkScalar mid;
2357 int i;
2358 for (i = 0; i < 16; i++) {
2359 mid = SkScalarAve(minT, maxT);
2360 SkScalar delta = eval_cubic_coeff(A, B, C, D, mid);
2361 if (delta < 0) {
2362 minT = mid;
2363 delta = -delta;
2364 } else {
2365 maxT = mid;
2366 }
2367 if (delta < TOLERANCE) {
2368 break;
2369 }
2370 }
2371 *t = mid;
2372 return true;
2373}
2374
2375template <size_t N> static void find_minmax(const SkPoint pts[],
2376 SkScalar* minPtr, SkScalar* maxPtr) {
2377 SkScalar min, max;
2378 min = max = pts[0].fX;
2379 for (size_t i = 1; i < N; ++i) {
2380 min = SkMinScalar(min, pts[i].fX);
2381 max = SkMaxScalar(max, pts[i].fX);
2382 }
2383 *minPtr = min;
2384 *maxPtr = max;
2385}
2386
2387static int winding_mono_cubic(const SkPoint pts[], SkScalar x, SkScalar y) {
2388 SkPoint storage[4];
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002389
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002390 int dir = 1;
2391 if (pts[0].fY > pts[3].fY) {
2392 storage[0] = pts[3];
2393 storage[1] = pts[2];
2394 storage[2] = pts[1];
2395 storage[3] = pts[0];
2396 pts = storage;
2397 dir = -1;
2398 }
2399 if (y < pts[0].fY || y >= pts[3].fY) {
2400 return 0;
2401 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002402
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002403 // quickreject or quickaccept
2404 SkScalar min, max;
2405 find_minmax<4>(pts, &min, &max);
2406 if (x < min) {
2407 return 0;
2408 }
2409 if (x > max) {
2410 return dir;
2411 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002412
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002413 // compute the actual x(t) value
2414 SkScalar t, xt;
2415 if (chopMonoCubicAt(pts[0].fY, pts[1].fY, pts[2].fY, pts[3].fY, y, &t)) {
2416 xt = eval_cubic_pts(pts[0].fX, pts[1].fX, pts[2].fX, pts[3].fX, t);
2417 } else {
2418 SkScalar mid = SkScalarAve(pts[0].fY, pts[3].fY);
2419 xt = y < mid ? pts[0].fX : pts[3].fX;
2420 }
2421 return xt < x ? dir : 0;
2422}
2423
2424static int winding_cubic(const SkPoint pts[], SkScalar x, SkScalar y) {
2425 SkPoint dst[10];
2426 int n = SkChopCubicAtYExtrema(pts, dst);
2427 int w = 0;
2428 for (int i = 0; i <= n; ++i) {
2429 w += winding_mono_cubic(&dst[i * 3], x, y);
2430 }
2431 return w;
2432}
2433
2434static int winding_mono_quad(const SkPoint pts[], SkScalar x, SkScalar y) {
2435 SkScalar y0 = pts[0].fY;
2436 SkScalar y2 = pts[2].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002437
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002438 int dir = 1;
2439 if (y0 > y2) {
2440 SkTSwap(y0, y2);
2441 dir = -1;
2442 }
2443 if (y < y0 || y >= y2) {
2444 return 0;
2445 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002446
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002447 // bounds check on X (not required. is it faster?)
2448#if 0
2449 if (pts[0].fX > x && pts[1].fX > x && pts[2].fX > x) {
2450 return 0;
2451 }
2452#endif
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002453
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002454 SkScalar roots[2];
2455 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
2456 2 * (pts[1].fY - pts[0].fY),
2457 pts[0].fY - y,
2458 roots);
2459 SkASSERT(n <= 1);
2460 SkScalar xt;
2461 if (0 == n) {
2462 SkScalar mid = SkScalarAve(y0, y2);
2463 // Need [0] and [2] if dir == 1
2464 // and [2] and [0] if dir == -1
2465 xt = y < mid ? pts[1 - dir].fX : pts[dir - 1].fX;
2466 } else {
2467 SkScalar t = roots[0];
2468 SkScalar C = pts[0].fX;
2469 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
2470 SkScalar B = 2 * (pts[1].fX - C);
2471 xt = SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C);
2472 }
2473 return xt < x ? dir : 0;
2474}
2475
2476static bool is_mono_quad(SkScalar y0, SkScalar y1, SkScalar y2) {
2477 // return SkScalarSignAsInt(y0 - y1) + SkScalarSignAsInt(y1 - y2) != 0;
2478 if (y0 == y1) {
2479 return true;
2480 }
2481 if (y0 < y1) {
2482 return y1 <= y2;
2483 } else {
2484 return y1 >= y2;
2485 }
2486}
2487
2488static int winding_quad(const SkPoint pts[], SkScalar x, SkScalar y) {
2489 SkPoint dst[5];
2490 int n = 0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002491
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002492 if (!is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY)) {
2493 n = SkChopQuadAtYExtrema(pts, dst);
2494 pts = dst;
2495 }
2496 int w = winding_mono_quad(pts, x, y);
2497 if (n > 0) {
2498 w += winding_mono_quad(&pts[2], x, y);
2499 }
2500 return w;
2501}
2502
2503static int winding_line(const SkPoint pts[], SkScalar x, SkScalar y) {
2504 SkScalar x0 = pts[0].fX;
2505 SkScalar y0 = pts[0].fY;
2506 SkScalar x1 = pts[1].fX;
2507 SkScalar y1 = pts[1].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002508
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002509 SkScalar dy = y1 - y0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002510
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002511 int dir = 1;
2512 if (y0 > y1) {
2513 SkTSwap(y0, y1);
2514 dir = -1;
2515 }
2516 if (y < y0 || y >= y1) {
2517 return 0;
2518 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002519
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002520 SkScalar cross = SkScalarMul(x1 - x0, y - pts[0].fY) -
2521 SkScalarMul(dy, x - pts[0].fX);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002522
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002523 if (SkScalarSignAsInt(cross) == dir) {
2524 dir = 0;
2525 }
2526 return dir;
2527}
2528
2529bool SkPath::contains(SkScalar x, SkScalar y) const {
2530 bool isInverse = this->isInverseFillType();
2531 if (this->isEmpty()) {
2532 return isInverse;
2533 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002534
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002535 const SkRect& bounds = this->getBounds();
2536 if (!bounds.contains(x, y)) {
2537 return isInverse;
2538 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002539
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002540 SkPath::Iter iter(*this, true);
2541 bool done = false;
2542 int w = 0;
2543 do {
2544 SkPoint pts[4];
2545 switch (iter.next(pts, false)) {
2546 case SkPath::kMove_Verb:
2547 case SkPath::kClose_Verb:
2548 break;
2549 case SkPath::kLine_Verb:
2550 w += winding_line(pts, x, y);
2551 break;
2552 case SkPath::kQuad_Verb:
2553 w += winding_quad(pts, x, y);
2554 break;
2555 case SkPath::kCubic_Verb:
2556 w += winding_cubic(pts, x, y);
2557 break;
2558 case SkPath::kDone_Verb:
2559 done = true;
2560 break;
2561 }
2562 } while (!done);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002563
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002564 switch (this->getFillType()) {
2565 case SkPath::kEvenOdd_FillType:
2566 case SkPath::kInverseEvenOdd_FillType:
2567 w &= 1;
2568 break;
reed@google.come9bb6232012-07-11 18:56:10 +00002569 default:
2570 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002571 }
2572 return SkToBool(w);
2573}
2574