blob: de3bb2e5e825067b2c0f1f1b1c0a4a31a115d36d [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
bsalomon@google.com2ec72802011-09-21 21:46:03 +000091SkPath::SkPath()
92 : fFillType(kWinding_FillType)
93 , fBoundsIsDirty(true) {
reed@google.com04863fa2011-05-15 04:08:24 +000094 fConvexity = kUnknown_Convexity;
reed@google.com10296cc2011-09-21 12:29:05 +000095 fSegmentMask = 0;
djsollen@google.com56c69772011-11-08 19:00:26 +000096#ifdef SK_BUILD_FOR_ANDROID
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +000097 fGenerationID = 0;
98#endif
reed@android.com6b82d1a2009-06-03 02:35:01 +000099}
reed@android.com8a1c16f2008-12-17 15:59:43 +0000100
101SkPath::SkPath(const SkPath& src) {
102 SkDEBUGCODE(src.validate();)
103 *this = src;
djsollen@google.com56c69772011-11-08 19:00:26 +0000104#ifdef SK_BUILD_FOR_ANDROID
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000105 // the assignment operator above increments the ID so correct for that here
106 fGenerationID--;
107#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000108}
109
110SkPath::~SkPath() {
111 SkDEBUGCODE(this->validate();)
112}
113
114SkPath& SkPath::operator=(const SkPath& src) {
115 SkDEBUGCODE(src.validate();)
116
117 if (this != &src) {
reed@android.comd252db02009-04-01 18:31:44 +0000118 fBounds = src.fBounds;
119 fPts = src.fPts;
120 fVerbs = src.fVerbs;
121 fFillType = src.fFillType;
122 fBoundsIsDirty = src.fBoundsIsDirty;
reed@google.com04863fa2011-05-15 04:08:24 +0000123 fConvexity = src.fConvexity;
reed@google.com10296cc2011-09-21 12:29:05 +0000124 fSegmentMask = src.fSegmentMask;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000125 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000126 }
127 SkDEBUGCODE(this->validate();)
128 return *this;
129}
130
reed@android.com3abec1d2009-03-02 05:36:20 +0000131bool operator==(const SkPath& a, const SkPath& b) {
reed@android.com6b82d1a2009-06-03 02:35:01 +0000132 // note: don't need to look at isConvex or bounds, since just comparing the
133 // raw data is sufficient.
reed@google.com10296cc2011-09-21 12:29:05 +0000134
135 // We explicitly check fSegmentMask as a quick-reject. We could skip it,
136 // since it is only a cache of info in the fVerbs, but its a fast way to
137 // notice a difference
138
reed@android.com3abec1d2009-03-02 05:36:20 +0000139 return &a == &b ||
reed@google.com10296cc2011-09-21 12:29:05 +0000140 (a.fFillType == b.fFillType && a.fSegmentMask == b.fSegmentMask &&
141 a.fVerbs == b.fVerbs && a.fPts == b.fPts);
reed@android.com3abec1d2009-03-02 05:36:20 +0000142}
143
reed@android.com8a1c16f2008-12-17 15:59:43 +0000144void SkPath::swap(SkPath& other) {
145 SkASSERT(&other != NULL);
146
147 if (this != &other) {
reed@android.comd252db02009-04-01 18:31:44 +0000148 SkTSwap<SkRect>(fBounds, other.fBounds);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000149 fPts.swap(other.fPts);
150 fVerbs.swap(other.fVerbs);
151 SkTSwap<uint8_t>(fFillType, other.fFillType);
reed@android.comd252db02009-04-01 18:31:44 +0000152 SkTSwap<uint8_t>(fBoundsIsDirty, other.fBoundsIsDirty);
reed@google.com04863fa2011-05-15 04:08:24 +0000153 SkTSwap<uint8_t>(fConvexity, other.fConvexity);
reed@google.com10296cc2011-09-21 12:29:05 +0000154 SkTSwap<uint8_t>(fSegmentMask, other.fSegmentMask);
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000155 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000156 }
157}
158
djsollen@google.com56c69772011-11-08 19:00:26 +0000159#ifdef SK_BUILD_FOR_ANDROID
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000160uint32_t SkPath::getGenerationID() const {
161 return fGenerationID;
162}
163#endif
164
reed@android.com8a1c16f2008-12-17 15:59:43 +0000165void SkPath::reset() {
166 SkDEBUGCODE(this->validate();)
167
168 fPts.reset();
169 fVerbs.reset();
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000170 GEN_ID_INC;
reed@android.comd252db02009-04-01 18:31:44 +0000171 fBoundsIsDirty = true;
reed@google.com04863fa2011-05-15 04:08:24 +0000172 fConvexity = kUnknown_Convexity;
reed@google.com10296cc2011-09-21 12:29:05 +0000173 fSegmentMask = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000174}
175
176void SkPath::rewind() {
177 SkDEBUGCODE(this->validate();)
178
179 fPts.rewind();
180 fVerbs.rewind();
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000181 GEN_ID_INC;
reed@android.comd252db02009-04-01 18:31:44 +0000182 fBoundsIsDirty = true;
reed@google.com04863fa2011-05-15 04:08:24 +0000183 fConvexity = kUnknown_Convexity;
reed@google.com10296cc2011-09-21 12:29:05 +0000184 fSegmentMask = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000185}
186
187bool SkPath::isEmpty() const {
188 SkDEBUGCODE(this->validate();)
189
190 int count = fVerbs.count();
191 return count == 0 || (count == 1 && fVerbs[0] == kMove_Verb);
192}
193
caryclark@google.comf1316942011-07-26 19:54:45 +0000194/*
195 Determines if path is a rect by keeping track of changes in direction
196 and looking for a loop either clockwise or counterclockwise.
197
198 The direction is computed such that:
199 0: vertical up
200 1: horizontal right
201 2: vertical down
202 3: horizontal left
203
204A rectangle cycles up/right/down/left or up/left/down/right.
205
206The test fails if:
207 The path is closed, and followed by a line.
208 A second move creates a new endpoint.
209 A diagonal line is parsed.
210 There's more than four changes of direction.
211 There's a discontinuity on the line (e.g., a move in the middle)
212 The line reverses direction.
213 The rectangle doesn't complete a cycle.
214 The path contains a quadratic or cubic.
215 The path contains fewer than four points.
216 The final point isn't equal to the first point.
217
218It's OK if the path has:
219 Several colinear line segments composing a rectangle side.
220 Single points on the rectangle side.
221
222The direction takes advantage of the corners found since opposite sides
223must travel in opposite directions.
224
225FIXME: Allow colinear quads and cubics to be treated like lines.
226FIXME: If the API passes fill-only, return true if the filled stroke
227 is a rectangle, though the caller failed to close the path.
228 */
229bool SkPath::isRect(SkRect* rect) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000230 SkDEBUGCODE(this->validate();)
reed@google.comabf15c12011-01-18 20:35:51 +0000231
caryclark@google.comf1316942011-07-26 19:54:45 +0000232 int corners = 0;
233 SkPoint first, last;
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000234 first.set(0, 0);
235 last.set(0, 0);
236 int firstDirection = 0;
237 int lastDirection = 0;
238 int nextDirection = 0;
239 bool closedOrMoved = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000240 bool autoClose = false;
241 const uint8_t* verbs = fVerbs.begin();
242 const uint8_t* verbStop = fVerbs.end();
243 const SkPoint* pts = fPts.begin();
244 while (verbs != verbStop) {
245 switch (*verbs++) {
246 case kClose_Verb:
247 pts = fPts.begin();
248 autoClose = true;
249 case kLine_Verb: {
250 SkScalar left = last.fX;
251 SkScalar top = last.fY;
252 SkScalar right = pts->fX;
253 SkScalar bottom = pts->fY;
254 ++pts;
255 if (left != right && top != bottom) {
256 return false; // diagonal
257 }
258 if (left == right && top == bottom) {
259 break; // single point on side OK
260 }
261 nextDirection = (left != right) << 0 |
262 (left < right || top < bottom) << 1;
263 if (0 == corners) {
264 firstDirection = nextDirection;
265 first = last;
266 last = pts[-1];
267 corners = 1;
268 closedOrMoved = false;
269 break;
270 }
271 if (closedOrMoved) {
272 return false; // closed followed by a line
273 }
274 closedOrMoved = autoClose;
275 if (lastDirection != nextDirection) {
276 if (++corners > 4) {
277 return false; // too many direction changes
278 }
279 }
280 last = pts[-1];
281 if (lastDirection == nextDirection) {
282 break; // colinear segment
283 }
284 // Possible values for corners are 2, 3, and 4.
285 // When corners == 3, nextDirection opposes firstDirection.
286 // Otherwise, nextDirection at corner 2 opposes corner 4.
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000287 int turn = firstDirection ^ (corners - 1);
caryclark@google.comf1316942011-07-26 19:54:45 +0000288 int directionCycle = 3 == corners ? 0 : nextDirection ^ turn;
289 if ((directionCycle ^ turn) != nextDirection) {
290 return false; // direction didn't follow cycle
291 }
292 break;
293 }
294 case kQuad_Verb:
295 case kCubic_Verb:
296 return false; // quadratic, cubic not allowed
297 case kMove_Verb:
298 last = *pts++;
299 closedOrMoved = true;
300 break;
301 }
302 lastDirection = nextDirection;
303 }
304 // Success if 4 corners and first point equals last
305 bool result = 4 == corners && first == last;
306 if (result && rect) {
307 *rect = getBounds();
308 }
309 return result;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000310}
311
312int SkPath::getPoints(SkPoint copy[], int max) const {
313 SkDEBUGCODE(this->validate();)
314
315 SkASSERT(max >= 0);
316 int count = fPts.count();
317 if (copy && max > 0 && count > 0) {
318 memcpy(copy, fPts.begin(), sizeof(SkPoint) * SkMin32(max, count));
319 }
320 return count;
321}
322
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000323SkPoint SkPath::getPoint(int index) const {
324 if ((unsigned)index < (unsigned)fPts.count()) {
325 return fPts[index];
326 }
327 return SkPoint::Make(0, 0);
328}
329
reed@google.com294dd7b2011-10-11 11:58:32 +0000330bool SkPath::getLastPt(SkPoint* lastPt) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000331 SkDEBUGCODE(this->validate();)
332
reed@google.com294dd7b2011-10-11 11:58:32 +0000333 int count = fPts.count();
334 if (count > 0) {
335 if (lastPt) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000336 *lastPt = fPts[count - 1];
337 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000338 return true;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000339 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000340 if (lastPt) {
341 lastPt->set(0, 0);
342 }
343 return false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000344}
345
346void SkPath::setLastPt(SkScalar x, SkScalar y) {
347 SkDEBUGCODE(this->validate();)
348
349 int count = fPts.count();
350 if (count == 0) {
351 this->moveTo(x, y);
352 } else {
353 fPts[count - 1].set(x, y);
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000354 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000355 }
356}
357
reed@android.comd252db02009-04-01 18:31:44 +0000358void SkPath::computeBounds() const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000359 SkDEBUGCODE(this->validate();)
reed@android.comd252db02009-04-01 18:31:44 +0000360 SkASSERT(fBoundsIsDirty);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000361
reed@android.comd252db02009-04-01 18:31:44 +0000362 fBoundsIsDirty = false;
363 compute_pt_bounds(&fBounds, fPts);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000364}
365
reed@google.com04863fa2011-05-15 04:08:24 +0000366void SkPath::setConvexity(Convexity c) {
367 if (fConvexity != c) {
368 fConvexity = c;
369 GEN_ID_INC;
370 }
371}
372
reed@android.com8a1c16f2008-12-17 15:59:43 +0000373//////////////////////////////////////////////////////////////////////////////
374// Construction methods
375
reed@google.comb54455e2011-05-16 14:16:04 +0000376#define DIRTY_AFTER_EDIT \
377 do { \
378 fBoundsIsDirty = true; \
379 fConvexity = kUnknown_Convexity;\
380 } while (0)
381
reed@android.com8a1c16f2008-12-17 15:59:43 +0000382void SkPath::incReserve(U16CPU inc) {
383 SkDEBUGCODE(this->validate();)
384
385 fVerbs.setReserve(fVerbs.count() + inc);
386 fPts.setReserve(fPts.count() + inc);
387
388 SkDEBUGCODE(this->validate();)
389}
390
391void SkPath::moveTo(SkScalar x, SkScalar y) {
392 SkDEBUGCODE(this->validate();)
393
394 int vc = fVerbs.count();
395 SkPoint* pt;
396
397 if (vc > 0 && fVerbs[vc - 1] == kMove_Verb) {
398 pt = &fPts[fPts.count() - 1];
399 } else {
400 pt = fPts.append();
401 *fVerbs.append() = kMove_Verb;
402 }
403 pt->set(x, y);
404
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000405 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000406 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000407}
408
409void SkPath::rMoveTo(SkScalar x, SkScalar y) {
410 SkPoint pt;
411 this->getLastPt(&pt);
412 this->moveTo(pt.fX + x, pt.fY + y);
413}
414
415void SkPath::lineTo(SkScalar x, SkScalar y) {
416 SkDEBUGCODE(this->validate();)
417
418 if (fVerbs.count() == 0) {
419 fPts.append()->set(0, 0);
420 *fVerbs.append() = kMove_Verb;
421 }
422 fPts.append()->set(x, y);
423 *fVerbs.append() = kLine_Verb;
reed@google.com10296cc2011-09-21 12:29:05 +0000424 fSegmentMask |= kLine_SegmentMask;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000425
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000426 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000427 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000428}
429
430void SkPath::rLineTo(SkScalar x, SkScalar y) {
431 SkPoint pt;
432 this->getLastPt(&pt);
433 this->lineTo(pt.fX + x, pt.fY + y);
434}
435
436void SkPath::quadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
437 SkDEBUGCODE(this->validate();)
438
439 if (fVerbs.count() == 0) {
440 fPts.append()->set(0, 0);
441 *fVerbs.append() = kMove_Verb;
442 }
443
444 SkPoint* pts = fPts.append(2);
445 pts[0].set(x1, y1);
446 pts[1].set(x2, y2);
447 *fVerbs.append() = kQuad_Verb;
reed@google.com10296cc2011-09-21 12:29:05 +0000448 fSegmentMask |= kQuad_SegmentMask;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000449
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000450 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000451 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000452}
453
454void SkPath::rQuadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
455 SkPoint pt;
456 this->getLastPt(&pt);
457 this->quadTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2);
458}
459
460void SkPath::cubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
461 SkScalar x3, SkScalar y3) {
462 SkDEBUGCODE(this->validate();)
463
464 if (fVerbs.count() == 0) {
465 fPts.append()->set(0, 0);
466 *fVerbs.append() = kMove_Verb;
467 }
468 SkPoint* pts = fPts.append(3);
469 pts[0].set(x1, y1);
470 pts[1].set(x2, y2);
471 pts[2].set(x3, y3);
472 *fVerbs.append() = kCubic_Verb;
reed@google.com10296cc2011-09-21 12:29:05 +0000473 fSegmentMask |= kCubic_SegmentMask;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000474
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000475 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000476 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000477}
478
479void SkPath::rCubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
480 SkScalar x3, SkScalar y3) {
481 SkPoint pt;
482 this->getLastPt(&pt);
483 this->cubicTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2,
484 pt.fX + x3, pt.fY + y3);
485}
486
487void SkPath::close() {
488 SkDEBUGCODE(this->validate();)
489
490 int count = fVerbs.count();
491 if (count > 0) {
492 switch (fVerbs[count - 1]) {
493 case kLine_Verb:
494 case kQuad_Verb:
495 case kCubic_Verb:
496 *fVerbs.append() = kClose_Verb;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000497 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000498 break;
499 default:
500 // don't add a close if the prev wasn't a primitive
501 break;
502 }
503 }
504}
505
506///////////////////////////////////////////////////////////////////////////////
reed@google.comabf15c12011-01-18 20:35:51 +0000507
reed@android.com8a1c16f2008-12-17 15:59:43 +0000508void SkPath::addRect(const SkRect& rect, Direction dir) {
509 this->addRect(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, dir);
510}
511
512void SkPath::addRect(SkScalar left, SkScalar top, SkScalar right,
513 SkScalar bottom, Direction dir) {
514 SkAutoPathBoundsUpdate apbu(this, left, top, right, bottom);
515
516 this->incReserve(5);
517
518 this->moveTo(left, top);
519 if (dir == kCCW_Direction) {
520 this->lineTo(left, bottom);
521 this->lineTo(right, bottom);
522 this->lineTo(right, top);
523 } else {
524 this->lineTo(right, top);
525 this->lineTo(right, bottom);
526 this->lineTo(left, bottom);
527 }
528 this->close();
529}
530
531#define CUBIC_ARC_FACTOR ((SK_ScalarSqrt2 - SK_Scalar1) * 4 / 3)
532
533void SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
534 Direction dir) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000535 SkScalar w = rect.width();
536 SkScalar halfW = SkScalarHalf(w);
537 SkScalar h = rect.height();
538 SkScalar halfH = SkScalarHalf(h);
539
540 if (halfW <= 0 || halfH <= 0) {
541 return;
542 }
543
reed@google.comabf15c12011-01-18 20:35:51 +0000544 bool skip_hori = rx >= halfW;
545 bool skip_vert = ry >= halfH;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000546
547 if (skip_hori && skip_vert) {
548 this->addOval(rect, dir);
549 return;
550 }
reed@google.comabf15c12011-01-18 20:35:51 +0000551
552 SkAutoPathBoundsUpdate apbu(this, rect);
553
reed@android.com8a1c16f2008-12-17 15:59:43 +0000554 if (skip_hori) {
555 rx = halfW;
556 } else if (skip_vert) {
557 ry = halfH;
558 }
559
560 SkScalar sx = SkScalarMul(rx, CUBIC_ARC_FACTOR);
561 SkScalar sy = SkScalarMul(ry, CUBIC_ARC_FACTOR);
562
563 this->incReserve(17);
564 this->moveTo(rect.fRight - rx, rect.fTop);
565 if (dir == kCCW_Direction) {
566 if (!skip_hori) {
567 this->lineTo(rect.fLeft + rx, rect.fTop); // top
568 }
569 this->cubicTo(rect.fLeft + rx - sx, rect.fTop,
570 rect.fLeft, rect.fTop + ry - sy,
571 rect.fLeft, rect.fTop + ry); // top-left
572 if (!skip_vert) {
573 this->lineTo(rect.fLeft, rect.fBottom - ry); // left
574 }
575 this->cubicTo(rect.fLeft, rect.fBottom - ry + sy,
576 rect.fLeft + rx - sx, rect.fBottom,
577 rect.fLeft + rx, rect.fBottom); // bot-left
578 if (!skip_hori) {
579 this->lineTo(rect.fRight - rx, rect.fBottom); // bottom
580 }
581 this->cubicTo(rect.fRight - rx + sx, rect.fBottom,
582 rect.fRight, rect.fBottom - ry + sy,
583 rect.fRight, rect.fBottom - ry); // bot-right
584 if (!skip_vert) {
585 this->lineTo(rect.fRight, rect.fTop + ry);
586 }
587 this->cubicTo(rect.fRight, rect.fTop + ry - sy,
588 rect.fRight - rx + sx, rect.fTop,
589 rect.fRight - rx, rect.fTop); // top-right
590 } else {
591 this->cubicTo(rect.fRight - rx + sx, rect.fTop,
592 rect.fRight, rect.fTop + ry - sy,
593 rect.fRight, rect.fTop + ry); // top-right
594 if (!skip_vert) {
595 this->lineTo(rect.fRight, rect.fBottom - ry);
596 }
597 this->cubicTo(rect.fRight, rect.fBottom - ry + sy,
598 rect.fRight - rx + sx, rect.fBottom,
599 rect.fRight - rx, rect.fBottom); // bot-right
600 if (!skip_hori) {
601 this->lineTo(rect.fLeft + rx, rect.fBottom); // bottom
602 }
603 this->cubicTo(rect.fLeft + rx - sx, rect.fBottom,
604 rect.fLeft, rect.fBottom - ry + sy,
605 rect.fLeft, rect.fBottom - ry); // bot-left
606 if (!skip_vert) {
607 this->lineTo(rect.fLeft, rect.fTop + ry); // left
608 }
609 this->cubicTo(rect.fLeft, rect.fTop + ry - sy,
610 rect.fLeft + rx - sx, rect.fTop,
611 rect.fLeft + rx, rect.fTop); // top-left
612 if (!skip_hori) {
613 this->lineTo(rect.fRight - rx, rect.fTop); // top
614 }
615 }
616 this->close();
617}
618
619static void add_corner_arc(SkPath* path, const SkRect& rect,
620 SkScalar rx, SkScalar ry, int startAngle,
621 SkPath::Direction dir, bool forceMoveTo) {
622 rx = SkMinScalar(SkScalarHalf(rect.width()), rx);
623 ry = SkMinScalar(SkScalarHalf(rect.height()), ry);
reed@google.comabf15c12011-01-18 20:35:51 +0000624
reed@android.com8a1c16f2008-12-17 15:59:43 +0000625 SkRect r;
626 r.set(-rx, -ry, rx, ry);
627
628 switch (startAngle) {
629 case 0:
630 r.offset(rect.fRight - r.fRight, rect.fBottom - r.fBottom);
631 break;
632 case 90:
633 r.offset(rect.fLeft - r.fLeft, rect.fBottom - r.fBottom);
634 break;
635 case 180: r.offset(rect.fLeft - r.fLeft, rect.fTop - r.fTop); break;
636 case 270: r.offset(rect.fRight - r.fRight, rect.fTop - r.fTop); break;
637 default: SkASSERT(!"unexpected startAngle in add_corner_arc");
638 }
reed@google.comabf15c12011-01-18 20:35:51 +0000639
reed@android.com8a1c16f2008-12-17 15:59:43 +0000640 SkScalar start = SkIntToScalar(startAngle);
641 SkScalar sweep = SkIntToScalar(90);
642 if (SkPath::kCCW_Direction == dir) {
643 start += sweep;
644 sweep = -sweep;
645 }
reed@google.comabf15c12011-01-18 20:35:51 +0000646
reed@android.com8a1c16f2008-12-17 15:59:43 +0000647 path->arcTo(r, start, sweep, forceMoveTo);
648}
649
650void SkPath::addRoundRect(const SkRect& rect, const SkScalar rad[],
651 Direction dir) {
reed@google.com44b2c732011-01-18 20:55:57 +0000652 // abort before we invoke SkAutoPathBoundsUpdate()
653 if (rect.isEmpty()) {
654 return;
655 }
656
reed@android.com8a1c16f2008-12-17 15:59:43 +0000657 SkAutoPathBoundsUpdate apbu(this, rect);
658
659 if (kCW_Direction == dir) {
660 add_corner_arc(this, rect, rad[0], rad[1], 180, dir, true);
661 add_corner_arc(this, rect, rad[2], rad[3], 270, dir, false);
662 add_corner_arc(this, rect, rad[4], rad[5], 0, dir, false);
663 add_corner_arc(this, rect, rad[6], rad[7], 90, dir, false);
664 } else {
665 add_corner_arc(this, rect, rad[0], rad[1], 180, dir, true);
666 add_corner_arc(this, rect, rad[6], rad[7], 90, dir, false);
667 add_corner_arc(this, rect, rad[4], rad[5], 0, dir, false);
668 add_corner_arc(this, rect, rad[2], rad[3], 270, dir, false);
669 }
670 this->close();
671}
672
673void SkPath::addOval(const SkRect& oval, Direction dir) {
674 SkAutoPathBoundsUpdate apbu(this, oval);
675
676 SkScalar cx = oval.centerX();
677 SkScalar cy = oval.centerY();
678 SkScalar rx = SkScalarHalf(oval.width());
679 SkScalar ry = SkScalarHalf(oval.height());
680#if 0 // these seem faster than using quads (1/2 the number of edges)
681 SkScalar sx = SkScalarMul(rx, CUBIC_ARC_FACTOR);
682 SkScalar sy = SkScalarMul(ry, CUBIC_ARC_FACTOR);
683
684 this->incReserve(13);
685 this->moveTo(cx + rx, cy);
686 if (dir == kCCW_Direction) {
687 this->cubicTo(cx + rx, cy - sy, cx + sx, cy - ry, cx, cy - ry);
688 this->cubicTo(cx - sx, cy - ry, cx - rx, cy - sy, cx - rx, cy);
689 this->cubicTo(cx - rx, cy + sy, cx - sx, cy + ry, cx, cy + ry);
690 this->cubicTo(cx + sx, cy + ry, cx + rx, cy + sy, cx + rx, cy);
691 } else {
692 this->cubicTo(cx + rx, cy + sy, cx + sx, cy + ry, cx, cy + ry);
693 this->cubicTo(cx - sx, cy + ry, cx - rx, cy + sy, cx - rx, cy);
694 this->cubicTo(cx - rx, cy - sy, cx - sx, cy - ry, cx, cy - ry);
695 this->cubicTo(cx + sx, cy - ry, cx + rx, cy - sy, cx + rx, cy);
696 }
697#else
698 SkScalar sx = SkScalarMul(rx, SK_ScalarTanPIOver8);
699 SkScalar sy = SkScalarMul(ry, SK_ScalarTanPIOver8);
700 SkScalar mx = SkScalarMul(rx, SK_ScalarRoot2Over2);
701 SkScalar my = SkScalarMul(ry, SK_ScalarRoot2Over2);
702
703 /*
704 To handle imprecision in computing the center and radii, we revert to
705 the provided bounds when we can (i.e. use oval.fLeft instead of cx-rx)
706 to ensure that we don't exceed the oval's bounds *ever*, since we want
707 to use oval for our fast-bounds, rather than have to recompute it.
708 */
709 const SkScalar L = oval.fLeft; // cx - rx
710 const SkScalar T = oval.fTop; // cy - ry
711 const SkScalar R = oval.fRight; // cx + rx
712 const SkScalar B = oval.fBottom; // cy + ry
713
714 this->incReserve(17); // 8 quads + close
715 this->moveTo(R, cy);
716 if (dir == kCCW_Direction) {
717 this->quadTo( R, cy - sy, cx + mx, cy - my);
718 this->quadTo(cx + sx, T, cx , T);
719 this->quadTo(cx - sx, T, cx - mx, cy - my);
720 this->quadTo( L, cy - sy, L, cy );
721 this->quadTo( L, cy + sy, cx - mx, cy + my);
722 this->quadTo(cx - sx, B, cx , B);
723 this->quadTo(cx + sx, B, cx + mx, cy + my);
724 this->quadTo( R, cy + sy, R, cy );
725 } else {
726 this->quadTo( R, cy + sy, cx + mx, cy + my);
727 this->quadTo(cx + sx, B, cx , B);
728 this->quadTo(cx - sx, B, cx - mx, cy + my);
729 this->quadTo( L, cy + sy, L, cy );
730 this->quadTo( L, cy - sy, cx - mx, cy - my);
731 this->quadTo(cx - sx, T, cx , T);
732 this->quadTo(cx + sx, T, cx + mx, cy - my);
733 this->quadTo( R, cy - sy, R, cy );
734 }
735#endif
736 this->close();
737}
738
739void SkPath::addCircle(SkScalar x, SkScalar y, SkScalar r, Direction dir) {
740 if (r > 0) {
741 SkRect rect;
742 rect.set(x - r, y - r, x + r, y + r);
743 this->addOval(rect, dir);
744 }
745}
746
747#include "SkGeometry.h"
748
749static int build_arc_points(const SkRect& oval, SkScalar startAngle,
750 SkScalar sweepAngle,
751 SkPoint pts[kSkBuildQuadArcStorage]) {
752 SkVector start, stop;
753
754 start.fY = SkScalarSinCos(SkDegreesToRadians(startAngle), &start.fX);
755 stop.fY = SkScalarSinCos(SkDegreesToRadians(startAngle + sweepAngle),
756 &stop.fX);
reed@android.comeebf5cb2010-02-09 18:30:59 +0000757
758 /* If the sweep angle is nearly (but less than) 360, then due to precision
759 loss in radians-conversion and/or sin/cos, we may end up with coincident
760 vectors, which will fool SkBuildQuadArc into doing nothing (bad) instead
761 of drawing a nearly complete circle (good).
762 e.g. canvas.drawArc(0, 359.99, ...)
763 -vs- canvas.drawArc(0, 359.9, ...)
764 We try to detect this edge case, and tweak the stop vector
765 */
766 if (start == stop) {
767 SkScalar sw = SkScalarAbs(sweepAngle);
768 if (sw < SkIntToScalar(360) && sw > SkIntToScalar(359)) {
769 SkScalar stopRad = SkDegreesToRadians(startAngle + sweepAngle);
770 // make a guess at a tiny angle (in radians) to tweak by
771 SkScalar deltaRad = SkScalarCopySign(SK_Scalar1/512, sweepAngle);
772 // not sure how much will be enough, so we use a loop
773 do {
774 stopRad -= deltaRad;
775 stop.fY = SkScalarSinCos(stopRad, &stop.fX);
776 } while (start == stop);
777 }
778 }
779
reed@android.com8a1c16f2008-12-17 15:59:43 +0000780 SkMatrix matrix;
reed@google.comabf15c12011-01-18 20:35:51 +0000781
reed@android.com8a1c16f2008-12-17 15:59:43 +0000782 matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height()));
783 matrix.postTranslate(oval.centerX(), oval.centerY());
reed@google.comabf15c12011-01-18 20:35:51 +0000784
reed@android.com8a1c16f2008-12-17 15:59:43 +0000785 return SkBuildQuadArc(start, stop,
786 sweepAngle > 0 ? kCW_SkRotationDirection : kCCW_SkRotationDirection,
787 &matrix, pts);
788}
789
790void SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
791 bool forceMoveTo) {
792 if (oval.width() < 0 || oval.height() < 0) {
793 return;
794 }
795
796 SkPoint pts[kSkBuildQuadArcStorage];
797 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
798 SkASSERT((count & 1) == 1);
799
800 if (fVerbs.count() == 0) {
801 forceMoveTo = true;
802 }
803 this->incReserve(count);
804 forceMoveTo ? this->moveTo(pts[0]) : this->lineTo(pts[0]);
805 for (int i = 1; i < count; i += 2) {
806 this->quadTo(pts[i], pts[i+1]);
807 }
808}
809
810void SkPath::addArc(const SkRect& oval, SkScalar startAngle,
811 SkScalar sweepAngle) {
812 if (oval.isEmpty() || 0 == sweepAngle) {
813 return;
814 }
815
816 const SkScalar kFullCircleAngle = SkIntToScalar(360);
817
818 if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
819 this->addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction);
820 return;
821 }
822
823 SkPoint pts[kSkBuildQuadArcStorage];
824 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
825
826 this->incReserve(count);
827 this->moveTo(pts[0]);
828 for (int i = 1; i < count; i += 2) {
829 this->quadTo(pts[i], pts[i+1]);
830 }
831}
832
833/*
834 Need to handle the case when the angle is sharp, and our computed end-points
835 for the arc go behind pt1 and/or p2...
836*/
837void SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
838 SkScalar radius) {
839 SkVector before, after;
reed@google.comabf15c12011-01-18 20:35:51 +0000840
reed@android.com8a1c16f2008-12-17 15:59:43 +0000841 // need to know our prev pt so we can construct tangent vectors
842 {
843 SkPoint start;
844 this->getLastPt(&start);
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +0000845 // Handle degenerate cases by adding a line to the first point and
846 // bailing out.
847 if ((x1 == start.fX && y1 == start.fY) ||
848 (x1 == x2 && y1 == y2) ||
849 radius == 0) {
850 this->lineTo(x1, y1);
851 return;
852 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000853 before.setNormalize(x1 - start.fX, y1 - start.fY);
854 after.setNormalize(x2 - x1, y2 - y1);
855 }
reed@google.comabf15c12011-01-18 20:35:51 +0000856
reed@android.com8a1c16f2008-12-17 15:59:43 +0000857 SkScalar cosh = SkPoint::DotProduct(before, after);
858 SkScalar sinh = SkPoint::CrossProduct(before, after);
859
860 if (SkScalarNearlyZero(sinh)) { // angle is too tight
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +0000861 this->lineTo(x1, y1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000862 return;
863 }
reed@google.comabf15c12011-01-18 20:35:51 +0000864
reed@android.com8a1c16f2008-12-17 15:59:43 +0000865 SkScalar dist = SkScalarMulDiv(radius, SK_Scalar1 - cosh, sinh);
866 if (dist < 0) {
867 dist = -dist;
868 }
869
870 SkScalar xx = x1 - SkScalarMul(dist, before.fX);
871 SkScalar yy = y1 - SkScalarMul(dist, before.fY);
872 SkRotationDirection arcDir;
873
874 // now turn before/after into normals
875 if (sinh > 0) {
876 before.rotateCCW();
877 after.rotateCCW();
878 arcDir = kCW_SkRotationDirection;
879 } else {
880 before.rotateCW();
881 after.rotateCW();
882 arcDir = kCCW_SkRotationDirection;
883 }
884
885 SkMatrix matrix;
886 SkPoint pts[kSkBuildQuadArcStorage];
reed@google.comabf15c12011-01-18 20:35:51 +0000887
reed@android.com8a1c16f2008-12-17 15:59:43 +0000888 matrix.setScale(radius, radius);
889 matrix.postTranslate(xx - SkScalarMul(radius, before.fX),
890 yy - SkScalarMul(radius, before.fY));
reed@google.comabf15c12011-01-18 20:35:51 +0000891
reed@android.com8a1c16f2008-12-17 15:59:43 +0000892 int count = SkBuildQuadArc(before, after, arcDir, &matrix, pts);
reed@google.comabf15c12011-01-18 20:35:51 +0000893
reed@android.com8a1c16f2008-12-17 15:59:43 +0000894 this->incReserve(count);
895 // [xx,yy] == pts[0]
896 this->lineTo(xx, yy);
897 for (int i = 1; i < count; i += 2) {
898 this->quadTo(pts[i], pts[i+1]);
899 }
900}
901
902///////////////////////////////////////////////////////////////////////////////
903
904void SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy) {
905 SkMatrix matrix;
906
907 matrix.setTranslate(dx, dy);
908 this->addPath(path, matrix);
909}
910
911void SkPath::addPath(const SkPath& path, const SkMatrix& matrix) {
912 this->incReserve(path.fPts.count());
913
914 Iter iter(path, false);
915 SkPoint pts[4];
916 Verb verb;
917
918 SkMatrix::MapPtsProc proc = matrix.getMapPtsProc();
919
920 while ((verb = iter.next(pts)) != kDone_Verb) {
921 switch (verb) {
922 case kMove_Verb:
923 proc(matrix, &pts[0], &pts[0], 1);
924 this->moveTo(pts[0]);
925 break;
926 case kLine_Verb:
927 proc(matrix, &pts[1], &pts[1], 1);
928 this->lineTo(pts[1]);
929 break;
930 case kQuad_Verb:
931 proc(matrix, &pts[1], &pts[1], 2);
932 this->quadTo(pts[1], pts[2]);
933 break;
934 case kCubic_Verb:
935 proc(matrix, &pts[1], &pts[1], 3);
936 this->cubicTo(pts[1], pts[2], pts[3]);
937 break;
938 case kClose_Verb:
939 this->close();
940 break;
941 default:
942 SkASSERT(!"unknown verb");
943 }
944 }
945}
946
947///////////////////////////////////////////////////////////////////////////////
948
949static const uint8_t gPtsInVerb[] = {
950 1, // kMove
951 1, // kLine
952 2, // kQuad
953 3, // kCubic
954 0, // kClose
955 0 // kDone
956};
957
958// ignore the initial moveto, and stop when the 1st contour ends
959void SkPath::pathTo(const SkPath& path) {
960 int i, vcount = path.fVerbs.count();
961 if (vcount == 0) {
962 return;
963 }
964
965 this->incReserve(vcount);
966
967 const uint8_t* verbs = path.fVerbs.begin();
968 const SkPoint* pts = path.fPts.begin() + 1; // 1 for the initial moveTo
969
970 SkASSERT(verbs[0] == kMove_Verb);
971 for (i = 1; i < vcount; i++) {
972 switch (verbs[i]) {
973 case kLine_Verb:
974 this->lineTo(pts[0].fX, pts[0].fY);
975 break;
976 case kQuad_Verb:
977 this->quadTo(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY);
978 break;
979 case kCubic_Verb:
980 this->cubicTo(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY,
981 pts[2].fX, pts[2].fY);
982 break;
983 case kClose_Verb:
984 return;
985 }
986 pts += gPtsInVerb[verbs[i]];
987 }
988}
989
990// ignore the last point of the 1st contour
991void SkPath::reversePathTo(const SkPath& path) {
992 int i, vcount = path.fVerbs.count();
993 if (vcount == 0) {
994 return;
995 }
996
997 this->incReserve(vcount);
998
999 const uint8_t* verbs = path.fVerbs.begin();
1000 const SkPoint* pts = path.fPts.begin();
1001
1002 SkASSERT(verbs[0] == kMove_Verb);
1003 for (i = 1; i < vcount; i++) {
1004 int n = gPtsInVerb[verbs[i]];
1005 if (n == 0) {
1006 break;
1007 }
1008 pts += n;
1009 }
1010
1011 while (--i > 0) {
1012 switch (verbs[i]) {
1013 case kLine_Verb:
1014 this->lineTo(pts[-1].fX, pts[-1].fY);
1015 break;
1016 case kQuad_Verb:
1017 this->quadTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY);
1018 break;
1019 case kCubic_Verb:
1020 this->cubicTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY,
1021 pts[-3].fX, pts[-3].fY);
1022 break;
1023 default:
1024 SkASSERT(!"bad verb");
1025 break;
1026 }
1027 pts -= gPtsInVerb[verbs[i]];
1028 }
1029}
1030
1031///////////////////////////////////////////////////////////////////////////////
1032
1033void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
1034 SkMatrix matrix;
1035
1036 matrix.setTranslate(dx, dy);
1037 this->transform(matrix, dst);
1038}
1039
1040#include "SkGeometry.h"
1041
1042static void subdivide_quad_to(SkPath* path, const SkPoint pts[3],
1043 int level = 2) {
1044 if (--level >= 0) {
1045 SkPoint tmp[5];
1046
1047 SkChopQuadAtHalf(pts, tmp);
1048 subdivide_quad_to(path, &tmp[0], level);
1049 subdivide_quad_to(path, &tmp[2], level);
1050 } else {
1051 path->quadTo(pts[1], pts[2]);
1052 }
1053}
1054
1055static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
1056 int level = 2) {
1057 if (--level >= 0) {
1058 SkPoint tmp[7];
1059
1060 SkChopCubicAtHalf(pts, tmp);
1061 subdivide_cubic_to(path, &tmp[0], level);
1062 subdivide_cubic_to(path, &tmp[3], level);
1063 } else {
1064 path->cubicTo(pts[1], pts[2], pts[3]);
1065 }
1066}
1067
1068void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
1069 SkDEBUGCODE(this->validate();)
1070 if (dst == NULL) {
1071 dst = (SkPath*)this;
1072 }
1073
tomhudson@google.com8d430182011-06-06 19:11:19 +00001074 if (matrix.hasPerspective()) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001075 SkPath tmp;
1076 tmp.fFillType = fFillType;
1077
1078 SkPath::Iter iter(*this, false);
1079 SkPoint pts[4];
1080 SkPath::Verb verb;
1081
1082 while ((verb = iter.next(pts)) != kDone_Verb) {
1083 switch (verb) {
1084 case kMove_Verb:
1085 tmp.moveTo(pts[0]);
1086 break;
1087 case kLine_Verb:
1088 tmp.lineTo(pts[1]);
1089 break;
1090 case kQuad_Verb:
1091 subdivide_quad_to(&tmp, pts);
1092 break;
1093 case kCubic_Verb:
1094 subdivide_cubic_to(&tmp, pts);
1095 break;
1096 case kClose_Verb:
1097 tmp.close();
1098 break;
1099 default:
1100 SkASSERT(!"unknown verb");
1101 break;
1102 }
1103 }
1104
1105 dst->swap(tmp);
1106 matrix.mapPoints(dst->fPts.begin(), dst->fPts.count());
1107 } else {
1108 // remember that dst might == this, so be sure to check
reed@android.comd252db02009-04-01 18:31:44 +00001109 // fBoundsIsDirty before we set it
1110 if (!fBoundsIsDirty && matrix.rectStaysRect() && fPts.count() > 1) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001111 // if we're empty, fastbounds should not be mapped
reed@android.comd252db02009-04-01 18:31:44 +00001112 matrix.mapRect(&dst->fBounds, fBounds);
1113 dst->fBoundsIsDirty = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001114 } else {
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +00001115 GEN_ID_PTR_INC(dst);
reed@android.comd252db02009-04-01 18:31:44 +00001116 dst->fBoundsIsDirty = true;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001117 }
1118
1119 if (this != dst) {
1120 dst->fVerbs = fVerbs;
1121 dst->fPts.setCount(fPts.count());
1122 dst->fFillType = fFillType;
reed@google.comb3b0a5b2011-09-21 12:58:51 +00001123 dst->fSegmentMask = fSegmentMask;
reed@google.com2a6f8ab2011-10-25 18:41:23 +00001124 dst->fConvexity = fConvexity;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001125 }
1126 matrix.mapPoints(dst->fPts.begin(), fPts.begin(), fPts.count());
1127 SkDEBUGCODE(dst->validate();)
1128 }
1129}
1130
reed@android.com8a1c16f2008-12-17 15:59:43 +00001131///////////////////////////////////////////////////////////////////////////////
1132///////////////////////////////////////////////////////////////////////////////
1133
1134enum NeedMoveToState {
1135 kAfterClose_NeedMoveToState,
1136 kAfterCons_NeedMoveToState,
1137 kAfterPrefix_NeedMoveToState
1138};
1139
1140SkPath::Iter::Iter() {
1141#ifdef SK_DEBUG
1142 fPts = NULL;
1143 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
1144 fForceClose = fNeedMoveTo = fCloseLine = false;
1145#endif
1146 // need to init enough to make next() harmlessly return kDone_Verb
1147 fVerbs = NULL;
1148 fVerbStop = NULL;
1149 fNeedClose = false;
1150}
1151
1152SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
1153 this->setPath(path, forceClose);
1154}
1155
1156void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
1157 fPts = path.fPts.begin();
1158 fVerbs = path.fVerbs.begin();
1159 fVerbStop = path.fVerbs.end();
1160 fForceClose = SkToU8(forceClose);
1161 fNeedClose = false;
1162 fNeedMoveTo = kAfterPrefix_NeedMoveToState;
1163}
1164
1165bool SkPath::Iter::isClosedContour() const {
1166 if (fVerbs == NULL || fVerbs == fVerbStop) {
1167 return false;
1168 }
1169 if (fForceClose) {
1170 return true;
1171 }
1172
1173 const uint8_t* verbs = fVerbs;
1174 const uint8_t* stop = fVerbStop;
reed@google.comabf15c12011-01-18 20:35:51 +00001175
reed@android.com8a1c16f2008-12-17 15:59:43 +00001176 if (kMove_Verb == *verbs) {
1177 verbs += 1; // skip the initial moveto
1178 }
1179
1180 while (verbs < stop) {
reed@google.comabf15c12011-01-18 20:35:51 +00001181 unsigned v = *verbs++;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001182 if (kMove_Verb == v) {
1183 break;
1184 }
1185 if (kClose_Verb == v) {
1186 return true;
1187 }
1188 }
1189 return false;
1190}
1191
1192SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
1193 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001194 // A special case: if both points are NaN, SkPoint::operation== returns
1195 // false, but the iterator expects that they are treated as the same.
1196 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001197 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1198 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001199 return kClose_Verb;
1200 }
1201
reed@android.com8a1c16f2008-12-17 15:59:43 +00001202 if (pts) {
1203 pts[0] = fLastPt;
1204 pts[1] = fMoveTo;
1205 }
1206 fLastPt = fMoveTo;
1207 fCloseLine = true;
1208 return kLine_Verb;
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +00001209 } else {
1210 pts[0] = fMoveTo;
1211 return kClose_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001212 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001213}
1214
1215bool SkPath::Iter::cons_moveTo(SkPoint pts[1]) {
1216 if (fNeedMoveTo == kAfterClose_NeedMoveToState) {
1217 if (pts) {
1218 *pts = fMoveTo;
1219 }
1220 fNeedClose = fForceClose;
1221 fNeedMoveTo = kAfterCons_NeedMoveToState;
1222 fVerbs -= 1;
1223 return true;
1224 }
1225
1226 if (fNeedMoveTo == kAfterCons_NeedMoveToState) {
1227 if (pts) {
1228 *pts = fMoveTo;
1229 }
1230 fNeedMoveTo = kAfterPrefix_NeedMoveToState;
1231 } else {
1232 SkASSERT(fNeedMoveTo == kAfterPrefix_NeedMoveToState);
1233 if (pts) {
1234 *pts = fPts[-1];
1235 }
1236 }
1237 return false;
1238}
1239
1240SkPath::Verb SkPath::Iter::next(SkPoint pts[4]) {
1241 if (fVerbs == fVerbStop) {
1242 if (fNeedClose) {
1243 if (kLine_Verb == this->autoClose(pts)) {
1244 return kLine_Verb;
1245 }
1246 fNeedClose = false;
1247 return kClose_Verb;
1248 }
1249 return kDone_Verb;
1250 }
1251
1252 unsigned verb = *fVerbs++;
1253 const SkPoint* srcPts = fPts;
1254
1255 switch (verb) {
1256 case kMove_Verb:
1257 if (fNeedClose) {
1258 fVerbs -= 1;
1259 verb = this->autoClose(pts);
1260 if (verb == kClose_Verb) {
1261 fNeedClose = false;
1262 }
1263 return (Verb)verb;
1264 }
1265 if (fVerbs == fVerbStop) { // might be a trailing moveto
1266 return kDone_Verb;
1267 }
1268 fMoveTo = *srcPts;
1269 if (pts) {
1270 pts[0] = *srcPts;
1271 }
1272 srcPts += 1;
1273 fNeedMoveTo = kAfterCons_NeedMoveToState;
1274 fNeedClose = fForceClose;
1275 break;
1276 case kLine_Verb:
1277 if (this->cons_moveTo(pts)) {
1278 return kMove_Verb;
1279 }
1280 if (pts) {
1281 pts[1] = srcPts[0];
1282 }
1283 fLastPt = srcPts[0];
1284 fCloseLine = false;
1285 srcPts += 1;
1286 break;
1287 case kQuad_Verb:
1288 if (this->cons_moveTo(pts)) {
1289 return kMove_Verb;
1290 }
1291 if (pts) {
1292 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
1293 }
1294 fLastPt = srcPts[1];
1295 srcPts += 2;
1296 break;
1297 case kCubic_Verb:
1298 if (this->cons_moveTo(pts)) {
1299 return kMove_Verb;
1300 }
1301 if (pts) {
1302 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
1303 }
1304 fLastPt = srcPts[2];
1305 srcPts += 3;
1306 break;
1307 case kClose_Verb:
1308 verb = this->autoClose(pts);
1309 if (verb == kLine_Verb) {
1310 fVerbs -= 1;
1311 } else {
1312 fNeedClose = false;
1313 }
1314 fNeedMoveTo = kAfterClose_NeedMoveToState;
1315 break;
1316 }
1317 fPts = srcPts;
1318 return (Verb)verb;
1319}
1320
1321///////////////////////////////////////////////////////////////////////////////
1322
reed@android.com8a1c16f2008-12-17 15:59:43 +00001323/*
1324 Format in flattened buffer: [ptCount, verbCount, pts[], verbs[]]
1325*/
1326
reed@google.com73945652011-04-25 19:04:27 +00001327void SkPath::flatten(SkWriter32& buffer) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001328 SkDEBUGCODE(this->validate();)
1329
1330 buffer.write32(fPts.count());
1331 buffer.write32(fVerbs.count());
reed@google.com98b11f12011-09-21 18:40:27 +00001332 buffer.write32((fFillType << 8) | fSegmentMask);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001333 buffer.writeMul4(fPts.begin(), sizeof(SkPoint) * fPts.count());
1334 buffer.writePad(fVerbs.begin(), fVerbs.count());
1335}
1336
reed@google.com73945652011-04-25 19:04:27 +00001337void SkPath::unflatten(SkReader32& buffer) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001338 fPts.setCount(buffer.readS32());
1339 fVerbs.setCount(buffer.readS32());
reed@google.com98b11f12011-09-21 18:40:27 +00001340 uint32_t packed = buffer.readS32();
1341 fFillType = packed >> 8;
1342 fSegmentMask = packed & 0xFF;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001343 buffer.read(fPts.begin(), sizeof(SkPoint) * fPts.count());
1344 buffer.read(fVerbs.begin(), fVerbs.count());
reed@google.comabf15c12011-01-18 20:35:51 +00001345
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +00001346 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +00001347 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001348
1349 SkDEBUGCODE(this->validate();)
1350}
1351
1352///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00001353///////////////////////////////////////////////////////////////////////////////
1354
reed@android.com8a1c16f2008-12-17 15:59:43 +00001355void SkPath::dump(bool forceClose, const char title[]) const {
1356 Iter iter(*this, forceClose);
1357 SkPoint pts[4];
1358 Verb verb;
1359
1360 SkDebugf("path: forceClose=%s %s\n", forceClose ? "true" : "false",
1361 title ? title : "");
1362
1363 while ((verb = iter.next(pts)) != kDone_Verb) {
1364 switch (verb) {
1365 case kMove_Verb:
1366#ifdef SK_CAN_USE_FLOAT
1367 SkDebugf(" path: moveTo [%g %g]\n",
1368 SkScalarToFloat(pts[0].fX), SkScalarToFloat(pts[0].fY));
1369#else
1370 SkDebugf(" path: moveTo [%x %x]\n", pts[0].fX, pts[0].fY);
1371#endif
1372 break;
1373 case kLine_Verb:
1374#ifdef SK_CAN_USE_FLOAT
1375 SkDebugf(" path: lineTo [%g %g]\n",
1376 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY));
1377#else
1378 SkDebugf(" path: lineTo [%x %x]\n", pts[1].fX, pts[1].fY);
1379#endif
1380 break;
1381 case kQuad_Verb:
1382#ifdef SK_CAN_USE_FLOAT
1383 SkDebugf(" path: quadTo [%g %g] [%g %g]\n",
1384 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY),
1385 SkScalarToFloat(pts[2].fX), SkScalarToFloat(pts[2].fY));
1386#else
1387 SkDebugf(" path: quadTo [%x %x] [%x %x]\n",
1388 pts[1].fX, pts[1].fY, pts[2].fX, pts[2].fY);
1389#endif
1390 break;
1391 case kCubic_Verb:
1392#ifdef SK_CAN_USE_FLOAT
1393 SkDebugf(" path: cubeTo [%g %g] [%g %g] [%g %g]\n",
1394 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY),
1395 SkScalarToFloat(pts[2].fX), SkScalarToFloat(pts[2].fY),
1396 SkScalarToFloat(pts[3].fX), SkScalarToFloat(pts[3].fY));
1397#else
1398 SkDebugf(" path: cubeTo [%x %x] [%x %x] [%x %x]\n",
1399 pts[1].fX, pts[1].fY, pts[2].fX, pts[2].fY,
1400 pts[3].fX, pts[3].fY);
1401#endif
1402 break;
1403 case kClose_Verb:
1404 SkDebugf(" path: close\n");
1405 break;
1406 default:
1407 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
1408 verb = kDone_Verb; // stop the loop
1409 break;
1410 }
1411 }
1412 SkDebugf("path: done %s\n", title ? title : "");
1413}
1414
reed@android.come522ca52009-11-23 20:10:41 +00001415void SkPath::dump() const {
1416 this->dump(false);
1417}
1418
1419#ifdef SK_DEBUG
1420void SkPath::validate() const {
1421 SkASSERT(this != NULL);
1422 SkASSERT((fFillType & ~3) == 0);
1423 fPts.validate();
1424 fVerbs.validate();
reed@google.comabf15c12011-01-18 20:35:51 +00001425
reed@android.come522ca52009-11-23 20:10:41 +00001426 if (!fBoundsIsDirty) {
1427 SkRect bounds;
1428 compute_pt_bounds(&bounds, fPts);
1429 if (fPts.count() <= 1) {
1430 // if we're empty, fBounds may be empty but translated, so we can't
1431 // necessarily compare to bounds directly
1432 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
1433 // be [2, 2, 2, 2]
1434 SkASSERT(bounds.isEmpty());
1435 SkASSERT(fBounds.isEmpty());
1436 } else {
reed@google.comeac52bd2011-11-14 18:13:59 +00001437 if (bounds.isEmpty()) {
1438 SkASSERT(fBounds.isEmpty());
1439 } else {
1440 SkASSERT(fBounds.contains(bounds));
1441 }
reed@android.come522ca52009-11-23 20:10:41 +00001442 }
1443 }
reed@google.com10296cc2011-09-21 12:29:05 +00001444
1445 uint32_t mask = 0;
1446 for (int i = 0; i < fVerbs.count(); i++) {
1447 switch (fVerbs[i]) {
1448 case kLine_Verb:
1449 mask |= kLine_SegmentMask;
1450 break;
1451 case kQuad_Verb:
1452 mask |= kQuad_SegmentMask;
1453 break;
1454 case kCubic_Verb:
1455 mask |= kCubic_SegmentMask;
1456 }
1457 }
1458 SkASSERT(mask == fSegmentMask);
reed@android.come522ca52009-11-23 20:10:41 +00001459}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001460#endif
reed@android.come522ca52009-11-23 20:10:41 +00001461
reed@google.com04863fa2011-05-15 04:08:24 +00001462///////////////////////////////////////////////////////////////////////////////
1463
reed@google.com0b7b9822011-05-16 12:29:27 +00001464static int sign(SkScalar x) { return x < 0; }
1465#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00001466
reed@google.com04863fa2011-05-15 04:08:24 +00001467static int CrossProductSign(const SkVector& a, const SkVector& b) {
bsalomon@google.com647a8042011-08-23 14:39:01 +00001468 return SkScalarSignAsInt(SkPoint::CrossProduct(a, b));
reed@google.com04863fa2011-05-15 04:08:24 +00001469}
1470
1471// only valid for a single contour
1472struct Convexicator {
reed@google.comb54455e2011-05-16 14:16:04 +00001473 Convexicator() : fPtCount(0), fConvexity(SkPath::kConvex_Convexity) {
reed@google.com04863fa2011-05-15 04:08:24 +00001474 fSign = 0;
1475 // warnings
1476 fCurrPt.set(0, 0);
1477 fVec0.set(0, 0);
1478 fVec1.set(0, 0);
1479 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00001480
1481 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00001482 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00001483 }
1484
1485 SkPath::Convexity getConvexity() const { return fConvexity; }
1486
1487 void addPt(const SkPoint& pt) {
1488 if (SkPath::kConcave_Convexity == fConvexity) {
1489 return;
1490 }
1491
1492 if (0 == fPtCount) {
1493 fCurrPt = pt;
1494 ++fPtCount;
1495 } else {
1496 SkVector vec = pt - fCurrPt;
1497 if (vec.fX || vec.fY) {
1498 fCurrPt = pt;
1499 if (++fPtCount == 2) {
1500 fFirstVec = fVec1 = vec;
1501 } else {
1502 SkASSERT(fPtCount > 2);
1503 this->addVec(vec);
1504 }
reed@google.com85b6e392011-05-15 20:25:17 +00001505
1506 int sx = sign(vec.fX);
1507 int sy = sign(vec.fY);
1508 fDx += (sx != fSx);
1509 fDy += (sy != fSy);
1510 fSx = sx;
1511 fSy = sy;
1512
1513 if (fDx > 3 || fDy > 3) {
1514 fConvexity = SkPath::kConcave_Convexity;
1515 }
reed@google.com04863fa2011-05-15 04:08:24 +00001516 }
1517 }
1518 }
1519
1520 void close() {
1521 if (fPtCount > 2) {
1522 this->addVec(fFirstVec);
1523 }
1524 }
1525
1526private:
1527 void addVec(const SkVector& vec) {
1528 SkASSERT(vec.fX || vec.fY);
1529 fVec0 = fVec1;
1530 fVec1 = vec;
1531 int sign = CrossProductSign(fVec0, fVec1);
1532 if (0 == fSign) {
1533 fSign = sign;
1534 } else if (sign) {
reed@google.comb54455e2011-05-16 14:16:04 +00001535 if (fSign != sign) {
reed@google.com04863fa2011-05-15 04:08:24 +00001536 fConvexity = SkPath::kConcave_Convexity;
1537 }
1538 }
1539 }
1540
1541 SkPoint fCurrPt;
1542 SkVector fVec0, fVec1, fFirstVec;
1543 int fPtCount; // non-degenerate points
1544 int fSign;
1545 SkPath::Convexity fConvexity;
reed@google.com0b7b9822011-05-16 12:29:27 +00001546 int fDx, fDy, fSx, fSy;
reed@google.com04863fa2011-05-15 04:08:24 +00001547};
1548
1549SkPath::Convexity SkPath::ComputeConvexity(const SkPath& path) {
1550 SkPoint pts[4];
1551 SkPath::Verb verb;
1552 SkPath::Iter iter(path, true);
1553
1554 int contourCount = 0;
1555 int count;
1556 Convexicator state;
1557
1558 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
1559 switch (verb) {
1560 case kMove_Verb:
1561 if (++contourCount > 1) {
1562 return kConcave_Convexity;
1563 }
1564 pts[1] = pts[0];
1565 count = 1;
1566 break;
1567 case kLine_Verb: count = 1; break;
1568 case kQuad_Verb: count = 2; break;
1569 case kCubic_Verb: count = 3; break;
1570 case kClose_Verb:
1571 state.close();
1572 count = 0;
1573 break;
1574 default:
1575 SkASSERT(!"bad verb");
1576 return kConcave_Convexity;
1577 }
1578
1579 for (int i = 1; i <= count; i++) {
1580 state.addPt(pts[i]);
1581 }
1582 // early exit
1583 if (kConcave_Convexity == state.getConvexity()) {
1584 return kConcave_Convexity;
1585 }
1586 }
1587 return state.getConvexity();
1588}