blob: 1b9121ee181b107eae676fff6b3866aa62fa3b54 [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;
221 int firstDirection;
222 int lastDirection;
223 int nextDirection;
224 bool closedOrMoved;
225 bool autoClose = false;
226 const uint8_t* verbs = fVerbs.begin();
227 const uint8_t* verbStop = fVerbs.end();
228 const SkPoint* pts = fPts.begin();
229 while (verbs != verbStop) {
230 switch (*verbs++) {
231 case kClose_Verb:
232 pts = fPts.begin();
233 autoClose = true;
234 case kLine_Verb: {
235 SkScalar left = last.fX;
236 SkScalar top = last.fY;
237 SkScalar right = pts->fX;
238 SkScalar bottom = pts->fY;
239 ++pts;
240 if (left != right && top != bottom) {
241 return false; // diagonal
242 }
243 if (left == right && top == bottom) {
244 break; // single point on side OK
245 }
246 nextDirection = (left != right) << 0 |
247 (left < right || top < bottom) << 1;
248 if (0 == corners) {
249 firstDirection = nextDirection;
250 first = last;
251 last = pts[-1];
252 corners = 1;
253 closedOrMoved = false;
254 break;
255 }
256 if (closedOrMoved) {
257 return false; // closed followed by a line
258 }
259 closedOrMoved = autoClose;
260 if (lastDirection != nextDirection) {
261 if (++corners > 4) {
262 return false; // too many direction changes
263 }
264 }
265 last = pts[-1];
266 if (lastDirection == nextDirection) {
267 break; // colinear segment
268 }
269 // Possible values for corners are 2, 3, and 4.
270 // When corners == 3, nextDirection opposes firstDirection.
271 // Otherwise, nextDirection at corner 2 opposes corner 4.
272 int turn = firstDirection ^ corners - 1;
273 int directionCycle = 3 == corners ? 0 : nextDirection ^ turn;
274 if ((directionCycle ^ turn) != nextDirection) {
275 return false; // direction didn't follow cycle
276 }
277 break;
278 }
279 case kQuad_Verb:
280 case kCubic_Verb:
281 return false; // quadratic, cubic not allowed
282 case kMove_Verb:
283 last = *pts++;
284 closedOrMoved = true;
285 break;
286 }
287 lastDirection = nextDirection;
288 }
289 // Success if 4 corners and first point equals last
290 bool result = 4 == corners && first == last;
291 if (result && rect) {
292 *rect = getBounds();
293 }
294 return result;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000295}
296
297int SkPath::getPoints(SkPoint copy[], int max) const {
298 SkDEBUGCODE(this->validate();)
299
300 SkASSERT(max >= 0);
301 int count = fPts.count();
302 if (copy && max > 0 && count > 0) {
303 memcpy(copy, fPts.begin(), sizeof(SkPoint) * SkMin32(max, count));
304 }
305 return count;
306}
307
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000308SkPoint SkPath::getPoint(int index) const {
309 if ((unsigned)index < (unsigned)fPts.count()) {
310 return fPts[index];
311 }
312 return SkPoint::Make(0, 0);
313}
314
reed@android.com8a1c16f2008-12-17 15:59:43 +0000315void SkPath::getLastPt(SkPoint* lastPt) const {
316 SkDEBUGCODE(this->validate();)
317
318 if (lastPt) {
319 int count = fPts.count();
320 if (count == 0) {
321 lastPt->set(0, 0);
322 } else {
323 *lastPt = fPts[count - 1];
324 }
325 }
326}
327
328void SkPath::setLastPt(SkScalar x, SkScalar y) {
329 SkDEBUGCODE(this->validate();)
330
331 int count = fPts.count();
332 if (count == 0) {
333 this->moveTo(x, y);
334 } else {
335 fPts[count - 1].set(x, y);
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000336 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000337 }
338}
339
reed@android.comd252db02009-04-01 18:31:44 +0000340void SkPath::computeBounds() const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000341 SkDEBUGCODE(this->validate();)
reed@android.comd252db02009-04-01 18:31:44 +0000342 SkASSERT(fBoundsIsDirty);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000343
reed@android.comd252db02009-04-01 18:31:44 +0000344 fBoundsIsDirty = false;
345 compute_pt_bounds(&fBounds, fPts);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000346}
347
reed@google.com04863fa2011-05-15 04:08:24 +0000348void SkPath::setConvexity(Convexity c) {
349 if (fConvexity != c) {
350 fConvexity = c;
351 GEN_ID_INC;
352 }
353}
354
reed@android.com8a1c16f2008-12-17 15:59:43 +0000355//////////////////////////////////////////////////////////////////////////////
356// Construction methods
357
reed@google.comb54455e2011-05-16 14:16:04 +0000358#define DIRTY_AFTER_EDIT \
359 do { \
360 fBoundsIsDirty = true; \
361 fConvexity = kUnknown_Convexity;\
362 } while (0)
363
reed@android.com8a1c16f2008-12-17 15:59:43 +0000364void SkPath::incReserve(U16CPU inc) {
365 SkDEBUGCODE(this->validate();)
366
367 fVerbs.setReserve(fVerbs.count() + inc);
368 fPts.setReserve(fPts.count() + inc);
369
370 SkDEBUGCODE(this->validate();)
371}
372
373void SkPath::moveTo(SkScalar x, SkScalar y) {
374 SkDEBUGCODE(this->validate();)
375
376 int vc = fVerbs.count();
377 SkPoint* pt;
378
379 if (vc > 0 && fVerbs[vc - 1] == kMove_Verb) {
380 pt = &fPts[fPts.count() - 1];
381 } else {
382 pt = fPts.append();
383 *fVerbs.append() = kMove_Verb;
384 }
385 pt->set(x, y);
386
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000387 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000388 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000389}
390
391void SkPath::rMoveTo(SkScalar x, SkScalar y) {
392 SkPoint pt;
393 this->getLastPt(&pt);
394 this->moveTo(pt.fX + x, pt.fY + y);
395}
396
397void SkPath::lineTo(SkScalar x, SkScalar y) {
398 SkDEBUGCODE(this->validate();)
399
400 if (fVerbs.count() == 0) {
401 fPts.append()->set(0, 0);
402 *fVerbs.append() = kMove_Verb;
403 }
404 fPts.append()->set(x, y);
405 *fVerbs.append() = kLine_Verb;
406
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000407 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000408 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000409}
410
411void SkPath::rLineTo(SkScalar x, SkScalar y) {
412 SkPoint pt;
413 this->getLastPt(&pt);
414 this->lineTo(pt.fX + x, pt.fY + y);
415}
416
417void SkPath::quadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
418 SkDEBUGCODE(this->validate();)
419
420 if (fVerbs.count() == 0) {
421 fPts.append()->set(0, 0);
422 *fVerbs.append() = kMove_Verb;
423 }
424
425 SkPoint* pts = fPts.append(2);
426 pts[0].set(x1, y1);
427 pts[1].set(x2, y2);
428 *fVerbs.append() = kQuad_Verb;
429
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000430 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000431 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000432}
433
434void SkPath::rQuadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
435 SkPoint pt;
436 this->getLastPt(&pt);
437 this->quadTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2);
438}
439
440void SkPath::cubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
441 SkScalar x3, SkScalar y3) {
442 SkDEBUGCODE(this->validate();)
443
444 if (fVerbs.count() == 0) {
445 fPts.append()->set(0, 0);
446 *fVerbs.append() = kMove_Verb;
447 }
448 SkPoint* pts = fPts.append(3);
449 pts[0].set(x1, y1);
450 pts[1].set(x2, y2);
451 pts[2].set(x3, y3);
452 *fVerbs.append() = kCubic_Verb;
453
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000454 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000455 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000456}
457
458void SkPath::rCubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
459 SkScalar x3, SkScalar y3) {
460 SkPoint pt;
461 this->getLastPt(&pt);
462 this->cubicTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2,
463 pt.fX + x3, pt.fY + y3);
464}
465
466void SkPath::close() {
467 SkDEBUGCODE(this->validate();)
468
469 int count = fVerbs.count();
470 if (count > 0) {
471 switch (fVerbs[count - 1]) {
472 case kLine_Verb:
473 case kQuad_Verb:
474 case kCubic_Verb:
475 *fVerbs.append() = kClose_Verb;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000476 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000477 break;
478 default:
479 // don't add a close if the prev wasn't a primitive
480 break;
481 }
482 }
483}
484
485///////////////////////////////////////////////////////////////////////////////
reed@google.comabf15c12011-01-18 20:35:51 +0000486
reed@android.com8a1c16f2008-12-17 15:59:43 +0000487void SkPath::addRect(const SkRect& rect, Direction dir) {
488 this->addRect(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, dir);
489}
490
491void SkPath::addRect(SkScalar left, SkScalar top, SkScalar right,
492 SkScalar bottom, Direction dir) {
493 SkAutoPathBoundsUpdate apbu(this, left, top, right, bottom);
494
495 this->incReserve(5);
496
497 this->moveTo(left, top);
498 if (dir == kCCW_Direction) {
499 this->lineTo(left, bottom);
500 this->lineTo(right, bottom);
501 this->lineTo(right, top);
502 } else {
503 this->lineTo(right, top);
504 this->lineTo(right, bottom);
505 this->lineTo(left, bottom);
506 }
507 this->close();
508}
509
510#define CUBIC_ARC_FACTOR ((SK_ScalarSqrt2 - SK_Scalar1) * 4 / 3)
511
512void SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
513 Direction dir) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000514 SkScalar w = rect.width();
515 SkScalar halfW = SkScalarHalf(w);
516 SkScalar h = rect.height();
517 SkScalar halfH = SkScalarHalf(h);
518
519 if (halfW <= 0 || halfH <= 0) {
520 return;
521 }
522
reed@google.comabf15c12011-01-18 20:35:51 +0000523 bool skip_hori = rx >= halfW;
524 bool skip_vert = ry >= halfH;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000525
526 if (skip_hori && skip_vert) {
527 this->addOval(rect, dir);
528 return;
529 }
reed@google.comabf15c12011-01-18 20:35:51 +0000530
531 SkAutoPathBoundsUpdate apbu(this, rect);
532
reed@android.com8a1c16f2008-12-17 15:59:43 +0000533 if (skip_hori) {
534 rx = halfW;
535 } else if (skip_vert) {
536 ry = halfH;
537 }
538
539 SkScalar sx = SkScalarMul(rx, CUBIC_ARC_FACTOR);
540 SkScalar sy = SkScalarMul(ry, CUBIC_ARC_FACTOR);
541
542 this->incReserve(17);
543 this->moveTo(rect.fRight - rx, rect.fTop);
544 if (dir == kCCW_Direction) {
545 if (!skip_hori) {
546 this->lineTo(rect.fLeft + rx, rect.fTop); // top
547 }
548 this->cubicTo(rect.fLeft + rx - sx, rect.fTop,
549 rect.fLeft, rect.fTop + ry - sy,
550 rect.fLeft, rect.fTop + ry); // top-left
551 if (!skip_vert) {
552 this->lineTo(rect.fLeft, rect.fBottom - ry); // left
553 }
554 this->cubicTo(rect.fLeft, rect.fBottom - ry + sy,
555 rect.fLeft + rx - sx, rect.fBottom,
556 rect.fLeft + rx, rect.fBottom); // bot-left
557 if (!skip_hori) {
558 this->lineTo(rect.fRight - rx, rect.fBottom); // bottom
559 }
560 this->cubicTo(rect.fRight - rx + sx, rect.fBottom,
561 rect.fRight, rect.fBottom - ry + sy,
562 rect.fRight, rect.fBottom - ry); // bot-right
563 if (!skip_vert) {
564 this->lineTo(rect.fRight, rect.fTop + ry);
565 }
566 this->cubicTo(rect.fRight, rect.fTop + ry - sy,
567 rect.fRight - rx + sx, rect.fTop,
568 rect.fRight - rx, rect.fTop); // top-right
569 } else {
570 this->cubicTo(rect.fRight - rx + sx, rect.fTop,
571 rect.fRight, rect.fTop + ry - sy,
572 rect.fRight, rect.fTop + ry); // top-right
573 if (!skip_vert) {
574 this->lineTo(rect.fRight, rect.fBottom - ry);
575 }
576 this->cubicTo(rect.fRight, rect.fBottom - ry + sy,
577 rect.fRight - rx + sx, rect.fBottom,
578 rect.fRight - rx, rect.fBottom); // bot-right
579 if (!skip_hori) {
580 this->lineTo(rect.fLeft + rx, rect.fBottom); // bottom
581 }
582 this->cubicTo(rect.fLeft + rx - sx, rect.fBottom,
583 rect.fLeft, rect.fBottom - ry + sy,
584 rect.fLeft, rect.fBottom - ry); // bot-left
585 if (!skip_vert) {
586 this->lineTo(rect.fLeft, rect.fTop + ry); // left
587 }
588 this->cubicTo(rect.fLeft, rect.fTop + ry - sy,
589 rect.fLeft + rx - sx, rect.fTop,
590 rect.fLeft + rx, rect.fTop); // top-left
591 if (!skip_hori) {
592 this->lineTo(rect.fRight - rx, rect.fTop); // top
593 }
594 }
595 this->close();
596}
597
598static void add_corner_arc(SkPath* path, const SkRect& rect,
599 SkScalar rx, SkScalar ry, int startAngle,
600 SkPath::Direction dir, bool forceMoveTo) {
601 rx = SkMinScalar(SkScalarHalf(rect.width()), rx);
602 ry = SkMinScalar(SkScalarHalf(rect.height()), ry);
reed@google.comabf15c12011-01-18 20:35:51 +0000603
reed@android.com8a1c16f2008-12-17 15:59:43 +0000604 SkRect r;
605 r.set(-rx, -ry, rx, ry);
606
607 switch (startAngle) {
608 case 0:
609 r.offset(rect.fRight - r.fRight, rect.fBottom - r.fBottom);
610 break;
611 case 90:
612 r.offset(rect.fLeft - r.fLeft, rect.fBottom - r.fBottom);
613 break;
614 case 180: r.offset(rect.fLeft - r.fLeft, rect.fTop - r.fTop); break;
615 case 270: r.offset(rect.fRight - r.fRight, rect.fTop - r.fTop); break;
616 default: SkASSERT(!"unexpected startAngle in add_corner_arc");
617 }
reed@google.comabf15c12011-01-18 20:35:51 +0000618
reed@android.com8a1c16f2008-12-17 15:59:43 +0000619 SkScalar start = SkIntToScalar(startAngle);
620 SkScalar sweep = SkIntToScalar(90);
621 if (SkPath::kCCW_Direction == dir) {
622 start += sweep;
623 sweep = -sweep;
624 }
reed@google.comabf15c12011-01-18 20:35:51 +0000625
reed@android.com8a1c16f2008-12-17 15:59:43 +0000626 path->arcTo(r, start, sweep, forceMoveTo);
627}
628
629void SkPath::addRoundRect(const SkRect& rect, const SkScalar rad[],
630 Direction dir) {
reed@google.com44b2c732011-01-18 20:55:57 +0000631 // abort before we invoke SkAutoPathBoundsUpdate()
632 if (rect.isEmpty()) {
633 return;
634 }
635
reed@android.com8a1c16f2008-12-17 15:59:43 +0000636 SkAutoPathBoundsUpdate apbu(this, rect);
637
638 if (kCW_Direction == dir) {
639 add_corner_arc(this, rect, rad[0], rad[1], 180, dir, true);
640 add_corner_arc(this, rect, rad[2], rad[3], 270, dir, false);
641 add_corner_arc(this, rect, rad[4], rad[5], 0, dir, false);
642 add_corner_arc(this, rect, rad[6], rad[7], 90, dir, false);
643 } else {
644 add_corner_arc(this, rect, rad[0], rad[1], 180, dir, true);
645 add_corner_arc(this, rect, rad[6], rad[7], 90, dir, false);
646 add_corner_arc(this, rect, rad[4], rad[5], 0, dir, false);
647 add_corner_arc(this, rect, rad[2], rad[3], 270, dir, false);
648 }
649 this->close();
650}
651
652void SkPath::addOval(const SkRect& oval, Direction dir) {
653 SkAutoPathBoundsUpdate apbu(this, oval);
654
655 SkScalar cx = oval.centerX();
656 SkScalar cy = oval.centerY();
657 SkScalar rx = SkScalarHalf(oval.width());
658 SkScalar ry = SkScalarHalf(oval.height());
659#if 0 // these seem faster than using quads (1/2 the number of edges)
660 SkScalar sx = SkScalarMul(rx, CUBIC_ARC_FACTOR);
661 SkScalar sy = SkScalarMul(ry, CUBIC_ARC_FACTOR);
662
663 this->incReserve(13);
664 this->moveTo(cx + rx, cy);
665 if (dir == kCCW_Direction) {
666 this->cubicTo(cx + rx, cy - sy, cx + sx, cy - ry, cx, cy - ry);
667 this->cubicTo(cx - sx, cy - ry, cx - rx, cy - sy, cx - rx, cy);
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 } else {
671 this->cubicTo(cx + rx, cy + sy, cx + sx, cy + ry, cx, cy + ry);
672 this->cubicTo(cx - sx, cy + ry, cx - rx, cy + sy, cx - rx, cy);
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 }
676#else
677 SkScalar sx = SkScalarMul(rx, SK_ScalarTanPIOver8);
678 SkScalar sy = SkScalarMul(ry, SK_ScalarTanPIOver8);
679 SkScalar mx = SkScalarMul(rx, SK_ScalarRoot2Over2);
680 SkScalar my = SkScalarMul(ry, SK_ScalarRoot2Over2);
681
682 /*
683 To handle imprecision in computing the center and radii, we revert to
684 the provided bounds when we can (i.e. use oval.fLeft instead of cx-rx)
685 to ensure that we don't exceed the oval's bounds *ever*, since we want
686 to use oval for our fast-bounds, rather than have to recompute it.
687 */
688 const SkScalar L = oval.fLeft; // cx - rx
689 const SkScalar T = oval.fTop; // cy - ry
690 const SkScalar R = oval.fRight; // cx + rx
691 const SkScalar B = oval.fBottom; // cy + ry
692
693 this->incReserve(17); // 8 quads + close
694 this->moveTo(R, cy);
695 if (dir == kCCW_Direction) {
696 this->quadTo( R, cy - sy, cx + mx, cy - my);
697 this->quadTo(cx + sx, T, cx , T);
698 this->quadTo(cx - sx, T, cx - mx, cy - my);
699 this->quadTo( L, cy - sy, L, cy );
700 this->quadTo( L, cy + sy, cx - mx, cy + my);
701 this->quadTo(cx - sx, B, cx , B);
702 this->quadTo(cx + sx, B, cx + mx, cy + my);
703 this->quadTo( R, cy + sy, R, cy );
704 } else {
705 this->quadTo( R, cy + sy, cx + mx, cy + my);
706 this->quadTo(cx + sx, B, cx , B);
707 this->quadTo(cx - sx, B, cx - mx, cy + my);
708 this->quadTo( L, cy + sy, L, cy );
709 this->quadTo( L, cy - sy, cx - mx, cy - my);
710 this->quadTo(cx - sx, T, cx , T);
711 this->quadTo(cx + sx, T, cx + mx, cy - my);
712 this->quadTo( R, cy - sy, R, cy );
713 }
714#endif
715 this->close();
716}
717
718void SkPath::addCircle(SkScalar x, SkScalar y, SkScalar r, Direction dir) {
719 if (r > 0) {
720 SkRect rect;
721 rect.set(x - r, y - r, x + r, y + r);
722 this->addOval(rect, dir);
723 }
724}
725
726#include "SkGeometry.h"
727
728static int build_arc_points(const SkRect& oval, SkScalar startAngle,
729 SkScalar sweepAngle,
730 SkPoint pts[kSkBuildQuadArcStorage]) {
731 SkVector start, stop;
732
733 start.fY = SkScalarSinCos(SkDegreesToRadians(startAngle), &start.fX);
734 stop.fY = SkScalarSinCos(SkDegreesToRadians(startAngle + sweepAngle),
735 &stop.fX);
reed@android.comeebf5cb2010-02-09 18:30:59 +0000736
737 /* If the sweep angle is nearly (but less than) 360, then due to precision
738 loss in radians-conversion and/or sin/cos, we may end up with coincident
739 vectors, which will fool SkBuildQuadArc into doing nothing (bad) instead
740 of drawing a nearly complete circle (good).
741 e.g. canvas.drawArc(0, 359.99, ...)
742 -vs- canvas.drawArc(0, 359.9, ...)
743 We try to detect this edge case, and tweak the stop vector
744 */
745 if (start == stop) {
746 SkScalar sw = SkScalarAbs(sweepAngle);
747 if (sw < SkIntToScalar(360) && sw > SkIntToScalar(359)) {
748 SkScalar stopRad = SkDegreesToRadians(startAngle + sweepAngle);
749 // make a guess at a tiny angle (in radians) to tweak by
750 SkScalar deltaRad = SkScalarCopySign(SK_Scalar1/512, sweepAngle);
751 // not sure how much will be enough, so we use a loop
752 do {
753 stopRad -= deltaRad;
754 stop.fY = SkScalarSinCos(stopRad, &stop.fX);
755 } while (start == stop);
756 }
757 }
758
reed@android.com8a1c16f2008-12-17 15:59:43 +0000759 SkMatrix matrix;
reed@google.comabf15c12011-01-18 20:35:51 +0000760
reed@android.com8a1c16f2008-12-17 15:59:43 +0000761 matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height()));
762 matrix.postTranslate(oval.centerX(), oval.centerY());
reed@google.comabf15c12011-01-18 20:35:51 +0000763
reed@android.com8a1c16f2008-12-17 15:59:43 +0000764 return SkBuildQuadArc(start, stop,
765 sweepAngle > 0 ? kCW_SkRotationDirection : kCCW_SkRotationDirection,
766 &matrix, pts);
767}
768
769void SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
770 bool forceMoveTo) {
771 if (oval.width() < 0 || oval.height() < 0) {
772 return;
773 }
774
775 SkPoint pts[kSkBuildQuadArcStorage];
776 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
777 SkASSERT((count & 1) == 1);
778
779 if (fVerbs.count() == 0) {
780 forceMoveTo = true;
781 }
782 this->incReserve(count);
783 forceMoveTo ? this->moveTo(pts[0]) : this->lineTo(pts[0]);
784 for (int i = 1; i < count; i += 2) {
785 this->quadTo(pts[i], pts[i+1]);
786 }
787}
788
789void SkPath::addArc(const SkRect& oval, SkScalar startAngle,
790 SkScalar sweepAngle) {
791 if (oval.isEmpty() || 0 == sweepAngle) {
792 return;
793 }
794
795 const SkScalar kFullCircleAngle = SkIntToScalar(360);
796
797 if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
798 this->addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction);
799 return;
800 }
801
802 SkPoint pts[kSkBuildQuadArcStorage];
803 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
804
805 this->incReserve(count);
806 this->moveTo(pts[0]);
807 for (int i = 1; i < count; i += 2) {
808 this->quadTo(pts[i], pts[i+1]);
809 }
810}
811
812/*
813 Need to handle the case when the angle is sharp, and our computed end-points
814 for the arc go behind pt1 and/or p2...
815*/
816void SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
817 SkScalar radius) {
818 SkVector before, after;
reed@google.comabf15c12011-01-18 20:35:51 +0000819
reed@android.com8a1c16f2008-12-17 15:59:43 +0000820 // need to know our prev pt so we can construct tangent vectors
821 {
822 SkPoint start;
823 this->getLastPt(&start);
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +0000824 // Handle degenerate cases by adding a line to the first point and
825 // bailing out.
826 if ((x1 == start.fX && y1 == start.fY) ||
827 (x1 == x2 && y1 == y2) ||
828 radius == 0) {
829 this->lineTo(x1, y1);
830 return;
831 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000832 before.setNormalize(x1 - start.fX, y1 - start.fY);
833 after.setNormalize(x2 - x1, y2 - y1);
834 }
reed@google.comabf15c12011-01-18 20:35:51 +0000835
reed@android.com8a1c16f2008-12-17 15:59:43 +0000836 SkScalar cosh = SkPoint::DotProduct(before, after);
837 SkScalar sinh = SkPoint::CrossProduct(before, after);
838
839 if (SkScalarNearlyZero(sinh)) { // angle is too tight
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +0000840 this->lineTo(x1, y1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000841 return;
842 }
reed@google.comabf15c12011-01-18 20:35:51 +0000843
reed@android.com8a1c16f2008-12-17 15:59:43 +0000844 SkScalar dist = SkScalarMulDiv(radius, SK_Scalar1 - cosh, sinh);
845 if (dist < 0) {
846 dist = -dist;
847 }
848
849 SkScalar xx = x1 - SkScalarMul(dist, before.fX);
850 SkScalar yy = y1 - SkScalarMul(dist, before.fY);
851 SkRotationDirection arcDir;
852
853 // now turn before/after into normals
854 if (sinh > 0) {
855 before.rotateCCW();
856 after.rotateCCW();
857 arcDir = kCW_SkRotationDirection;
858 } else {
859 before.rotateCW();
860 after.rotateCW();
861 arcDir = kCCW_SkRotationDirection;
862 }
863
864 SkMatrix matrix;
865 SkPoint pts[kSkBuildQuadArcStorage];
reed@google.comabf15c12011-01-18 20:35:51 +0000866
reed@android.com8a1c16f2008-12-17 15:59:43 +0000867 matrix.setScale(radius, radius);
868 matrix.postTranslate(xx - SkScalarMul(radius, before.fX),
869 yy - SkScalarMul(radius, before.fY));
reed@google.comabf15c12011-01-18 20:35:51 +0000870
reed@android.com8a1c16f2008-12-17 15:59:43 +0000871 int count = SkBuildQuadArc(before, after, arcDir, &matrix, pts);
reed@google.comabf15c12011-01-18 20:35:51 +0000872
reed@android.com8a1c16f2008-12-17 15:59:43 +0000873 this->incReserve(count);
874 // [xx,yy] == pts[0]
875 this->lineTo(xx, yy);
876 for (int i = 1; i < count; i += 2) {
877 this->quadTo(pts[i], pts[i+1]);
878 }
879}
880
881///////////////////////////////////////////////////////////////////////////////
882
883void SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy) {
884 SkMatrix matrix;
885
886 matrix.setTranslate(dx, dy);
887 this->addPath(path, matrix);
888}
889
890void SkPath::addPath(const SkPath& path, const SkMatrix& matrix) {
891 this->incReserve(path.fPts.count());
892
893 Iter iter(path, false);
894 SkPoint pts[4];
895 Verb verb;
896
897 SkMatrix::MapPtsProc proc = matrix.getMapPtsProc();
898
899 while ((verb = iter.next(pts)) != kDone_Verb) {
900 switch (verb) {
901 case kMove_Verb:
902 proc(matrix, &pts[0], &pts[0], 1);
903 this->moveTo(pts[0]);
904 break;
905 case kLine_Verb:
906 proc(matrix, &pts[1], &pts[1], 1);
907 this->lineTo(pts[1]);
908 break;
909 case kQuad_Verb:
910 proc(matrix, &pts[1], &pts[1], 2);
911 this->quadTo(pts[1], pts[2]);
912 break;
913 case kCubic_Verb:
914 proc(matrix, &pts[1], &pts[1], 3);
915 this->cubicTo(pts[1], pts[2], pts[3]);
916 break;
917 case kClose_Verb:
918 this->close();
919 break;
920 default:
921 SkASSERT(!"unknown verb");
922 }
923 }
924}
925
926///////////////////////////////////////////////////////////////////////////////
927
928static const uint8_t gPtsInVerb[] = {
929 1, // kMove
930 1, // kLine
931 2, // kQuad
932 3, // kCubic
933 0, // kClose
934 0 // kDone
935};
936
937// ignore the initial moveto, and stop when the 1st contour ends
938void SkPath::pathTo(const SkPath& path) {
939 int i, vcount = path.fVerbs.count();
940 if (vcount == 0) {
941 return;
942 }
943
944 this->incReserve(vcount);
945
946 const uint8_t* verbs = path.fVerbs.begin();
947 const SkPoint* pts = path.fPts.begin() + 1; // 1 for the initial moveTo
948
949 SkASSERT(verbs[0] == kMove_Verb);
950 for (i = 1; i < vcount; i++) {
951 switch (verbs[i]) {
952 case kLine_Verb:
953 this->lineTo(pts[0].fX, pts[0].fY);
954 break;
955 case kQuad_Verb:
956 this->quadTo(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY);
957 break;
958 case kCubic_Verb:
959 this->cubicTo(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY,
960 pts[2].fX, pts[2].fY);
961 break;
962 case kClose_Verb:
963 return;
964 }
965 pts += gPtsInVerb[verbs[i]];
966 }
967}
968
969// ignore the last point of the 1st contour
970void SkPath::reversePathTo(const SkPath& path) {
971 int i, vcount = path.fVerbs.count();
972 if (vcount == 0) {
973 return;
974 }
975
976 this->incReserve(vcount);
977
978 const uint8_t* verbs = path.fVerbs.begin();
979 const SkPoint* pts = path.fPts.begin();
980
981 SkASSERT(verbs[0] == kMove_Verb);
982 for (i = 1; i < vcount; i++) {
983 int n = gPtsInVerb[verbs[i]];
984 if (n == 0) {
985 break;
986 }
987 pts += n;
988 }
989
990 while (--i > 0) {
991 switch (verbs[i]) {
992 case kLine_Verb:
993 this->lineTo(pts[-1].fX, pts[-1].fY);
994 break;
995 case kQuad_Verb:
996 this->quadTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY);
997 break;
998 case kCubic_Verb:
999 this->cubicTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY,
1000 pts[-3].fX, pts[-3].fY);
1001 break;
1002 default:
1003 SkASSERT(!"bad verb");
1004 break;
1005 }
1006 pts -= gPtsInVerb[verbs[i]];
1007 }
1008}
1009
1010///////////////////////////////////////////////////////////////////////////////
1011
1012void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
1013 SkMatrix matrix;
1014
1015 matrix.setTranslate(dx, dy);
1016 this->transform(matrix, dst);
1017}
1018
1019#include "SkGeometry.h"
1020
1021static void subdivide_quad_to(SkPath* path, const SkPoint pts[3],
1022 int level = 2) {
1023 if (--level >= 0) {
1024 SkPoint tmp[5];
1025
1026 SkChopQuadAtHalf(pts, tmp);
1027 subdivide_quad_to(path, &tmp[0], level);
1028 subdivide_quad_to(path, &tmp[2], level);
1029 } else {
1030 path->quadTo(pts[1], pts[2]);
1031 }
1032}
1033
1034static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
1035 int level = 2) {
1036 if (--level >= 0) {
1037 SkPoint tmp[7];
1038
1039 SkChopCubicAtHalf(pts, tmp);
1040 subdivide_cubic_to(path, &tmp[0], level);
1041 subdivide_cubic_to(path, &tmp[3], level);
1042 } else {
1043 path->cubicTo(pts[1], pts[2], pts[3]);
1044 }
1045}
1046
1047void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
1048 SkDEBUGCODE(this->validate();)
1049 if (dst == NULL) {
1050 dst = (SkPath*)this;
1051 }
1052
tomhudson@google.com8d430182011-06-06 19:11:19 +00001053 if (matrix.hasPerspective()) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001054 SkPath tmp;
1055 tmp.fFillType = fFillType;
1056
1057 SkPath::Iter iter(*this, false);
1058 SkPoint pts[4];
1059 SkPath::Verb verb;
1060
1061 while ((verb = iter.next(pts)) != kDone_Verb) {
1062 switch (verb) {
1063 case kMove_Verb:
1064 tmp.moveTo(pts[0]);
1065 break;
1066 case kLine_Verb:
1067 tmp.lineTo(pts[1]);
1068 break;
1069 case kQuad_Verb:
1070 subdivide_quad_to(&tmp, pts);
1071 break;
1072 case kCubic_Verb:
1073 subdivide_cubic_to(&tmp, pts);
1074 break;
1075 case kClose_Verb:
1076 tmp.close();
1077 break;
1078 default:
1079 SkASSERT(!"unknown verb");
1080 break;
1081 }
1082 }
1083
1084 dst->swap(tmp);
1085 matrix.mapPoints(dst->fPts.begin(), dst->fPts.count());
1086 } else {
1087 // remember that dst might == this, so be sure to check
reed@android.comd252db02009-04-01 18:31:44 +00001088 // fBoundsIsDirty before we set it
1089 if (!fBoundsIsDirty && matrix.rectStaysRect() && fPts.count() > 1) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001090 // if we're empty, fastbounds should not be mapped
reed@android.comd252db02009-04-01 18:31:44 +00001091 matrix.mapRect(&dst->fBounds, fBounds);
1092 dst->fBoundsIsDirty = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001093 } else {
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +00001094 GEN_ID_PTR_INC(dst);
reed@android.comd252db02009-04-01 18:31:44 +00001095 dst->fBoundsIsDirty = true;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001096 }
1097
1098 if (this != dst) {
1099 dst->fVerbs = fVerbs;
1100 dst->fPts.setCount(fPts.count());
1101 dst->fFillType = fFillType;
1102 }
1103 matrix.mapPoints(dst->fPts.begin(), fPts.begin(), fPts.count());
1104 SkDEBUGCODE(dst->validate();)
1105 }
1106}
1107
reed@android.com8a1c16f2008-12-17 15:59:43 +00001108///////////////////////////////////////////////////////////////////////////////
1109///////////////////////////////////////////////////////////////////////////////
1110
1111enum NeedMoveToState {
1112 kAfterClose_NeedMoveToState,
1113 kAfterCons_NeedMoveToState,
1114 kAfterPrefix_NeedMoveToState
1115};
1116
1117SkPath::Iter::Iter() {
1118#ifdef SK_DEBUG
1119 fPts = NULL;
1120 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
1121 fForceClose = fNeedMoveTo = fCloseLine = false;
1122#endif
1123 // need to init enough to make next() harmlessly return kDone_Verb
1124 fVerbs = NULL;
1125 fVerbStop = NULL;
1126 fNeedClose = false;
1127}
1128
1129SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
1130 this->setPath(path, forceClose);
1131}
1132
1133void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
1134 fPts = path.fPts.begin();
1135 fVerbs = path.fVerbs.begin();
1136 fVerbStop = path.fVerbs.end();
1137 fForceClose = SkToU8(forceClose);
1138 fNeedClose = false;
1139 fNeedMoveTo = kAfterPrefix_NeedMoveToState;
1140}
1141
1142bool SkPath::Iter::isClosedContour() const {
1143 if (fVerbs == NULL || fVerbs == fVerbStop) {
1144 return false;
1145 }
1146 if (fForceClose) {
1147 return true;
1148 }
1149
1150 const uint8_t* verbs = fVerbs;
1151 const uint8_t* stop = fVerbStop;
reed@google.comabf15c12011-01-18 20:35:51 +00001152
reed@android.com8a1c16f2008-12-17 15:59:43 +00001153 if (kMove_Verb == *verbs) {
1154 verbs += 1; // skip the initial moveto
1155 }
1156
1157 while (verbs < stop) {
reed@google.comabf15c12011-01-18 20:35:51 +00001158 unsigned v = *verbs++;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001159 if (kMove_Verb == v) {
1160 break;
1161 }
1162 if (kClose_Verb == v) {
1163 return true;
1164 }
1165 }
1166 return false;
1167}
1168
1169SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
1170 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001171 // A special case: if both points are NaN, SkPoint::operation== returns
1172 // false, but the iterator expects that they are treated as the same.
1173 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001174 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1175 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001176 return kClose_Verb;
1177 }
1178
reed@android.com8a1c16f2008-12-17 15:59:43 +00001179 if (pts) {
1180 pts[0] = fLastPt;
1181 pts[1] = fMoveTo;
1182 }
1183 fLastPt = fMoveTo;
1184 fCloseLine = true;
1185 return kLine_Verb;
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +00001186 } else {
1187 pts[0] = fMoveTo;
1188 return kClose_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001189 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001190}
1191
1192bool SkPath::Iter::cons_moveTo(SkPoint pts[1]) {
1193 if (fNeedMoveTo == kAfterClose_NeedMoveToState) {
1194 if (pts) {
1195 *pts = fMoveTo;
1196 }
1197 fNeedClose = fForceClose;
1198 fNeedMoveTo = kAfterCons_NeedMoveToState;
1199 fVerbs -= 1;
1200 return true;
1201 }
1202
1203 if (fNeedMoveTo == kAfterCons_NeedMoveToState) {
1204 if (pts) {
1205 *pts = fMoveTo;
1206 }
1207 fNeedMoveTo = kAfterPrefix_NeedMoveToState;
1208 } else {
1209 SkASSERT(fNeedMoveTo == kAfterPrefix_NeedMoveToState);
1210 if (pts) {
1211 *pts = fPts[-1];
1212 }
1213 }
1214 return false;
1215}
1216
1217SkPath::Verb SkPath::Iter::next(SkPoint pts[4]) {
1218 if (fVerbs == fVerbStop) {
1219 if (fNeedClose) {
1220 if (kLine_Verb == this->autoClose(pts)) {
1221 return kLine_Verb;
1222 }
1223 fNeedClose = false;
1224 return kClose_Verb;
1225 }
1226 return kDone_Verb;
1227 }
1228
1229 unsigned verb = *fVerbs++;
1230 const SkPoint* srcPts = fPts;
1231
1232 switch (verb) {
1233 case kMove_Verb:
1234 if (fNeedClose) {
1235 fVerbs -= 1;
1236 verb = this->autoClose(pts);
1237 if (verb == kClose_Verb) {
1238 fNeedClose = false;
1239 }
1240 return (Verb)verb;
1241 }
1242 if (fVerbs == fVerbStop) { // might be a trailing moveto
1243 return kDone_Verb;
1244 }
1245 fMoveTo = *srcPts;
1246 if (pts) {
1247 pts[0] = *srcPts;
1248 }
1249 srcPts += 1;
1250 fNeedMoveTo = kAfterCons_NeedMoveToState;
1251 fNeedClose = fForceClose;
1252 break;
1253 case kLine_Verb:
1254 if (this->cons_moveTo(pts)) {
1255 return kMove_Verb;
1256 }
1257 if (pts) {
1258 pts[1] = srcPts[0];
1259 }
1260 fLastPt = srcPts[0];
1261 fCloseLine = false;
1262 srcPts += 1;
1263 break;
1264 case kQuad_Verb:
1265 if (this->cons_moveTo(pts)) {
1266 return kMove_Verb;
1267 }
1268 if (pts) {
1269 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
1270 }
1271 fLastPt = srcPts[1];
1272 srcPts += 2;
1273 break;
1274 case kCubic_Verb:
1275 if (this->cons_moveTo(pts)) {
1276 return kMove_Verb;
1277 }
1278 if (pts) {
1279 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
1280 }
1281 fLastPt = srcPts[2];
1282 srcPts += 3;
1283 break;
1284 case kClose_Verb:
1285 verb = this->autoClose(pts);
1286 if (verb == kLine_Verb) {
1287 fVerbs -= 1;
1288 } else {
1289 fNeedClose = false;
1290 }
1291 fNeedMoveTo = kAfterClose_NeedMoveToState;
1292 break;
1293 }
1294 fPts = srcPts;
1295 return (Verb)verb;
1296}
1297
1298///////////////////////////////////////////////////////////////////////////////
1299
reed@android.com8a1c16f2008-12-17 15:59:43 +00001300/*
1301 Format in flattened buffer: [ptCount, verbCount, pts[], verbs[]]
1302*/
1303
reed@google.com73945652011-04-25 19:04:27 +00001304void SkPath::flatten(SkWriter32& buffer) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001305 SkDEBUGCODE(this->validate();)
1306
1307 buffer.write32(fPts.count());
1308 buffer.write32(fVerbs.count());
1309 buffer.write32(fFillType);
1310 buffer.writeMul4(fPts.begin(), sizeof(SkPoint) * fPts.count());
1311 buffer.writePad(fVerbs.begin(), fVerbs.count());
1312}
1313
reed@google.com73945652011-04-25 19:04:27 +00001314void SkPath::unflatten(SkReader32& buffer) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001315 fPts.setCount(buffer.readS32());
1316 fVerbs.setCount(buffer.readS32());
1317 fFillType = buffer.readS32();
1318 buffer.read(fPts.begin(), sizeof(SkPoint) * fPts.count());
1319 buffer.read(fVerbs.begin(), fVerbs.count());
reed@google.comabf15c12011-01-18 20:35:51 +00001320
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +00001321 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +00001322 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001323
1324 SkDEBUGCODE(this->validate();)
1325}
1326
1327///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00001328///////////////////////////////////////////////////////////////////////////////
1329
reed@android.com8a1c16f2008-12-17 15:59:43 +00001330void SkPath::dump(bool forceClose, const char title[]) const {
1331 Iter iter(*this, forceClose);
1332 SkPoint pts[4];
1333 Verb verb;
1334
1335 SkDebugf("path: forceClose=%s %s\n", forceClose ? "true" : "false",
1336 title ? title : "");
1337
1338 while ((verb = iter.next(pts)) != kDone_Verb) {
1339 switch (verb) {
1340 case kMove_Verb:
1341#ifdef SK_CAN_USE_FLOAT
1342 SkDebugf(" path: moveTo [%g %g]\n",
1343 SkScalarToFloat(pts[0].fX), SkScalarToFloat(pts[0].fY));
1344#else
1345 SkDebugf(" path: moveTo [%x %x]\n", pts[0].fX, pts[0].fY);
1346#endif
1347 break;
1348 case kLine_Verb:
1349#ifdef SK_CAN_USE_FLOAT
1350 SkDebugf(" path: lineTo [%g %g]\n",
1351 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY));
1352#else
1353 SkDebugf(" path: lineTo [%x %x]\n", pts[1].fX, pts[1].fY);
1354#endif
1355 break;
1356 case kQuad_Verb:
1357#ifdef SK_CAN_USE_FLOAT
1358 SkDebugf(" path: quadTo [%g %g] [%g %g]\n",
1359 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY),
1360 SkScalarToFloat(pts[2].fX), SkScalarToFloat(pts[2].fY));
1361#else
1362 SkDebugf(" path: quadTo [%x %x] [%x %x]\n",
1363 pts[1].fX, pts[1].fY, pts[2].fX, pts[2].fY);
1364#endif
1365 break;
1366 case kCubic_Verb:
1367#ifdef SK_CAN_USE_FLOAT
1368 SkDebugf(" path: cubeTo [%g %g] [%g %g] [%g %g]\n",
1369 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY),
1370 SkScalarToFloat(pts[2].fX), SkScalarToFloat(pts[2].fY),
1371 SkScalarToFloat(pts[3].fX), SkScalarToFloat(pts[3].fY));
1372#else
1373 SkDebugf(" path: cubeTo [%x %x] [%x %x] [%x %x]\n",
1374 pts[1].fX, pts[1].fY, pts[2].fX, pts[2].fY,
1375 pts[3].fX, pts[3].fY);
1376#endif
1377 break;
1378 case kClose_Verb:
1379 SkDebugf(" path: close\n");
1380 break;
1381 default:
1382 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
1383 verb = kDone_Verb; // stop the loop
1384 break;
1385 }
1386 }
1387 SkDebugf("path: done %s\n", title ? title : "");
1388}
1389
reed@android.come522ca52009-11-23 20:10:41 +00001390void SkPath::dump() const {
1391 this->dump(false);
1392}
1393
1394#ifdef SK_DEBUG
1395void SkPath::validate() const {
1396 SkASSERT(this != NULL);
1397 SkASSERT((fFillType & ~3) == 0);
1398 fPts.validate();
1399 fVerbs.validate();
reed@google.comabf15c12011-01-18 20:35:51 +00001400
reed@android.come522ca52009-11-23 20:10:41 +00001401 if (!fBoundsIsDirty) {
1402 SkRect bounds;
1403 compute_pt_bounds(&bounds, fPts);
1404 if (fPts.count() <= 1) {
1405 // if we're empty, fBounds may be empty but translated, so we can't
1406 // necessarily compare to bounds directly
1407 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
1408 // be [2, 2, 2, 2]
1409 SkASSERT(bounds.isEmpty());
1410 SkASSERT(fBounds.isEmpty());
1411 } else {
1412 fBounds.contains(bounds);
1413 }
1414 }
1415}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001416#endif
reed@android.come522ca52009-11-23 20:10:41 +00001417
reed@google.com04863fa2011-05-15 04:08:24 +00001418///////////////////////////////////////////////////////////////////////////////
1419
1420/**
1421 * Returns -1 || 0 || 1 depending on the sign of value:
1422 * -1 if value < 0
1423 * 0 if vlaue == 0
1424 * 1 if value > 0
1425 */
1426static int SkScalarSign(SkScalar value) {
1427 return value < 0 ? -1 : (value > 0);
1428}
1429
reed@google.com0b7b9822011-05-16 12:29:27 +00001430static int sign(SkScalar x) { return x < 0; }
1431#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00001432
reed@google.com04863fa2011-05-15 04:08:24 +00001433static int CrossProductSign(const SkVector& a, const SkVector& b) {
1434 return SkScalarSign(SkPoint::CrossProduct(a, b));
1435}
1436
1437// only valid for a single contour
1438struct Convexicator {
reed@google.comb54455e2011-05-16 14:16:04 +00001439 Convexicator() : fPtCount(0), fConvexity(SkPath::kConvex_Convexity) {
reed@google.com04863fa2011-05-15 04:08:24 +00001440 fSign = 0;
1441 // warnings
1442 fCurrPt.set(0, 0);
1443 fVec0.set(0, 0);
1444 fVec1.set(0, 0);
1445 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00001446
1447 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00001448 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00001449 }
1450
1451 SkPath::Convexity getConvexity() const { return fConvexity; }
1452
1453 void addPt(const SkPoint& pt) {
1454 if (SkPath::kConcave_Convexity == fConvexity) {
1455 return;
1456 }
1457
1458 if (0 == fPtCount) {
1459 fCurrPt = pt;
1460 ++fPtCount;
1461 } else {
1462 SkVector vec = pt - fCurrPt;
1463 if (vec.fX || vec.fY) {
1464 fCurrPt = pt;
1465 if (++fPtCount == 2) {
1466 fFirstVec = fVec1 = vec;
1467 } else {
1468 SkASSERT(fPtCount > 2);
1469 this->addVec(vec);
1470 }
reed@google.com85b6e392011-05-15 20:25:17 +00001471
1472 int sx = sign(vec.fX);
1473 int sy = sign(vec.fY);
1474 fDx += (sx != fSx);
1475 fDy += (sy != fSy);
1476 fSx = sx;
1477 fSy = sy;
1478
1479 if (fDx > 3 || fDy > 3) {
1480 fConvexity = SkPath::kConcave_Convexity;
1481 }
reed@google.com04863fa2011-05-15 04:08:24 +00001482 }
1483 }
1484 }
1485
1486 void close() {
1487 if (fPtCount > 2) {
1488 this->addVec(fFirstVec);
1489 }
1490 }
1491
1492private:
1493 void addVec(const SkVector& vec) {
1494 SkASSERT(vec.fX || vec.fY);
1495 fVec0 = fVec1;
1496 fVec1 = vec;
1497 int sign = CrossProductSign(fVec0, fVec1);
1498 if (0 == fSign) {
1499 fSign = sign;
1500 } else if (sign) {
reed@google.comb54455e2011-05-16 14:16:04 +00001501 if (fSign != sign) {
reed@google.com04863fa2011-05-15 04:08:24 +00001502 fConvexity = SkPath::kConcave_Convexity;
1503 }
1504 }
1505 }
1506
1507 SkPoint fCurrPt;
1508 SkVector fVec0, fVec1, fFirstVec;
1509 int fPtCount; // non-degenerate points
1510 int fSign;
1511 SkPath::Convexity fConvexity;
reed@google.com0b7b9822011-05-16 12:29:27 +00001512 int fDx, fDy, fSx, fSy;
reed@google.com04863fa2011-05-15 04:08:24 +00001513};
1514
1515SkPath::Convexity SkPath::ComputeConvexity(const SkPath& path) {
1516 SkPoint pts[4];
1517 SkPath::Verb verb;
1518 SkPath::Iter iter(path, true);
1519
1520 int contourCount = 0;
1521 int count;
1522 Convexicator state;
1523
1524 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
1525 switch (verb) {
1526 case kMove_Verb:
1527 if (++contourCount > 1) {
1528 return kConcave_Convexity;
1529 }
1530 pts[1] = pts[0];
1531 count = 1;
1532 break;
1533 case kLine_Verb: count = 1; break;
1534 case kQuad_Verb: count = 2; break;
1535 case kCubic_Verb: count = 3; break;
1536 case kClose_Verb:
1537 state.close();
1538 count = 0;
1539 break;
1540 default:
1541 SkASSERT(!"bad verb");
1542 return kConcave_Convexity;
1543 }
1544
1545 for (int i = 1; i <= count; i++) {
1546 state.addPt(pts[i]);
1547 }
1548 // early exit
1549 if (kConcave_Convexity == state.getConvexity()) {
1550 return kConcave_Convexity;
1551 }
1552 }
1553 return state.getConvexity();
1554}