blob: e0d5b69cb8c7b3121a8c7572ec7d4a2fc3eacff8 [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"
reed@google.com73945652011-04-25 19:04:27 +000011#include "SkReader32.h"
12#include "SkWriter32.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000013#include "SkMath.h"
14
15////////////////////////////////////////////////////////////////////////////
16
17/* This guy's constructor/destructor bracket a path editing operation. It is
18 used when we know the bounds of the amount we are going to add to the path
19 (usually a new contour, but not required).
reed@google.comabf15c12011-01-18 20:35:51 +000020
reed@android.com8a1c16f2008-12-17 15:59:43 +000021 It captures some state about the path up front (i.e. if it already has a
22 cached bounds), and the if it can, it updates the cache bounds explicitly,
reed@android.comd252db02009-04-01 18:31:44 +000023 avoiding the need to revisit all of the points in getBounds().
reed@google.comabf15c12011-01-18 20:35:51 +000024
reed@android.com6b82d1a2009-06-03 02:35:01 +000025 It also notes if the path was originally empty, and if so, sets isConvex
26 to true. Thus it can only be used if the contour being added is convex.
reed@android.com8a1c16f2008-12-17 15:59:43 +000027 */
28class SkAutoPathBoundsUpdate {
29public:
30 SkAutoPathBoundsUpdate(SkPath* path, const SkRect& r) : fRect(r) {
31 this->init(path);
32 }
33
34 SkAutoPathBoundsUpdate(SkPath* path, SkScalar left, SkScalar top,
35 SkScalar right, SkScalar bottom) {
36 fRect.set(left, top, right, bottom);
37 this->init(path);
38 }
reed@google.comabf15c12011-01-18 20:35:51 +000039
reed@android.com8a1c16f2008-12-17 15:59:43 +000040 ~SkAutoPathBoundsUpdate() {
reed@android.com6b82d1a2009-06-03 02:35:01 +000041 fPath->setIsConvex(fEmpty);
reed@android.com8a1c16f2008-12-17 15:59:43 +000042 if (fEmpty) {
reed@android.comd252db02009-04-01 18:31:44 +000043 fPath->fBounds = fRect;
44 fPath->fBoundsIsDirty = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +000045 } else if (!fDirty) {
reed@android.comd252db02009-04-01 18:31:44 +000046 fPath->fBounds.join(fRect);
47 fPath->fBoundsIsDirty = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +000048 }
49 }
reed@google.comabf15c12011-01-18 20:35:51 +000050
reed@android.com8a1c16f2008-12-17 15:59:43 +000051private:
reed@android.com6b82d1a2009-06-03 02:35:01 +000052 SkPath* fPath;
53 SkRect fRect;
54 bool fDirty;
55 bool fEmpty;
reed@google.comabf15c12011-01-18 20:35:51 +000056
reed@android.com8a1c16f2008-12-17 15:59:43 +000057 // returns true if we should proceed
reed@android.com6b82d1a2009-06-03 02:35:01 +000058 void init(SkPath* path) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000059 fPath = path;
reed@android.com63debae2009-12-16 17:25:43 +000060 fDirty = SkToBool(path->fBoundsIsDirty);
reed@android.com8a1c16f2008-12-17 15:59:43 +000061 fEmpty = path->isEmpty();
reed@android.com6c14b432009-03-23 20:11:11 +000062 // Cannot use fRect for our bounds unless we know it is sorted
reed@android.com49f0ff22009-03-19 21:52:42 +000063 fRect.sort();
reed@android.com8a1c16f2008-12-17 15:59:43 +000064 }
65};
66
reed@android.comd252db02009-04-01 18:31:44 +000067static void compute_pt_bounds(SkRect* bounds, const SkTDArray<SkPoint>& pts) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000068 if (pts.count() <= 1) { // we ignore just 1 point (moveto)
69 bounds->set(0, 0, 0, 0);
70 } else {
71 bounds->set(pts.begin(), pts.count());
72// SkDebugf("------- compute bounds %p %d", &pts, pts.count());
73 }
74}
75
76////////////////////////////////////////////////////////////////////////////
77
78/*
79 Stores the verbs and points as they are given to us, with exceptions:
80 - we only record "Close" if it was immediately preceeded by Line | Quad | Cubic
81 - we insert a Move(0,0) if Line | Quad | Cubic is our first command
82
83 The iterator does more cleanup, especially if forceClose == true
84 1. if we encounter Close, return a cons'd up Line() first (if the curr-pt != start-pt)
85 2. if we encounter Move without a preceeding Close, and forceClose is true, goto #1
86 3. if we encounter Line | Quad | Cubic after Close, cons up a Move
87*/
88
89////////////////////////////////////////////////////////////////////////////
90
reed@android.com6b82d1a2009-06-03 02:35:01 +000091SkPath::SkPath() : fBoundsIsDirty(true), fFillType(kWinding_FillType) {
reed@google.com04863fa2011-05-15 04:08:24 +000092 fConvexity = kUnknown_Convexity;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +000093#ifdef ANDROID
94 fGenerationID = 0;
95#endif
reed@android.com6b82d1a2009-06-03 02:35:01 +000096}
reed@android.com8a1c16f2008-12-17 15:59:43 +000097
98SkPath::SkPath(const SkPath& src) {
99 SkDEBUGCODE(src.validate();)
100 *this = src;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000101#ifdef ANDROID
102 // the assignment operator above increments the ID so correct for that here
103 fGenerationID--;
104#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000105}
106
107SkPath::~SkPath() {
108 SkDEBUGCODE(this->validate();)
109}
110
111SkPath& SkPath::operator=(const SkPath& src) {
112 SkDEBUGCODE(src.validate();)
113
114 if (this != &src) {
reed@android.comd252db02009-04-01 18:31:44 +0000115 fBounds = src.fBounds;
116 fPts = src.fPts;
117 fVerbs = src.fVerbs;
118 fFillType = src.fFillType;
119 fBoundsIsDirty = src.fBoundsIsDirty;
reed@google.com04863fa2011-05-15 04:08:24 +0000120 fConvexity = src.fConvexity;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000121 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000122 }
123 SkDEBUGCODE(this->validate();)
124 return *this;
125}
126
reed@android.com3abec1d2009-03-02 05:36:20 +0000127bool operator==(const SkPath& a, const SkPath& b) {
reed@android.com6b82d1a2009-06-03 02:35:01 +0000128 // note: don't need to look at isConvex or bounds, since just comparing the
129 // raw data is sufficient.
reed@android.com3abec1d2009-03-02 05:36:20 +0000130 return &a == &b ||
131 (a.fFillType == b.fFillType && a.fVerbs == b.fVerbs && a.fPts == b.fPts);
132}
133
reed@android.com8a1c16f2008-12-17 15:59:43 +0000134void SkPath::swap(SkPath& other) {
135 SkASSERT(&other != NULL);
136
137 if (this != &other) {
reed@android.comd252db02009-04-01 18:31:44 +0000138 SkTSwap<SkRect>(fBounds, other.fBounds);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000139 fPts.swap(other.fPts);
140 fVerbs.swap(other.fVerbs);
141 SkTSwap<uint8_t>(fFillType, other.fFillType);
reed@android.comd252db02009-04-01 18:31:44 +0000142 SkTSwap<uint8_t>(fBoundsIsDirty, other.fBoundsIsDirty);
reed@google.com04863fa2011-05-15 04:08:24 +0000143 SkTSwap<uint8_t>(fConvexity, other.fConvexity);
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000144 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000145 }
146}
147
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000148#ifdef ANDROID
149uint32_t SkPath::getGenerationID() const {
150 return fGenerationID;
151}
152#endif
153
reed@android.com8a1c16f2008-12-17 15:59:43 +0000154void SkPath::reset() {
155 SkDEBUGCODE(this->validate();)
156
157 fPts.reset();
158 fVerbs.reset();
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000159 GEN_ID_INC;
reed@android.comd252db02009-04-01 18:31:44 +0000160 fBoundsIsDirty = true;
reed@google.com04863fa2011-05-15 04:08:24 +0000161 fConvexity = kUnknown_Convexity;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000162}
163
164void SkPath::rewind() {
165 SkDEBUGCODE(this->validate();)
166
167 fPts.rewind();
168 fVerbs.rewind();
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000169 GEN_ID_INC;
reed@android.comd252db02009-04-01 18:31:44 +0000170 fBoundsIsDirty = true;
reed@google.com04863fa2011-05-15 04:08:24 +0000171 fConvexity = kUnknown_Convexity;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000172}
173
174bool SkPath::isEmpty() const {
175 SkDEBUGCODE(this->validate();)
176
177 int count = fVerbs.count();
178 return count == 0 || (count == 1 && fVerbs[0] == kMove_Verb);
179}
180
caryclark@google.comf1316942011-07-26 19:54:45 +0000181/*
182 Determines if path is a rect by keeping track of changes in direction
183 and looking for a loop either clockwise or counterclockwise.
184
185 The direction is computed such that:
186 0: vertical up
187 1: horizontal right
188 2: vertical down
189 3: horizontal left
190
191A rectangle cycles up/right/down/left or up/left/down/right.
192
193The test fails if:
194 The path is closed, and followed by a line.
195 A second move creates a new endpoint.
196 A diagonal line is parsed.
197 There's more than four changes of direction.
198 There's a discontinuity on the line (e.g., a move in the middle)
199 The line reverses direction.
200 The rectangle doesn't complete a cycle.
201 The path contains a quadratic or cubic.
202 The path contains fewer than four points.
203 The final point isn't equal to the first point.
204
205It's OK if the path has:
206 Several colinear line segments composing a rectangle side.
207 Single points on the rectangle side.
208
209The direction takes advantage of the corners found since opposite sides
210must travel in opposite directions.
211
212FIXME: Allow colinear quads and cubics to be treated like lines.
213FIXME: If the API passes fill-only, return true if the filled stroke
214 is a rectangle, though the caller failed to close the path.
215 */
216bool SkPath::isRect(SkRect* rect) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000217 SkDEBUGCODE(this->validate();)
reed@google.comabf15c12011-01-18 20:35:51 +0000218
caryclark@google.comf1316942011-07-26 19:54:45 +0000219 int corners = 0;
220 SkPoint first, last;
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000221 first.set(0, 0);
222 last.set(0, 0);
223 int firstDirection = 0;
224 int lastDirection = 0;
225 int nextDirection = 0;
226 bool closedOrMoved = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000227 bool autoClose = false;
228 const uint8_t* verbs = fVerbs.begin();
229 const uint8_t* verbStop = fVerbs.end();
230 const SkPoint* pts = fPts.begin();
231 while (verbs != verbStop) {
232 switch (*verbs++) {
233 case kClose_Verb:
234 pts = fPts.begin();
235 autoClose = true;
236 case kLine_Verb: {
237 SkScalar left = last.fX;
238 SkScalar top = last.fY;
239 SkScalar right = pts->fX;
240 SkScalar bottom = pts->fY;
241 ++pts;
242 if (left != right && top != bottom) {
243 return false; // diagonal
244 }
245 if (left == right && top == bottom) {
246 break; // single point on side OK
247 }
248 nextDirection = (left != right) << 0 |
249 (left < right || top < bottom) << 1;
250 if (0 == corners) {
251 firstDirection = nextDirection;
252 first = last;
253 last = pts[-1];
254 corners = 1;
255 closedOrMoved = false;
256 break;
257 }
258 if (closedOrMoved) {
259 return false; // closed followed by a line
260 }
261 closedOrMoved = autoClose;
262 if (lastDirection != nextDirection) {
263 if (++corners > 4) {
264 return false; // too many direction changes
265 }
266 }
267 last = pts[-1];
268 if (lastDirection == nextDirection) {
269 break; // colinear segment
270 }
271 // Possible values for corners are 2, 3, and 4.
272 // When corners == 3, nextDirection opposes firstDirection.
273 // Otherwise, nextDirection at corner 2 opposes corner 4.
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000274 int turn = firstDirection ^ (corners - 1);
caryclark@google.comf1316942011-07-26 19:54:45 +0000275 int directionCycle = 3 == corners ? 0 : nextDirection ^ turn;
276 if ((directionCycle ^ turn) != nextDirection) {
277 return false; // direction didn't follow cycle
278 }
279 break;
280 }
281 case kQuad_Verb:
282 case kCubic_Verb:
283 return false; // quadratic, cubic not allowed
284 case kMove_Verb:
285 last = *pts++;
286 closedOrMoved = true;
287 break;
288 }
289 lastDirection = nextDirection;
290 }
291 // Success if 4 corners and first point equals last
292 bool result = 4 == corners && first == last;
293 if (result && rect) {
294 *rect = getBounds();
295 }
296 return result;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000297}
298
299int SkPath::getPoints(SkPoint copy[], int max) const {
300 SkDEBUGCODE(this->validate();)
301
302 SkASSERT(max >= 0);
303 int count = fPts.count();
304 if (copy && max > 0 && count > 0) {
305 memcpy(copy, fPts.begin(), sizeof(SkPoint) * SkMin32(max, count));
306 }
307 return count;
308}
309
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000310SkPoint SkPath::getPoint(int index) const {
311 if ((unsigned)index < (unsigned)fPts.count()) {
312 return fPts[index];
313 }
314 return SkPoint::Make(0, 0);
315}
316
reed@android.com8a1c16f2008-12-17 15:59:43 +0000317void SkPath::getLastPt(SkPoint* lastPt) const {
318 SkDEBUGCODE(this->validate();)
319
320 if (lastPt) {
321 int count = fPts.count();
322 if (count == 0) {
323 lastPt->set(0, 0);
324 } else {
325 *lastPt = fPts[count - 1];
326 }
327 }
328}
329
330void SkPath::setLastPt(SkScalar x, SkScalar y) {
331 SkDEBUGCODE(this->validate();)
332
333 int count = fPts.count();
334 if (count == 0) {
335 this->moveTo(x, y);
336 } else {
337 fPts[count - 1].set(x, y);
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000338 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000339 }
340}
341
reed@android.comd252db02009-04-01 18:31:44 +0000342void SkPath::computeBounds() const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000343 SkDEBUGCODE(this->validate();)
reed@android.comd252db02009-04-01 18:31:44 +0000344 SkASSERT(fBoundsIsDirty);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000345
reed@android.comd252db02009-04-01 18:31:44 +0000346 fBoundsIsDirty = false;
347 compute_pt_bounds(&fBounds, fPts);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000348}
349
reed@google.com04863fa2011-05-15 04:08:24 +0000350void SkPath::setConvexity(Convexity c) {
351 if (fConvexity != c) {
352 fConvexity = c;
353 GEN_ID_INC;
354 }
355}
356
reed@android.com8a1c16f2008-12-17 15:59:43 +0000357//////////////////////////////////////////////////////////////////////////////
358// Construction methods
359
reed@google.comb54455e2011-05-16 14:16:04 +0000360#define DIRTY_AFTER_EDIT \
361 do { \
362 fBoundsIsDirty = true; \
363 fConvexity = kUnknown_Convexity;\
364 } while (0)
365
reed@android.com8a1c16f2008-12-17 15:59:43 +0000366void SkPath::incReserve(U16CPU inc) {
367 SkDEBUGCODE(this->validate();)
368
369 fVerbs.setReserve(fVerbs.count() + inc);
370 fPts.setReserve(fPts.count() + inc);
371
372 SkDEBUGCODE(this->validate();)
373}
374
375void SkPath::moveTo(SkScalar x, SkScalar y) {
376 SkDEBUGCODE(this->validate();)
377
378 int vc = fVerbs.count();
379 SkPoint* pt;
380
381 if (vc > 0 && fVerbs[vc - 1] == kMove_Verb) {
382 pt = &fPts[fPts.count() - 1];
383 } else {
384 pt = fPts.append();
385 *fVerbs.append() = kMove_Verb;
386 }
387 pt->set(x, y);
388
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000389 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000390 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000391}
392
393void SkPath::rMoveTo(SkScalar x, SkScalar y) {
394 SkPoint pt;
395 this->getLastPt(&pt);
396 this->moveTo(pt.fX + x, pt.fY + y);
397}
398
399void SkPath::lineTo(SkScalar x, SkScalar y) {
400 SkDEBUGCODE(this->validate();)
401
402 if (fVerbs.count() == 0) {
403 fPts.append()->set(0, 0);
404 *fVerbs.append() = kMove_Verb;
405 }
406 fPts.append()->set(x, y);
407 *fVerbs.append() = kLine_Verb;
408
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000409 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000410 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000411}
412
413void SkPath::rLineTo(SkScalar x, SkScalar y) {
414 SkPoint pt;
415 this->getLastPt(&pt);
416 this->lineTo(pt.fX + x, pt.fY + y);
417}
418
419void SkPath::quadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
420 SkDEBUGCODE(this->validate();)
421
422 if (fVerbs.count() == 0) {
423 fPts.append()->set(0, 0);
424 *fVerbs.append() = kMove_Verb;
425 }
426
427 SkPoint* pts = fPts.append(2);
428 pts[0].set(x1, y1);
429 pts[1].set(x2, y2);
430 *fVerbs.append() = kQuad_Verb;
431
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000432 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000433 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000434}
435
436void SkPath::rQuadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
437 SkPoint pt;
438 this->getLastPt(&pt);
439 this->quadTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2);
440}
441
442void SkPath::cubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
443 SkScalar x3, SkScalar y3) {
444 SkDEBUGCODE(this->validate();)
445
446 if (fVerbs.count() == 0) {
447 fPts.append()->set(0, 0);
448 *fVerbs.append() = kMove_Verb;
449 }
450 SkPoint* pts = fPts.append(3);
451 pts[0].set(x1, y1);
452 pts[1].set(x2, y2);
453 pts[2].set(x3, y3);
454 *fVerbs.append() = kCubic_Verb;
455
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000456 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000457 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000458}
459
460void SkPath::rCubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
461 SkScalar x3, SkScalar y3) {
462 SkPoint pt;
463 this->getLastPt(&pt);
464 this->cubicTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2,
465 pt.fX + x3, pt.fY + y3);
466}
467
468void SkPath::close() {
469 SkDEBUGCODE(this->validate();)
470
471 int count = fVerbs.count();
472 if (count > 0) {
473 switch (fVerbs[count - 1]) {
474 case kLine_Verb:
475 case kQuad_Verb:
476 case kCubic_Verb:
477 *fVerbs.append() = kClose_Verb;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000478 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000479 break;
480 default:
481 // don't add a close if the prev wasn't a primitive
482 break;
483 }
484 }
485}
486
487///////////////////////////////////////////////////////////////////////////////
reed@google.comabf15c12011-01-18 20:35:51 +0000488
reed@android.com8a1c16f2008-12-17 15:59:43 +0000489void SkPath::addRect(const SkRect& rect, Direction dir) {
490 this->addRect(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, dir);
491}
492
493void SkPath::addRect(SkScalar left, SkScalar top, SkScalar right,
494 SkScalar bottom, Direction dir) {
495 SkAutoPathBoundsUpdate apbu(this, left, top, right, bottom);
496
497 this->incReserve(5);
498
499 this->moveTo(left, top);
500 if (dir == kCCW_Direction) {
501 this->lineTo(left, bottom);
502 this->lineTo(right, bottom);
503 this->lineTo(right, top);
504 } else {
505 this->lineTo(right, top);
506 this->lineTo(right, bottom);
507 this->lineTo(left, bottom);
508 }
509 this->close();
510}
511
512#define CUBIC_ARC_FACTOR ((SK_ScalarSqrt2 - SK_Scalar1) * 4 / 3)
513
514void SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
515 Direction dir) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000516 SkScalar w = rect.width();
517 SkScalar halfW = SkScalarHalf(w);
518 SkScalar h = rect.height();
519 SkScalar halfH = SkScalarHalf(h);
520
521 if (halfW <= 0 || halfH <= 0) {
522 return;
523 }
524
reed@google.comabf15c12011-01-18 20:35:51 +0000525 bool skip_hori = rx >= halfW;
526 bool skip_vert = ry >= halfH;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000527
528 if (skip_hori && skip_vert) {
529 this->addOval(rect, dir);
530 return;
531 }
reed@google.comabf15c12011-01-18 20:35:51 +0000532
533 SkAutoPathBoundsUpdate apbu(this, rect);
534
reed@android.com8a1c16f2008-12-17 15:59:43 +0000535 if (skip_hori) {
536 rx = halfW;
537 } else if (skip_vert) {
538 ry = halfH;
539 }
540
541 SkScalar sx = SkScalarMul(rx, CUBIC_ARC_FACTOR);
542 SkScalar sy = SkScalarMul(ry, CUBIC_ARC_FACTOR);
543
544 this->incReserve(17);
545 this->moveTo(rect.fRight - rx, rect.fTop);
546 if (dir == kCCW_Direction) {
547 if (!skip_hori) {
548 this->lineTo(rect.fLeft + rx, rect.fTop); // top
549 }
550 this->cubicTo(rect.fLeft + rx - sx, rect.fTop,
551 rect.fLeft, rect.fTop + ry - sy,
552 rect.fLeft, rect.fTop + ry); // top-left
553 if (!skip_vert) {
554 this->lineTo(rect.fLeft, rect.fBottom - ry); // left
555 }
556 this->cubicTo(rect.fLeft, rect.fBottom - ry + sy,
557 rect.fLeft + rx - sx, rect.fBottom,
558 rect.fLeft + rx, rect.fBottom); // bot-left
559 if (!skip_hori) {
560 this->lineTo(rect.fRight - rx, rect.fBottom); // bottom
561 }
562 this->cubicTo(rect.fRight - rx + sx, rect.fBottom,
563 rect.fRight, rect.fBottom - ry + sy,
564 rect.fRight, rect.fBottom - ry); // bot-right
565 if (!skip_vert) {
566 this->lineTo(rect.fRight, rect.fTop + ry);
567 }
568 this->cubicTo(rect.fRight, rect.fTop + ry - sy,
569 rect.fRight - rx + sx, rect.fTop,
570 rect.fRight - rx, rect.fTop); // top-right
571 } else {
572 this->cubicTo(rect.fRight - rx + sx, rect.fTop,
573 rect.fRight, rect.fTop + ry - sy,
574 rect.fRight, rect.fTop + ry); // top-right
575 if (!skip_vert) {
576 this->lineTo(rect.fRight, rect.fBottom - ry);
577 }
578 this->cubicTo(rect.fRight, rect.fBottom - ry + sy,
579 rect.fRight - rx + sx, rect.fBottom,
580 rect.fRight - rx, rect.fBottom); // bot-right
581 if (!skip_hori) {
582 this->lineTo(rect.fLeft + rx, rect.fBottom); // bottom
583 }
584 this->cubicTo(rect.fLeft + rx - sx, rect.fBottom,
585 rect.fLeft, rect.fBottom - ry + sy,
586 rect.fLeft, rect.fBottom - ry); // bot-left
587 if (!skip_vert) {
588 this->lineTo(rect.fLeft, rect.fTop + ry); // left
589 }
590 this->cubicTo(rect.fLeft, rect.fTop + ry - sy,
591 rect.fLeft + rx - sx, rect.fTop,
592 rect.fLeft + rx, rect.fTop); // top-left
593 if (!skip_hori) {
594 this->lineTo(rect.fRight - rx, rect.fTop); // top
595 }
596 }
597 this->close();
598}
599
600static void add_corner_arc(SkPath* path, const SkRect& rect,
601 SkScalar rx, SkScalar ry, int startAngle,
602 SkPath::Direction dir, bool forceMoveTo) {
603 rx = SkMinScalar(SkScalarHalf(rect.width()), rx);
604 ry = SkMinScalar(SkScalarHalf(rect.height()), ry);
reed@google.comabf15c12011-01-18 20:35:51 +0000605
reed@android.com8a1c16f2008-12-17 15:59:43 +0000606 SkRect r;
607 r.set(-rx, -ry, rx, ry);
608
609 switch (startAngle) {
610 case 0:
611 r.offset(rect.fRight - r.fRight, rect.fBottom - r.fBottom);
612 break;
613 case 90:
614 r.offset(rect.fLeft - r.fLeft, rect.fBottom - r.fBottom);
615 break;
616 case 180: r.offset(rect.fLeft - r.fLeft, rect.fTop - r.fTop); break;
617 case 270: r.offset(rect.fRight - r.fRight, rect.fTop - r.fTop); break;
618 default: SkASSERT(!"unexpected startAngle in add_corner_arc");
619 }
reed@google.comabf15c12011-01-18 20:35:51 +0000620
reed@android.com8a1c16f2008-12-17 15:59:43 +0000621 SkScalar start = SkIntToScalar(startAngle);
622 SkScalar sweep = SkIntToScalar(90);
623 if (SkPath::kCCW_Direction == dir) {
624 start += sweep;
625 sweep = -sweep;
626 }
reed@google.comabf15c12011-01-18 20:35:51 +0000627
reed@android.com8a1c16f2008-12-17 15:59:43 +0000628 path->arcTo(r, start, sweep, forceMoveTo);
629}
630
631void SkPath::addRoundRect(const SkRect& rect, const SkScalar rad[],
632 Direction dir) {
reed@google.com44b2c732011-01-18 20:55:57 +0000633 // abort before we invoke SkAutoPathBoundsUpdate()
634 if (rect.isEmpty()) {
635 return;
636 }
637
reed@android.com8a1c16f2008-12-17 15:59:43 +0000638 SkAutoPathBoundsUpdate apbu(this, rect);
639
640 if (kCW_Direction == dir) {
641 add_corner_arc(this, rect, rad[0], rad[1], 180, dir, true);
642 add_corner_arc(this, rect, rad[2], rad[3], 270, dir, false);
643 add_corner_arc(this, rect, rad[4], rad[5], 0, dir, false);
644 add_corner_arc(this, rect, rad[6], rad[7], 90, dir, false);
645 } else {
646 add_corner_arc(this, rect, rad[0], rad[1], 180, dir, true);
647 add_corner_arc(this, rect, rad[6], rad[7], 90, dir, false);
648 add_corner_arc(this, rect, rad[4], rad[5], 0, dir, false);
649 add_corner_arc(this, rect, rad[2], rad[3], 270, dir, false);
650 }
651 this->close();
652}
653
654void SkPath::addOval(const SkRect& oval, Direction dir) {
655 SkAutoPathBoundsUpdate apbu(this, oval);
656
657 SkScalar cx = oval.centerX();
658 SkScalar cy = oval.centerY();
659 SkScalar rx = SkScalarHalf(oval.width());
660 SkScalar ry = SkScalarHalf(oval.height());
661#if 0 // these seem faster than using quads (1/2 the number of edges)
662 SkScalar sx = SkScalarMul(rx, CUBIC_ARC_FACTOR);
663 SkScalar sy = SkScalarMul(ry, CUBIC_ARC_FACTOR);
664
665 this->incReserve(13);
666 this->moveTo(cx + rx, cy);
667 if (dir == kCCW_Direction) {
668 this->cubicTo(cx + rx, cy - sy, cx + sx, cy - ry, cx, cy - ry);
669 this->cubicTo(cx - sx, cy - ry, cx - rx, cy - sy, cx - rx, cy);
670 this->cubicTo(cx - rx, cy + sy, cx - sx, cy + ry, cx, cy + ry);
671 this->cubicTo(cx + sx, cy + ry, cx + rx, cy + sy, cx + rx, cy);
672 } else {
673 this->cubicTo(cx + rx, cy + sy, cx + sx, cy + ry, cx, cy + ry);
674 this->cubicTo(cx - sx, cy + ry, cx - rx, cy + sy, cx - rx, cy);
675 this->cubicTo(cx - rx, cy - sy, cx - sx, cy - ry, cx, cy - ry);
676 this->cubicTo(cx + sx, cy - ry, cx + rx, cy - sy, cx + rx, cy);
677 }
678#else
679 SkScalar sx = SkScalarMul(rx, SK_ScalarTanPIOver8);
680 SkScalar sy = SkScalarMul(ry, SK_ScalarTanPIOver8);
681 SkScalar mx = SkScalarMul(rx, SK_ScalarRoot2Over2);
682 SkScalar my = SkScalarMul(ry, SK_ScalarRoot2Over2);
683
684 /*
685 To handle imprecision in computing the center and radii, we revert to
686 the provided bounds when we can (i.e. use oval.fLeft instead of cx-rx)
687 to ensure that we don't exceed the oval's bounds *ever*, since we want
688 to use oval for our fast-bounds, rather than have to recompute it.
689 */
690 const SkScalar L = oval.fLeft; // cx - rx
691 const SkScalar T = oval.fTop; // cy - ry
692 const SkScalar R = oval.fRight; // cx + rx
693 const SkScalar B = oval.fBottom; // cy + ry
694
695 this->incReserve(17); // 8 quads + close
696 this->moveTo(R, cy);
697 if (dir == kCCW_Direction) {
698 this->quadTo( R, cy - sy, cx + mx, cy - my);
699 this->quadTo(cx + sx, T, cx , T);
700 this->quadTo(cx - sx, T, cx - mx, cy - my);
701 this->quadTo( L, cy - sy, L, cy );
702 this->quadTo( L, cy + sy, cx - mx, cy + my);
703 this->quadTo(cx - sx, B, cx , B);
704 this->quadTo(cx + sx, B, cx + mx, cy + my);
705 this->quadTo( R, cy + sy, R, cy );
706 } else {
707 this->quadTo( R, cy + sy, cx + mx, cy + my);
708 this->quadTo(cx + sx, B, cx , B);
709 this->quadTo(cx - sx, B, cx - mx, cy + my);
710 this->quadTo( L, cy + sy, L, cy );
711 this->quadTo( L, cy - sy, cx - mx, cy - my);
712 this->quadTo(cx - sx, T, cx , T);
713 this->quadTo(cx + sx, T, cx + mx, cy - my);
714 this->quadTo( R, cy - sy, R, cy );
715 }
716#endif
717 this->close();
718}
719
720void SkPath::addCircle(SkScalar x, SkScalar y, SkScalar r, Direction dir) {
721 if (r > 0) {
722 SkRect rect;
723 rect.set(x - r, y - r, x + r, y + r);
724 this->addOval(rect, dir);
725 }
726}
727
728#include "SkGeometry.h"
729
730static int build_arc_points(const SkRect& oval, SkScalar startAngle,
731 SkScalar sweepAngle,
732 SkPoint pts[kSkBuildQuadArcStorage]) {
733 SkVector start, stop;
734
735 start.fY = SkScalarSinCos(SkDegreesToRadians(startAngle), &start.fX);
736 stop.fY = SkScalarSinCos(SkDegreesToRadians(startAngle + sweepAngle),
737 &stop.fX);
reed@android.comeebf5cb2010-02-09 18:30:59 +0000738
739 /* If the sweep angle is nearly (but less than) 360, then due to precision
740 loss in radians-conversion and/or sin/cos, we may end up with coincident
741 vectors, which will fool SkBuildQuadArc into doing nothing (bad) instead
742 of drawing a nearly complete circle (good).
743 e.g. canvas.drawArc(0, 359.99, ...)
744 -vs- canvas.drawArc(0, 359.9, ...)
745 We try to detect this edge case, and tweak the stop vector
746 */
747 if (start == stop) {
748 SkScalar sw = SkScalarAbs(sweepAngle);
749 if (sw < SkIntToScalar(360) && sw > SkIntToScalar(359)) {
750 SkScalar stopRad = SkDegreesToRadians(startAngle + sweepAngle);
751 // make a guess at a tiny angle (in radians) to tweak by
752 SkScalar deltaRad = SkScalarCopySign(SK_Scalar1/512, sweepAngle);
753 // not sure how much will be enough, so we use a loop
754 do {
755 stopRad -= deltaRad;
756 stop.fY = SkScalarSinCos(stopRad, &stop.fX);
757 } while (start == stop);
758 }
759 }
760
reed@android.com8a1c16f2008-12-17 15:59:43 +0000761 SkMatrix matrix;
reed@google.comabf15c12011-01-18 20:35:51 +0000762
reed@android.com8a1c16f2008-12-17 15:59:43 +0000763 matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height()));
764 matrix.postTranslate(oval.centerX(), oval.centerY());
reed@google.comabf15c12011-01-18 20:35:51 +0000765
reed@android.com8a1c16f2008-12-17 15:59:43 +0000766 return SkBuildQuadArc(start, stop,
767 sweepAngle > 0 ? kCW_SkRotationDirection : kCCW_SkRotationDirection,
768 &matrix, pts);
769}
770
771void SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
772 bool forceMoveTo) {
773 if (oval.width() < 0 || oval.height() < 0) {
774 return;
775 }
776
777 SkPoint pts[kSkBuildQuadArcStorage];
778 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
779 SkASSERT((count & 1) == 1);
780
781 if (fVerbs.count() == 0) {
782 forceMoveTo = true;
783 }
784 this->incReserve(count);
785 forceMoveTo ? this->moveTo(pts[0]) : this->lineTo(pts[0]);
786 for (int i = 1; i < count; i += 2) {
787 this->quadTo(pts[i], pts[i+1]);
788 }
789}
790
791void SkPath::addArc(const SkRect& oval, SkScalar startAngle,
792 SkScalar sweepAngle) {
793 if (oval.isEmpty() || 0 == sweepAngle) {
794 return;
795 }
796
797 const SkScalar kFullCircleAngle = SkIntToScalar(360);
798
799 if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
800 this->addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction);
801 return;
802 }
803
804 SkPoint pts[kSkBuildQuadArcStorage];
805 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
806
807 this->incReserve(count);
808 this->moveTo(pts[0]);
809 for (int i = 1; i < count; i += 2) {
810 this->quadTo(pts[i], pts[i+1]);
811 }
812}
813
814/*
815 Need to handle the case when the angle is sharp, and our computed end-points
816 for the arc go behind pt1 and/or p2...
817*/
818void SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
819 SkScalar radius) {
820 SkVector before, after;
reed@google.comabf15c12011-01-18 20:35:51 +0000821
reed@android.com8a1c16f2008-12-17 15:59:43 +0000822 // need to know our prev pt so we can construct tangent vectors
823 {
824 SkPoint start;
825 this->getLastPt(&start);
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +0000826 // Handle degenerate cases by adding a line to the first point and
827 // bailing out.
828 if ((x1 == start.fX && y1 == start.fY) ||
829 (x1 == x2 && y1 == y2) ||
830 radius == 0) {
831 this->lineTo(x1, y1);
832 return;
833 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000834 before.setNormalize(x1 - start.fX, y1 - start.fY);
835 after.setNormalize(x2 - x1, y2 - y1);
836 }
reed@google.comabf15c12011-01-18 20:35:51 +0000837
reed@android.com8a1c16f2008-12-17 15:59:43 +0000838 SkScalar cosh = SkPoint::DotProduct(before, after);
839 SkScalar sinh = SkPoint::CrossProduct(before, after);
840
841 if (SkScalarNearlyZero(sinh)) { // angle is too tight
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +0000842 this->lineTo(x1, y1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000843 return;
844 }
reed@google.comabf15c12011-01-18 20:35:51 +0000845
reed@android.com8a1c16f2008-12-17 15:59:43 +0000846 SkScalar dist = SkScalarMulDiv(radius, SK_Scalar1 - cosh, sinh);
847 if (dist < 0) {
848 dist = -dist;
849 }
850
851 SkScalar xx = x1 - SkScalarMul(dist, before.fX);
852 SkScalar yy = y1 - SkScalarMul(dist, before.fY);
853 SkRotationDirection arcDir;
854
855 // now turn before/after into normals
856 if (sinh > 0) {
857 before.rotateCCW();
858 after.rotateCCW();
859 arcDir = kCW_SkRotationDirection;
860 } else {
861 before.rotateCW();
862 after.rotateCW();
863 arcDir = kCCW_SkRotationDirection;
864 }
865
866 SkMatrix matrix;
867 SkPoint pts[kSkBuildQuadArcStorage];
reed@google.comabf15c12011-01-18 20:35:51 +0000868
reed@android.com8a1c16f2008-12-17 15:59:43 +0000869 matrix.setScale(radius, radius);
870 matrix.postTranslate(xx - SkScalarMul(radius, before.fX),
871 yy - SkScalarMul(radius, before.fY));
reed@google.comabf15c12011-01-18 20:35:51 +0000872
reed@android.com8a1c16f2008-12-17 15:59:43 +0000873 int count = SkBuildQuadArc(before, after, arcDir, &matrix, pts);
reed@google.comabf15c12011-01-18 20:35:51 +0000874
reed@android.com8a1c16f2008-12-17 15:59:43 +0000875 this->incReserve(count);
876 // [xx,yy] == pts[0]
877 this->lineTo(xx, yy);
878 for (int i = 1; i < count; i += 2) {
879 this->quadTo(pts[i], pts[i+1]);
880 }
881}
882
883///////////////////////////////////////////////////////////////////////////////
884
885void SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy) {
886 SkMatrix matrix;
887
888 matrix.setTranslate(dx, dy);
889 this->addPath(path, matrix);
890}
891
892void SkPath::addPath(const SkPath& path, const SkMatrix& matrix) {
893 this->incReserve(path.fPts.count());
894
895 Iter iter(path, false);
896 SkPoint pts[4];
897 Verb verb;
898
899 SkMatrix::MapPtsProc proc = matrix.getMapPtsProc();
900
901 while ((verb = iter.next(pts)) != kDone_Verb) {
902 switch (verb) {
903 case kMove_Verb:
904 proc(matrix, &pts[0], &pts[0], 1);
905 this->moveTo(pts[0]);
906 break;
907 case kLine_Verb:
908 proc(matrix, &pts[1], &pts[1], 1);
909 this->lineTo(pts[1]);
910 break;
911 case kQuad_Verb:
912 proc(matrix, &pts[1], &pts[1], 2);
913 this->quadTo(pts[1], pts[2]);
914 break;
915 case kCubic_Verb:
916 proc(matrix, &pts[1], &pts[1], 3);
917 this->cubicTo(pts[1], pts[2], pts[3]);
918 break;
919 case kClose_Verb:
920 this->close();
921 break;
922 default:
923 SkASSERT(!"unknown verb");
924 }
925 }
926}
927
928///////////////////////////////////////////////////////////////////////////////
929
930static const uint8_t gPtsInVerb[] = {
931 1, // kMove
932 1, // kLine
933 2, // kQuad
934 3, // kCubic
935 0, // kClose
936 0 // kDone
937};
938
939// ignore the initial moveto, and stop when the 1st contour ends
940void SkPath::pathTo(const SkPath& path) {
941 int i, vcount = path.fVerbs.count();
942 if (vcount == 0) {
943 return;
944 }
945
946 this->incReserve(vcount);
947
948 const uint8_t* verbs = path.fVerbs.begin();
949 const SkPoint* pts = path.fPts.begin() + 1; // 1 for the initial moveTo
950
951 SkASSERT(verbs[0] == kMove_Verb);
952 for (i = 1; i < vcount; i++) {
953 switch (verbs[i]) {
954 case kLine_Verb:
955 this->lineTo(pts[0].fX, pts[0].fY);
956 break;
957 case kQuad_Verb:
958 this->quadTo(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY);
959 break;
960 case kCubic_Verb:
961 this->cubicTo(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY,
962 pts[2].fX, pts[2].fY);
963 break;
964 case kClose_Verb:
965 return;
966 }
967 pts += gPtsInVerb[verbs[i]];
968 }
969}
970
971// ignore the last point of the 1st contour
972void SkPath::reversePathTo(const SkPath& path) {
973 int i, vcount = path.fVerbs.count();
974 if (vcount == 0) {
975 return;
976 }
977
978 this->incReserve(vcount);
979
980 const uint8_t* verbs = path.fVerbs.begin();
981 const SkPoint* pts = path.fPts.begin();
982
983 SkASSERT(verbs[0] == kMove_Verb);
984 for (i = 1; i < vcount; i++) {
985 int n = gPtsInVerb[verbs[i]];
986 if (n == 0) {
987 break;
988 }
989 pts += n;
990 }
991
992 while (--i > 0) {
993 switch (verbs[i]) {
994 case kLine_Verb:
995 this->lineTo(pts[-1].fX, pts[-1].fY);
996 break;
997 case kQuad_Verb:
998 this->quadTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY);
999 break;
1000 case kCubic_Verb:
1001 this->cubicTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY,
1002 pts[-3].fX, pts[-3].fY);
1003 break;
1004 default:
1005 SkASSERT(!"bad verb");
1006 break;
1007 }
1008 pts -= gPtsInVerb[verbs[i]];
1009 }
1010}
1011
1012///////////////////////////////////////////////////////////////////////////////
1013
1014void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
1015 SkMatrix matrix;
1016
1017 matrix.setTranslate(dx, dy);
1018 this->transform(matrix, dst);
1019}
1020
1021#include "SkGeometry.h"
1022
1023static void subdivide_quad_to(SkPath* path, const SkPoint pts[3],
1024 int level = 2) {
1025 if (--level >= 0) {
1026 SkPoint tmp[5];
1027
1028 SkChopQuadAtHalf(pts, tmp);
1029 subdivide_quad_to(path, &tmp[0], level);
1030 subdivide_quad_to(path, &tmp[2], level);
1031 } else {
1032 path->quadTo(pts[1], pts[2]);
1033 }
1034}
1035
1036static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
1037 int level = 2) {
1038 if (--level >= 0) {
1039 SkPoint tmp[7];
1040
1041 SkChopCubicAtHalf(pts, tmp);
1042 subdivide_cubic_to(path, &tmp[0], level);
1043 subdivide_cubic_to(path, &tmp[3], level);
1044 } else {
1045 path->cubicTo(pts[1], pts[2], pts[3]);
1046 }
1047}
1048
1049void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
1050 SkDEBUGCODE(this->validate();)
1051 if (dst == NULL) {
1052 dst = (SkPath*)this;
1053 }
1054
tomhudson@google.com8d430182011-06-06 19:11:19 +00001055 if (matrix.hasPerspective()) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001056 SkPath tmp;
1057 tmp.fFillType = fFillType;
1058
1059 SkPath::Iter iter(*this, false);
1060 SkPoint pts[4];
1061 SkPath::Verb verb;
1062
1063 while ((verb = iter.next(pts)) != kDone_Verb) {
1064 switch (verb) {
1065 case kMove_Verb:
1066 tmp.moveTo(pts[0]);
1067 break;
1068 case kLine_Verb:
1069 tmp.lineTo(pts[1]);
1070 break;
1071 case kQuad_Verb:
1072 subdivide_quad_to(&tmp, pts);
1073 break;
1074 case kCubic_Verb:
1075 subdivide_cubic_to(&tmp, pts);
1076 break;
1077 case kClose_Verb:
1078 tmp.close();
1079 break;
1080 default:
1081 SkASSERT(!"unknown verb");
1082 break;
1083 }
1084 }
1085
1086 dst->swap(tmp);
1087 matrix.mapPoints(dst->fPts.begin(), dst->fPts.count());
1088 } else {
1089 // remember that dst might == this, so be sure to check
reed@android.comd252db02009-04-01 18:31:44 +00001090 // fBoundsIsDirty before we set it
1091 if (!fBoundsIsDirty && matrix.rectStaysRect() && fPts.count() > 1) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001092 // if we're empty, fastbounds should not be mapped
reed@android.comd252db02009-04-01 18:31:44 +00001093 matrix.mapRect(&dst->fBounds, fBounds);
1094 dst->fBoundsIsDirty = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001095 } else {
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +00001096 GEN_ID_PTR_INC(dst);
reed@android.comd252db02009-04-01 18:31:44 +00001097 dst->fBoundsIsDirty = true;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001098 }
1099
1100 if (this != dst) {
1101 dst->fVerbs = fVerbs;
1102 dst->fPts.setCount(fPts.count());
1103 dst->fFillType = fFillType;
1104 }
1105 matrix.mapPoints(dst->fPts.begin(), fPts.begin(), fPts.count());
1106 SkDEBUGCODE(dst->validate();)
1107 }
1108}
1109
reed@android.com8a1c16f2008-12-17 15:59:43 +00001110///////////////////////////////////////////////////////////////////////////////
1111///////////////////////////////////////////////////////////////////////////////
1112
1113enum NeedMoveToState {
1114 kAfterClose_NeedMoveToState,
1115 kAfterCons_NeedMoveToState,
1116 kAfterPrefix_NeedMoveToState
1117};
1118
1119SkPath::Iter::Iter() {
1120#ifdef SK_DEBUG
1121 fPts = NULL;
1122 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
1123 fForceClose = fNeedMoveTo = fCloseLine = false;
1124#endif
1125 // need to init enough to make next() harmlessly return kDone_Verb
1126 fVerbs = NULL;
1127 fVerbStop = NULL;
1128 fNeedClose = false;
1129}
1130
1131SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
1132 this->setPath(path, forceClose);
1133}
1134
1135void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
1136 fPts = path.fPts.begin();
1137 fVerbs = path.fVerbs.begin();
1138 fVerbStop = path.fVerbs.end();
1139 fForceClose = SkToU8(forceClose);
1140 fNeedClose = false;
1141 fNeedMoveTo = kAfterPrefix_NeedMoveToState;
1142}
1143
1144bool SkPath::Iter::isClosedContour() const {
1145 if (fVerbs == NULL || fVerbs == fVerbStop) {
1146 return false;
1147 }
1148 if (fForceClose) {
1149 return true;
1150 }
1151
1152 const uint8_t* verbs = fVerbs;
1153 const uint8_t* stop = fVerbStop;
reed@google.comabf15c12011-01-18 20:35:51 +00001154
reed@android.com8a1c16f2008-12-17 15:59:43 +00001155 if (kMove_Verb == *verbs) {
1156 verbs += 1; // skip the initial moveto
1157 }
1158
1159 while (verbs < stop) {
reed@google.comabf15c12011-01-18 20:35:51 +00001160 unsigned v = *verbs++;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001161 if (kMove_Verb == v) {
1162 break;
1163 }
1164 if (kClose_Verb == v) {
1165 return true;
1166 }
1167 }
1168 return false;
1169}
1170
1171SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
1172 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001173 // A special case: if both points are NaN, SkPoint::operation== returns
1174 // false, but the iterator expects that they are treated as the same.
1175 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001176 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1177 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001178 return kClose_Verb;
1179 }
1180
reed@android.com8a1c16f2008-12-17 15:59:43 +00001181 if (pts) {
1182 pts[0] = fLastPt;
1183 pts[1] = fMoveTo;
1184 }
1185 fLastPt = fMoveTo;
1186 fCloseLine = true;
1187 return kLine_Verb;
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +00001188 } else {
1189 pts[0] = fMoveTo;
1190 return kClose_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001191 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001192}
1193
1194bool SkPath::Iter::cons_moveTo(SkPoint pts[1]) {
1195 if (fNeedMoveTo == kAfterClose_NeedMoveToState) {
1196 if (pts) {
1197 *pts = fMoveTo;
1198 }
1199 fNeedClose = fForceClose;
1200 fNeedMoveTo = kAfterCons_NeedMoveToState;
1201 fVerbs -= 1;
1202 return true;
1203 }
1204
1205 if (fNeedMoveTo == kAfterCons_NeedMoveToState) {
1206 if (pts) {
1207 *pts = fMoveTo;
1208 }
1209 fNeedMoveTo = kAfterPrefix_NeedMoveToState;
1210 } else {
1211 SkASSERT(fNeedMoveTo == kAfterPrefix_NeedMoveToState);
1212 if (pts) {
1213 *pts = fPts[-1];
1214 }
1215 }
1216 return false;
1217}
1218
1219SkPath::Verb SkPath::Iter::next(SkPoint pts[4]) {
1220 if (fVerbs == fVerbStop) {
1221 if (fNeedClose) {
1222 if (kLine_Verb == this->autoClose(pts)) {
1223 return kLine_Verb;
1224 }
1225 fNeedClose = false;
1226 return kClose_Verb;
1227 }
1228 return kDone_Verb;
1229 }
1230
1231 unsigned verb = *fVerbs++;
1232 const SkPoint* srcPts = fPts;
1233
1234 switch (verb) {
1235 case kMove_Verb:
1236 if (fNeedClose) {
1237 fVerbs -= 1;
1238 verb = this->autoClose(pts);
1239 if (verb == kClose_Verb) {
1240 fNeedClose = false;
1241 }
1242 return (Verb)verb;
1243 }
1244 if (fVerbs == fVerbStop) { // might be a trailing moveto
1245 return kDone_Verb;
1246 }
1247 fMoveTo = *srcPts;
1248 if (pts) {
1249 pts[0] = *srcPts;
1250 }
1251 srcPts += 1;
1252 fNeedMoveTo = kAfterCons_NeedMoveToState;
1253 fNeedClose = fForceClose;
1254 break;
1255 case kLine_Verb:
1256 if (this->cons_moveTo(pts)) {
1257 return kMove_Verb;
1258 }
1259 if (pts) {
1260 pts[1] = srcPts[0];
1261 }
1262 fLastPt = srcPts[0];
1263 fCloseLine = false;
1264 srcPts += 1;
1265 break;
1266 case kQuad_Verb:
1267 if (this->cons_moveTo(pts)) {
1268 return kMove_Verb;
1269 }
1270 if (pts) {
1271 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
1272 }
1273 fLastPt = srcPts[1];
1274 srcPts += 2;
1275 break;
1276 case kCubic_Verb:
1277 if (this->cons_moveTo(pts)) {
1278 return kMove_Verb;
1279 }
1280 if (pts) {
1281 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
1282 }
1283 fLastPt = srcPts[2];
1284 srcPts += 3;
1285 break;
1286 case kClose_Verb:
1287 verb = this->autoClose(pts);
1288 if (verb == kLine_Verb) {
1289 fVerbs -= 1;
1290 } else {
1291 fNeedClose = false;
1292 }
1293 fNeedMoveTo = kAfterClose_NeedMoveToState;
1294 break;
1295 }
1296 fPts = srcPts;
1297 return (Verb)verb;
1298}
1299
1300///////////////////////////////////////////////////////////////////////////////
1301
reed@android.com8a1c16f2008-12-17 15:59:43 +00001302/*
1303 Format in flattened buffer: [ptCount, verbCount, pts[], verbs[]]
1304*/
1305
reed@google.com73945652011-04-25 19:04:27 +00001306void SkPath::flatten(SkWriter32& buffer) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001307 SkDEBUGCODE(this->validate();)
1308
1309 buffer.write32(fPts.count());
1310 buffer.write32(fVerbs.count());
1311 buffer.write32(fFillType);
1312 buffer.writeMul4(fPts.begin(), sizeof(SkPoint) * fPts.count());
1313 buffer.writePad(fVerbs.begin(), fVerbs.count());
1314}
1315
reed@google.com73945652011-04-25 19:04:27 +00001316void SkPath::unflatten(SkReader32& buffer) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001317 fPts.setCount(buffer.readS32());
1318 fVerbs.setCount(buffer.readS32());
1319 fFillType = buffer.readS32();
1320 buffer.read(fPts.begin(), sizeof(SkPoint) * fPts.count());
1321 buffer.read(fVerbs.begin(), fVerbs.count());
reed@google.comabf15c12011-01-18 20:35:51 +00001322
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +00001323 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +00001324 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001325
1326 SkDEBUGCODE(this->validate();)
1327}
1328
1329///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00001330///////////////////////////////////////////////////////////////////////////////
1331
reed@android.com8a1c16f2008-12-17 15:59:43 +00001332void SkPath::dump(bool forceClose, const char title[]) const {
1333 Iter iter(*this, forceClose);
1334 SkPoint pts[4];
1335 Verb verb;
1336
1337 SkDebugf("path: forceClose=%s %s\n", forceClose ? "true" : "false",
1338 title ? title : "");
1339
1340 while ((verb = iter.next(pts)) != kDone_Verb) {
1341 switch (verb) {
1342 case kMove_Verb:
1343#ifdef SK_CAN_USE_FLOAT
1344 SkDebugf(" path: moveTo [%g %g]\n",
1345 SkScalarToFloat(pts[0].fX), SkScalarToFloat(pts[0].fY));
1346#else
1347 SkDebugf(" path: moveTo [%x %x]\n", pts[0].fX, pts[0].fY);
1348#endif
1349 break;
1350 case kLine_Verb:
1351#ifdef SK_CAN_USE_FLOAT
1352 SkDebugf(" path: lineTo [%g %g]\n",
1353 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY));
1354#else
1355 SkDebugf(" path: lineTo [%x %x]\n", pts[1].fX, pts[1].fY);
1356#endif
1357 break;
1358 case kQuad_Verb:
1359#ifdef SK_CAN_USE_FLOAT
1360 SkDebugf(" path: quadTo [%g %g] [%g %g]\n",
1361 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY),
1362 SkScalarToFloat(pts[2].fX), SkScalarToFloat(pts[2].fY));
1363#else
1364 SkDebugf(" path: quadTo [%x %x] [%x %x]\n",
1365 pts[1].fX, pts[1].fY, pts[2].fX, pts[2].fY);
1366#endif
1367 break;
1368 case kCubic_Verb:
1369#ifdef SK_CAN_USE_FLOAT
1370 SkDebugf(" path: cubeTo [%g %g] [%g %g] [%g %g]\n",
1371 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY),
1372 SkScalarToFloat(pts[2].fX), SkScalarToFloat(pts[2].fY),
1373 SkScalarToFloat(pts[3].fX), SkScalarToFloat(pts[3].fY));
1374#else
1375 SkDebugf(" path: cubeTo [%x %x] [%x %x] [%x %x]\n",
1376 pts[1].fX, pts[1].fY, pts[2].fX, pts[2].fY,
1377 pts[3].fX, pts[3].fY);
1378#endif
1379 break;
1380 case kClose_Verb:
1381 SkDebugf(" path: close\n");
1382 break;
1383 default:
1384 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
1385 verb = kDone_Verb; // stop the loop
1386 break;
1387 }
1388 }
1389 SkDebugf("path: done %s\n", title ? title : "");
1390}
1391
reed@android.come522ca52009-11-23 20:10:41 +00001392void SkPath::dump() const {
1393 this->dump(false);
1394}
1395
1396#ifdef SK_DEBUG
1397void SkPath::validate() const {
1398 SkASSERT(this != NULL);
1399 SkASSERT((fFillType & ~3) == 0);
1400 fPts.validate();
1401 fVerbs.validate();
reed@google.comabf15c12011-01-18 20:35:51 +00001402
reed@android.come522ca52009-11-23 20:10:41 +00001403 if (!fBoundsIsDirty) {
1404 SkRect bounds;
1405 compute_pt_bounds(&bounds, fPts);
1406 if (fPts.count() <= 1) {
1407 // if we're empty, fBounds may be empty but translated, so we can't
1408 // necessarily compare to bounds directly
1409 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
1410 // be [2, 2, 2, 2]
1411 SkASSERT(bounds.isEmpty());
1412 SkASSERT(fBounds.isEmpty());
1413 } else {
1414 fBounds.contains(bounds);
1415 }
1416 }
1417}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001418#endif
reed@android.come522ca52009-11-23 20:10:41 +00001419
reed@google.com04863fa2011-05-15 04:08:24 +00001420///////////////////////////////////////////////////////////////////////////////
1421
1422/**
1423 * Returns -1 || 0 || 1 depending on the sign of value:
1424 * -1 if value < 0
1425 * 0 if vlaue == 0
1426 * 1 if value > 0
1427 */
1428static int SkScalarSign(SkScalar value) {
1429 return value < 0 ? -1 : (value > 0);
1430}
1431
reed@google.com0b7b9822011-05-16 12:29:27 +00001432static int sign(SkScalar x) { return x < 0; }
1433#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00001434
reed@google.com04863fa2011-05-15 04:08:24 +00001435static int CrossProductSign(const SkVector& a, const SkVector& b) {
1436 return SkScalarSign(SkPoint::CrossProduct(a, b));
1437}
1438
1439// only valid for a single contour
1440struct Convexicator {
reed@google.comb54455e2011-05-16 14:16:04 +00001441 Convexicator() : fPtCount(0), fConvexity(SkPath::kConvex_Convexity) {
reed@google.com04863fa2011-05-15 04:08:24 +00001442 fSign = 0;
1443 // warnings
1444 fCurrPt.set(0, 0);
1445 fVec0.set(0, 0);
1446 fVec1.set(0, 0);
1447 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00001448
1449 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00001450 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00001451 }
1452
1453 SkPath::Convexity getConvexity() const { return fConvexity; }
1454
1455 void addPt(const SkPoint& pt) {
1456 if (SkPath::kConcave_Convexity == fConvexity) {
1457 return;
1458 }
1459
1460 if (0 == fPtCount) {
1461 fCurrPt = pt;
1462 ++fPtCount;
1463 } else {
1464 SkVector vec = pt - fCurrPt;
1465 if (vec.fX || vec.fY) {
1466 fCurrPt = pt;
1467 if (++fPtCount == 2) {
1468 fFirstVec = fVec1 = vec;
1469 } else {
1470 SkASSERT(fPtCount > 2);
1471 this->addVec(vec);
1472 }
reed@google.com85b6e392011-05-15 20:25:17 +00001473
1474 int sx = sign(vec.fX);
1475 int sy = sign(vec.fY);
1476 fDx += (sx != fSx);
1477 fDy += (sy != fSy);
1478 fSx = sx;
1479 fSy = sy;
1480
1481 if (fDx > 3 || fDy > 3) {
1482 fConvexity = SkPath::kConcave_Convexity;
1483 }
reed@google.com04863fa2011-05-15 04:08:24 +00001484 }
1485 }
1486 }
1487
1488 void close() {
1489 if (fPtCount > 2) {
1490 this->addVec(fFirstVec);
1491 }
1492 }
1493
1494private:
1495 void addVec(const SkVector& vec) {
1496 SkASSERT(vec.fX || vec.fY);
1497 fVec0 = fVec1;
1498 fVec1 = vec;
1499 int sign = CrossProductSign(fVec0, fVec1);
1500 if (0 == fSign) {
1501 fSign = sign;
1502 } else if (sign) {
reed@google.comb54455e2011-05-16 14:16:04 +00001503 if (fSign != sign) {
reed@google.com04863fa2011-05-15 04:08:24 +00001504 fConvexity = SkPath::kConcave_Convexity;
1505 }
1506 }
1507 }
1508
1509 SkPoint fCurrPt;
1510 SkVector fVec0, fVec1, fFirstVec;
1511 int fPtCount; // non-degenerate points
1512 int fSign;
1513 SkPath::Convexity fConvexity;
reed@google.com0b7b9822011-05-16 12:29:27 +00001514 int fDx, fDy, fSx, fSy;
reed@google.com04863fa2011-05-15 04:08:24 +00001515};
1516
1517SkPath::Convexity SkPath::ComputeConvexity(const SkPath& path) {
1518 SkPoint pts[4];
1519 SkPath::Verb verb;
1520 SkPath::Iter iter(path, true);
1521
1522 int contourCount = 0;
1523 int count;
1524 Convexicator state;
1525
1526 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
1527 switch (verb) {
1528 case kMove_Verb:
1529 if (++contourCount > 1) {
1530 return kConcave_Convexity;
1531 }
1532 pts[1] = pts[0];
1533 count = 1;
1534 break;
1535 case kLine_Verb: count = 1; break;
1536 case kQuad_Verb: count = 2; break;
1537 case kCubic_Verb: count = 3; break;
1538 case kClose_Verb:
1539 state.close();
1540 count = 0;
1541 break;
1542 default:
1543 SkASSERT(!"bad verb");
1544 return kConcave_Convexity;
1545 }
1546
1547 for (int i = 1; i <= count; i++) {
1548 state.addPt(pts[i]);
1549 }
1550 // early exit
1551 if (kConcave_Convexity == state.getConvexity()) {
1552 return kConcave_Convexity;
1553 }
1554 }
1555 return state.getConvexity();
1556}