blob: c415697bb0fa179628c816bed2eedd629298f040 [file] [log] [blame]
reed@android.com8a1c16f2008-12-17 15:59:43 +00001/* libs/graphics/sgl/SkPath.cpp
2**
3** Copyright 2006, The Android Open Source Project
4**
reed@google.comabf15c12011-01-18 20:35:51 +00005** Licensed under the Apache License, Version 2.0 (the "License");
6** you may not use this file except in compliance with the License.
7** You may obtain a copy of the License at
reed@android.com8a1c16f2008-12-17 15:59:43 +00008**
reed@google.comabf15c12011-01-18 20:35:51 +00009** http://www.apache.org/licenses/LICENSE-2.0
reed@android.com8a1c16f2008-12-17 15:59:43 +000010**
reed@google.comabf15c12011-01-18 20:35:51 +000011** Unless required by applicable law or agreed to in writing, software
12** distributed under the License is distributed on an "AS IS" BASIS,
13** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14** See the License for the specific language governing permissions and
reed@android.com8a1c16f2008-12-17 15:59:43 +000015** limitations under the License.
16*/
17
18#include "SkPath.h"
reed@google.com73945652011-04-25 19:04:27 +000019#include "SkReader32.h"
20#include "SkWriter32.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000021#include "SkMath.h"
22
23////////////////////////////////////////////////////////////////////////////
24
25/* This guy's constructor/destructor bracket a path editing operation. It is
26 used when we know the bounds of the amount we are going to add to the path
27 (usually a new contour, but not required).
reed@google.comabf15c12011-01-18 20:35:51 +000028
reed@android.com8a1c16f2008-12-17 15:59:43 +000029 It captures some state about the path up front (i.e. if it already has a
30 cached bounds), and the if it can, it updates the cache bounds explicitly,
reed@android.comd252db02009-04-01 18:31:44 +000031 avoiding the need to revisit all of the points in getBounds().
reed@google.comabf15c12011-01-18 20:35:51 +000032
reed@android.com6b82d1a2009-06-03 02:35:01 +000033 It also notes if the path was originally empty, and if so, sets isConvex
34 to true. Thus it can only be used if the contour being added is convex.
reed@android.com8a1c16f2008-12-17 15:59:43 +000035 */
36class SkAutoPathBoundsUpdate {
37public:
38 SkAutoPathBoundsUpdate(SkPath* path, const SkRect& r) : fRect(r) {
39 this->init(path);
40 }
41
42 SkAutoPathBoundsUpdate(SkPath* path, SkScalar left, SkScalar top,
43 SkScalar right, SkScalar bottom) {
44 fRect.set(left, top, right, bottom);
45 this->init(path);
46 }
reed@google.comabf15c12011-01-18 20:35:51 +000047
reed@android.com8a1c16f2008-12-17 15:59:43 +000048 ~SkAutoPathBoundsUpdate() {
reed@android.com6b82d1a2009-06-03 02:35:01 +000049 fPath->setIsConvex(fEmpty);
reed@android.com8a1c16f2008-12-17 15:59:43 +000050 if (fEmpty) {
reed@android.comd252db02009-04-01 18:31:44 +000051 fPath->fBounds = fRect;
52 fPath->fBoundsIsDirty = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +000053 } else if (!fDirty) {
reed@android.comd252db02009-04-01 18:31:44 +000054 fPath->fBounds.join(fRect);
55 fPath->fBoundsIsDirty = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +000056 }
57 }
reed@google.comabf15c12011-01-18 20:35:51 +000058
reed@android.com8a1c16f2008-12-17 15:59:43 +000059private:
reed@android.com6b82d1a2009-06-03 02:35:01 +000060 SkPath* fPath;
61 SkRect fRect;
62 bool fDirty;
63 bool fEmpty;
reed@google.comabf15c12011-01-18 20:35:51 +000064
reed@android.com8a1c16f2008-12-17 15:59:43 +000065 // returns true if we should proceed
reed@android.com6b82d1a2009-06-03 02:35:01 +000066 void init(SkPath* path) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000067 fPath = path;
reed@android.com63debae2009-12-16 17:25:43 +000068 fDirty = SkToBool(path->fBoundsIsDirty);
reed@android.com8a1c16f2008-12-17 15:59:43 +000069 fEmpty = path->isEmpty();
reed@android.com6c14b432009-03-23 20:11:11 +000070 // Cannot use fRect for our bounds unless we know it is sorted
reed@android.com49f0ff22009-03-19 21:52:42 +000071 fRect.sort();
reed@android.com8a1c16f2008-12-17 15:59:43 +000072 }
73};
74
reed@android.comd252db02009-04-01 18:31:44 +000075static void compute_pt_bounds(SkRect* bounds, const SkTDArray<SkPoint>& pts) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000076 if (pts.count() <= 1) { // we ignore just 1 point (moveto)
77 bounds->set(0, 0, 0, 0);
78 } else {
79 bounds->set(pts.begin(), pts.count());
80// SkDebugf("------- compute bounds %p %d", &pts, pts.count());
81 }
82}
83
84////////////////////////////////////////////////////////////////////////////
85
86/*
87 Stores the verbs and points as they are given to us, with exceptions:
88 - we only record "Close" if it was immediately preceeded by Line | Quad | Cubic
89 - we insert a Move(0,0) if Line | Quad | Cubic is our first command
90
91 The iterator does more cleanup, especially if forceClose == true
92 1. if we encounter Close, return a cons'd up Line() first (if the curr-pt != start-pt)
93 2. if we encounter Move without a preceeding Close, and forceClose is true, goto #1
94 3. if we encounter Line | Quad | Cubic after Close, cons up a Move
95*/
96
97////////////////////////////////////////////////////////////////////////////
98
reed@android.com6b82d1a2009-06-03 02:35:01 +000099SkPath::SkPath() : fBoundsIsDirty(true), fFillType(kWinding_FillType) {
reed@google.com04863fa2011-05-15 04:08:24 +0000100 fConvexity = kUnknown_Convexity;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000101#ifdef ANDROID
102 fGenerationID = 0;
103#endif
reed@android.com6b82d1a2009-06-03 02:35:01 +0000104}
reed@android.com8a1c16f2008-12-17 15:59:43 +0000105
106SkPath::SkPath(const SkPath& src) {
107 SkDEBUGCODE(src.validate();)
108 *this = src;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000109#ifdef ANDROID
110 // the assignment operator above increments the ID so correct for that here
111 fGenerationID--;
112#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000113}
114
115SkPath::~SkPath() {
116 SkDEBUGCODE(this->validate();)
117}
118
119SkPath& SkPath::operator=(const SkPath& src) {
120 SkDEBUGCODE(src.validate();)
121
122 if (this != &src) {
reed@android.comd252db02009-04-01 18:31:44 +0000123 fBounds = src.fBounds;
124 fPts = src.fPts;
125 fVerbs = src.fVerbs;
126 fFillType = src.fFillType;
127 fBoundsIsDirty = src.fBoundsIsDirty;
reed@google.com04863fa2011-05-15 04:08:24 +0000128 fConvexity = src.fConvexity;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000129 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000130 }
131 SkDEBUGCODE(this->validate();)
132 return *this;
133}
134
reed@android.com3abec1d2009-03-02 05:36:20 +0000135bool operator==(const SkPath& a, const SkPath& b) {
reed@android.com6b82d1a2009-06-03 02:35:01 +0000136 // note: don't need to look at isConvex or bounds, since just comparing the
137 // raw data is sufficient.
reed@android.com3abec1d2009-03-02 05:36:20 +0000138 return &a == &b ||
139 (a.fFillType == b.fFillType && a.fVerbs == b.fVerbs && a.fPts == b.fPts);
140}
141
reed@android.com8a1c16f2008-12-17 15:59:43 +0000142void SkPath::swap(SkPath& other) {
143 SkASSERT(&other != NULL);
144
145 if (this != &other) {
reed@android.comd252db02009-04-01 18:31:44 +0000146 SkTSwap<SkRect>(fBounds, other.fBounds);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000147 fPts.swap(other.fPts);
148 fVerbs.swap(other.fVerbs);
149 SkTSwap<uint8_t>(fFillType, other.fFillType);
reed@android.comd252db02009-04-01 18:31:44 +0000150 SkTSwap<uint8_t>(fBoundsIsDirty, other.fBoundsIsDirty);
reed@google.com04863fa2011-05-15 04:08:24 +0000151 SkTSwap<uint8_t>(fConvexity, other.fConvexity);
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000152 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000153 }
154}
155
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000156#ifdef ANDROID
157uint32_t SkPath::getGenerationID() const {
158 return fGenerationID;
159}
160#endif
161
reed@android.com8a1c16f2008-12-17 15:59:43 +0000162void SkPath::reset() {
163 SkDEBUGCODE(this->validate();)
164
165 fPts.reset();
166 fVerbs.reset();
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000167 GEN_ID_INC;
reed@android.comd252db02009-04-01 18:31:44 +0000168 fBoundsIsDirty = true;
reed@google.com04863fa2011-05-15 04:08:24 +0000169 fConvexity = kUnknown_Convexity;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000170}
171
172void SkPath::rewind() {
173 SkDEBUGCODE(this->validate();)
174
175 fPts.rewind();
176 fVerbs.rewind();
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000177 GEN_ID_INC;
reed@android.comd252db02009-04-01 18:31:44 +0000178 fBoundsIsDirty = true;
reed@google.com04863fa2011-05-15 04:08:24 +0000179 fConvexity = kUnknown_Convexity;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000180}
181
182bool SkPath::isEmpty() const {
183 SkDEBUGCODE(this->validate();)
184
185 int count = fVerbs.count();
186 return count == 0 || (count == 1 && fVerbs[0] == kMove_Verb);
187}
188
caryclark@google.comf1316942011-07-26 19:54:45 +0000189/*
190 Determines if path is a rect by keeping track of changes in direction
191 and looking for a loop either clockwise or counterclockwise.
192
193 The direction is computed such that:
194 0: vertical up
195 1: horizontal right
196 2: vertical down
197 3: horizontal left
198
199A rectangle cycles up/right/down/left or up/left/down/right.
200
201The test fails if:
202 The path is closed, and followed by a line.
203 A second move creates a new endpoint.
204 A diagonal line is parsed.
205 There's more than four changes of direction.
206 There's a discontinuity on the line (e.g., a move in the middle)
207 The line reverses direction.
208 The rectangle doesn't complete a cycle.
209 The path contains a quadratic or cubic.
210 The path contains fewer than four points.
211 The final point isn't equal to the first point.
212
213It's OK if the path has:
214 Several colinear line segments composing a rectangle side.
215 Single points on the rectangle side.
216
217The direction takes advantage of the corners found since opposite sides
218must travel in opposite directions.
219
220FIXME: Allow colinear quads and cubics to be treated like lines.
221FIXME: If the API passes fill-only, return true if the filled stroke
222 is a rectangle, though the caller failed to close the path.
223 */
224bool SkPath::isRect(SkRect* rect) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000225 SkDEBUGCODE(this->validate();)
reed@google.comabf15c12011-01-18 20:35:51 +0000226
caryclark@google.comf1316942011-07-26 19:54:45 +0000227 int corners = 0;
228 SkPoint first, last;
229 int firstDirection;
230 int lastDirection;
231 int nextDirection;
232 bool closedOrMoved;
233 bool autoClose = false;
234 const uint8_t* verbs = fVerbs.begin();
235 const uint8_t* verbStop = fVerbs.end();
236 const SkPoint* pts = fPts.begin();
237 while (verbs != verbStop) {
238 switch (*verbs++) {
239 case kClose_Verb:
240 pts = fPts.begin();
241 autoClose = true;
242 case kLine_Verb: {
243 SkScalar left = last.fX;
244 SkScalar top = last.fY;
245 SkScalar right = pts->fX;
246 SkScalar bottom = pts->fY;
247 ++pts;
248 if (left != right && top != bottom) {
249 return false; // diagonal
250 }
251 if (left == right && top == bottom) {
252 break; // single point on side OK
253 }
254 nextDirection = (left != right) << 0 |
255 (left < right || top < bottom) << 1;
256 if (0 == corners) {
257 firstDirection = nextDirection;
258 first = last;
259 last = pts[-1];
260 corners = 1;
261 closedOrMoved = false;
262 break;
263 }
264 if (closedOrMoved) {
265 return false; // closed followed by a line
266 }
267 closedOrMoved = autoClose;
268 if (lastDirection != nextDirection) {
269 if (++corners > 4) {
270 return false; // too many direction changes
271 }
272 }
273 last = pts[-1];
274 if (lastDirection == nextDirection) {
275 break; // colinear segment
276 }
277 // Possible values for corners are 2, 3, and 4.
278 // When corners == 3, nextDirection opposes firstDirection.
279 // Otherwise, nextDirection at corner 2 opposes corner 4.
280 int turn = firstDirection ^ corners - 1;
281 int directionCycle = 3 == corners ? 0 : nextDirection ^ turn;
282 if ((directionCycle ^ turn) != nextDirection) {
283 return false; // direction didn't follow cycle
284 }
285 break;
286 }
287 case kQuad_Verb:
288 case kCubic_Verb:
289 return false; // quadratic, cubic not allowed
290 case kMove_Verb:
291 last = *pts++;
292 closedOrMoved = true;
293 break;
294 }
295 lastDirection = nextDirection;
296 }
297 // Success if 4 corners and first point equals last
298 bool result = 4 == corners && first == last;
299 if (result && rect) {
300 *rect = getBounds();
301 }
302 return result;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000303}
304
305int SkPath::getPoints(SkPoint copy[], int max) const {
306 SkDEBUGCODE(this->validate();)
307
308 SkASSERT(max >= 0);
309 int count = fPts.count();
310 if (copy && max > 0 && count > 0) {
311 memcpy(copy, fPts.begin(), sizeof(SkPoint) * SkMin32(max, count));
312 }
313 return count;
314}
315
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000316SkPoint SkPath::getPoint(int index) const {
317 if ((unsigned)index < (unsigned)fPts.count()) {
318 return fPts[index];
319 }
320 return SkPoint::Make(0, 0);
321}
322
reed@android.com8a1c16f2008-12-17 15:59:43 +0000323void SkPath::getLastPt(SkPoint* lastPt) const {
324 SkDEBUGCODE(this->validate();)
325
326 if (lastPt) {
327 int count = fPts.count();
328 if (count == 0) {
329 lastPt->set(0, 0);
330 } else {
331 *lastPt = fPts[count - 1];
332 }
333 }
334}
335
336void SkPath::setLastPt(SkScalar x, SkScalar y) {
337 SkDEBUGCODE(this->validate();)
338
339 int count = fPts.count();
340 if (count == 0) {
341 this->moveTo(x, y);
342 } else {
343 fPts[count - 1].set(x, y);
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000344 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000345 }
346}
347
reed@android.comd252db02009-04-01 18:31:44 +0000348void SkPath::computeBounds() const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000349 SkDEBUGCODE(this->validate();)
reed@android.comd252db02009-04-01 18:31:44 +0000350 SkASSERT(fBoundsIsDirty);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000351
reed@android.comd252db02009-04-01 18:31:44 +0000352 fBoundsIsDirty = false;
353 compute_pt_bounds(&fBounds, fPts);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000354}
355
reed@google.com04863fa2011-05-15 04:08:24 +0000356void SkPath::setConvexity(Convexity c) {
357 if (fConvexity != c) {
358 fConvexity = c;
359 GEN_ID_INC;
360 }
361}
362
reed@android.com8a1c16f2008-12-17 15:59:43 +0000363//////////////////////////////////////////////////////////////////////////////
364// Construction methods
365
reed@google.comb54455e2011-05-16 14:16:04 +0000366#define DIRTY_AFTER_EDIT \
367 do { \
368 fBoundsIsDirty = true; \
369 fConvexity = kUnknown_Convexity;\
370 } while (0)
371
reed@android.com8a1c16f2008-12-17 15:59:43 +0000372void SkPath::incReserve(U16CPU inc) {
373 SkDEBUGCODE(this->validate();)
374
375 fVerbs.setReserve(fVerbs.count() + inc);
376 fPts.setReserve(fPts.count() + inc);
377
378 SkDEBUGCODE(this->validate();)
379}
380
381void SkPath::moveTo(SkScalar x, SkScalar y) {
382 SkDEBUGCODE(this->validate();)
383
384 int vc = fVerbs.count();
385 SkPoint* pt;
386
387 if (vc > 0 && fVerbs[vc - 1] == kMove_Verb) {
388 pt = &fPts[fPts.count() - 1];
389 } else {
390 pt = fPts.append();
391 *fVerbs.append() = kMove_Verb;
392 }
393 pt->set(x, y);
394
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000395 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000396 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000397}
398
399void SkPath::rMoveTo(SkScalar x, SkScalar y) {
400 SkPoint pt;
401 this->getLastPt(&pt);
402 this->moveTo(pt.fX + x, pt.fY + y);
403}
404
405void SkPath::lineTo(SkScalar x, SkScalar y) {
406 SkDEBUGCODE(this->validate();)
407
408 if (fVerbs.count() == 0) {
409 fPts.append()->set(0, 0);
410 *fVerbs.append() = kMove_Verb;
411 }
412 fPts.append()->set(x, y);
413 *fVerbs.append() = kLine_Verb;
414
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000415 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000416 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000417}
418
419void SkPath::rLineTo(SkScalar x, SkScalar y) {
420 SkPoint pt;
421 this->getLastPt(&pt);
422 this->lineTo(pt.fX + x, pt.fY + y);
423}
424
425void SkPath::quadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
426 SkDEBUGCODE(this->validate();)
427
428 if (fVerbs.count() == 0) {
429 fPts.append()->set(0, 0);
430 *fVerbs.append() = kMove_Verb;
431 }
432
433 SkPoint* pts = fPts.append(2);
434 pts[0].set(x1, y1);
435 pts[1].set(x2, y2);
436 *fVerbs.append() = kQuad_Verb;
437
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000438 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000439 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000440}
441
442void SkPath::rQuadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
443 SkPoint pt;
444 this->getLastPt(&pt);
445 this->quadTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2);
446}
447
448void SkPath::cubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
449 SkScalar x3, SkScalar y3) {
450 SkDEBUGCODE(this->validate();)
451
452 if (fVerbs.count() == 0) {
453 fPts.append()->set(0, 0);
454 *fVerbs.append() = kMove_Verb;
455 }
456 SkPoint* pts = fPts.append(3);
457 pts[0].set(x1, y1);
458 pts[1].set(x2, y2);
459 pts[2].set(x3, y3);
460 *fVerbs.append() = kCubic_Verb;
461
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000462 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000463 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000464}
465
466void SkPath::rCubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
467 SkScalar x3, SkScalar y3) {
468 SkPoint pt;
469 this->getLastPt(&pt);
470 this->cubicTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2,
471 pt.fX + x3, pt.fY + y3);
472}
473
474void SkPath::close() {
475 SkDEBUGCODE(this->validate();)
476
477 int count = fVerbs.count();
478 if (count > 0) {
479 switch (fVerbs[count - 1]) {
480 case kLine_Verb:
481 case kQuad_Verb:
482 case kCubic_Verb:
483 *fVerbs.append() = kClose_Verb;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000484 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000485 break;
486 default:
487 // don't add a close if the prev wasn't a primitive
488 break;
489 }
490 }
491}
492
493///////////////////////////////////////////////////////////////////////////////
reed@google.comabf15c12011-01-18 20:35:51 +0000494
reed@android.com8a1c16f2008-12-17 15:59:43 +0000495void SkPath::addRect(const SkRect& rect, Direction dir) {
496 this->addRect(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, dir);
497}
498
499void SkPath::addRect(SkScalar left, SkScalar top, SkScalar right,
500 SkScalar bottom, Direction dir) {
501 SkAutoPathBoundsUpdate apbu(this, left, top, right, bottom);
502
503 this->incReserve(5);
504
505 this->moveTo(left, top);
506 if (dir == kCCW_Direction) {
507 this->lineTo(left, bottom);
508 this->lineTo(right, bottom);
509 this->lineTo(right, top);
510 } else {
511 this->lineTo(right, top);
512 this->lineTo(right, bottom);
513 this->lineTo(left, bottom);
514 }
515 this->close();
516}
517
518#define CUBIC_ARC_FACTOR ((SK_ScalarSqrt2 - SK_Scalar1) * 4 / 3)
519
520void SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
521 Direction dir) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000522 SkScalar w = rect.width();
523 SkScalar halfW = SkScalarHalf(w);
524 SkScalar h = rect.height();
525 SkScalar halfH = SkScalarHalf(h);
526
527 if (halfW <= 0 || halfH <= 0) {
528 return;
529 }
530
reed@google.comabf15c12011-01-18 20:35:51 +0000531 bool skip_hori = rx >= halfW;
532 bool skip_vert = ry >= halfH;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000533
534 if (skip_hori && skip_vert) {
535 this->addOval(rect, dir);
536 return;
537 }
reed@google.comabf15c12011-01-18 20:35:51 +0000538
539 SkAutoPathBoundsUpdate apbu(this, rect);
540
reed@android.com8a1c16f2008-12-17 15:59:43 +0000541 if (skip_hori) {
542 rx = halfW;
543 } else if (skip_vert) {
544 ry = halfH;
545 }
546
547 SkScalar sx = SkScalarMul(rx, CUBIC_ARC_FACTOR);
548 SkScalar sy = SkScalarMul(ry, CUBIC_ARC_FACTOR);
549
550 this->incReserve(17);
551 this->moveTo(rect.fRight - rx, rect.fTop);
552 if (dir == kCCW_Direction) {
553 if (!skip_hori) {
554 this->lineTo(rect.fLeft + rx, rect.fTop); // top
555 }
556 this->cubicTo(rect.fLeft + rx - sx, rect.fTop,
557 rect.fLeft, rect.fTop + ry - sy,
558 rect.fLeft, rect.fTop + ry); // top-left
559 if (!skip_vert) {
560 this->lineTo(rect.fLeft, rect.fBottom - ry); // left
561 }
562 this->cubicTo(rect.fLeft, rect.fBottom - ry + sy,
563 rect.fLeft + rx - sx, rect.fBottom,
564 rect.fLeft + rx, rect.fBottom); // bot-left
565 if (!skip_hori) {
566 this->lineTo(rect.fRight - rx, rect.fBottom); // bottom
567 }
568 this->cubicTo(rect.fRight - rx + sx, rect.fBottom,
569 rect.fRight, rect.fBottom - ry + sy,
570 rect.fRight, rect.fBottom - ry); // bot-right
571 if (!skip_vert) {
572 this->lineTo(rect.fRight, rect.fTop + ry);
573 }
574 this->cubicTo(rect.fRight, rect.fTop + ry - sy,
575 rect.fRight - rx + sx, rect.fTop,
576 rect.fRight - rx, rect.fTop); // top-right
577 } else {
578 this->cubicTo(rect.fRight - rx + sx, rect.fTop,
579 rect.fRight, rect.fTop + ry - sy,
580 rect.fRight, rect.fTop + ry); // top-right
581 if (!skip_vert) {
582 this->lineTo(rect.fRight, rect.fBottom - ry);
583 }
584 this->cubicTo(rect.fRight, rect.fBottom - ry + sy,
585 rect.fRight - rx + sx, rect.fBottom,
586 rect.fRight - rx, rect.fBottom); // bot-right
587 if (!skip_hori) {
588 this->lineTo(rect.fLeft + rx, rect.fBottom); // bottom
589 }
590 this->cubicTo(rect.fLeft + rx - sx, rect.fBottom,
591 rect.fLeft, rect.fBottom - ry + sy,
592 rect.fLeft, rect.fBottom - ry); // bot-left
593 if (!skip_vert) {
594 this->lineTo(rect.fLeft, rect.fTop + ry); // left
595 }
596 this->cubicTo(rect.fLeft, rect.fTop + ry - sy,
597 rect.fLeft + rx - sx, rect.fTop,
598 rect.fLeft + rx, rect.fTop); // top-left
599 if (!skip_hori) {
600 this->lineTo(rect.fRight - rx, rect.fTop); // top
601 }
602 }
603 this->close();
604}
605
606static void add_corner_arc(SkPath* path, const SkRect& rect,
607 SkScalar rx, SkScalar ry, int startAngle,
608 SkPath::Direction dir, bool forceMoveTo) {
609 rx = SkMinScalar(SkScalarHalf(rect.width()), rx);
610 ry = SkMinScalar(SkScalarHalf(rect.height()), ry);
reed@google.comabf15c12011-01-18 20:35:51 +0000611
reed@android.com8a1c16f2008-12-17 15:59:43 +0000612 SkRect r;
613 r.set(-rx, -ry, rx, ry);
614
615 switch (startAngle) {
616 case 0:
617 r.offset(rect.fRight - r.fRight, rect.fBottom - r.fBottom);
618 break;
619 case 90:
620 r.offset(rect.fLeft - r.fLeft, rect.fBottom - r.fBottom);
621 break;
622 case 180: r.offset(rect.fLeft - r.fLeft, rect.fTop - r.fTop); break;
623 case 270: r.offset(rect.fRight - r.fRight, rect.fTop - r.fTop); break;
624 default: SkASSERT(!"unexpected startAngle in add_corner_arc");
625 }
reed@google.comabf15c12011-01-18 20:35:51 +0000626
reed@android.com8a1c16f2008-12-17 15:59:43 +0000627 SkScalar start = SkIntToScalar(startAngle);
628 SkScalar sweep = SkIntToScalar(90);
629 if (SkPath::kCCW_Direction == dir) {
630 start += sweep;
631 sweep = -sweep;
632 }
reed@google.comabf15c12011-01-18 20:35:51 +0000633
reed@android.com8a1c16f2008-12-17 15:59:43 +0000634 path->arcTo(r, start, sweep, forceMoveTo);
635}
636
637void SkPath::addRoundRect(const SkRect& rect, const SkScalar rad[],
638 Direction dir) {
reed@google.com44b2c732011-01-18 20:55:57 +0000639 // abort before we invoke SkAutoPathBoundsUpdate()
640 if (rect.isEmpty()) {
641 return;
642 }
643
reed@android.com8a1c16f2008-12-17 15:59:43 +0000644 SkAutoPathBoundsUpdate apbu(this, rect);
645
646 if (kCW_Direction == dir) {
647 add_corner_arc(this, rect, rad[0], rad[1], 180, dir, true);
648 add_corner_arc(this, rect, rad[2], rad[3], 270, dir, false);
649 add_corner_arc(this, rect, rad[4], rad[5], 0, dir, false);
650 add_corner_arc(this, rect, rad[6], rad[7], 90, dir, false);
651 } else {
652 add_corner_arc(this, rect, rad[0], rad[1], 180, dir, true);
653 add_corner_arc(this, rect, rad[6], rad[7], 90, dir, false);
654 add_corner_arc(this, rect, rad[4], rad[5], 0, dir, false);
655 add_corner_arc(this, rect, rad[2], rad[3], 270, dir, false);
656 }
657 this->close();
658}
659
660void SkPath::addOval(const SkRect& oval, Direction dir) {
661 SkAutoPathBoundsUpdate apbu(this, oval);
662
663 SkScalar cx = oval.centerX();
664 SkScalar cy = oval.centerY();
665 SkScalar rx = SkScalarHalf(oval.width());
666 SkScalar ry = SkScalarHalf(oval.height());
667#if 0 // these seem faster than using quads (1/2 the number of edges)
668 SkScalar sx = SkScalarMul(rx, CUBIC_ARC_FACTOR);
669 SkScalar sy = SkScalarMul(ry, CUBIC_ARC_FACTOR);
670
671 this->incReserve(13);
672 this->moveTo(cx + rx, cy);
673 if (dir == kCCW_Direction) {
674 this->cubicTo(cx + rx, cy - sy, cx + sx, cy - ry, cx, cy - ry);
675 this->cubicTo(cx - sx, cy - ry, cx - rx, cy - sy, cx - rx, cy);
676 this->cubicTo(cx - rx, cy + sy, cx - sx, cy + ry, cx, cy + ry);
677 this->cubicTo(cx + sx, cy + ry, cx + rx, cy + sy, cx + rx, cy);
678 } else {
679 this->cubicTo(cx + rx, cy + sy, cx + sx, cy + ry, cx, cy + ry);
680 this->cubicTo(cx - sx, cy + ry, cx - rx, cy + sy, cx - rx, cy);
681 this->cubicTo(cx - rx, cy - sy, cx - sx, cy - ry, cx, cy - ry);
682 this->cubicTo(cx + sx, cy - ry, cx + rx, cy - sy, cx + rx, cy);
683 }
684#else
685 SkScalar sx = SkScalarMul(rx, SK_ScalarTanPIOver8);
686 SkScalar sy = SkScalarMul(ry, SK_ScalarTanPIOver8);
687 SkScalar mx = SkScalarMul(rx, SK_ScalarRoot2Over2);
688 SkScalar my = SkScalarMul(ry, SK_ScalarRoot2Over2);
689
690 /*
691 To handle imprecision in computing the center and radii, we revert to
692 the provided bounds when we can (i.e. use oval.fLeft instead of cx-rx)
693 to ensure that we don't exceed the oval's bounds *ever*, since we want
694 to use oval for our fast-bounds, rather than have to recompute it.
695 */
696 const SkScalar L = oval.fLeft; // cx - rx
697 const SkScalar T = oval.fTop; // cy - ry
698 const SkScalar R = oval.fRight; // cx + rx
699 const SkScalar B = oval.fBottom; // cy + ry
700
701 this->incReserve(17); // 8 quads + close
702 this->moveTo(R, cy);
703 if (dir == kCCW_Direction) {
704 this->quadTo( R, cy - sy, cx + mx, cy - my);
705 this->quadTo(cx + sx, T, cx , T);
706 this->quadTo(cx - sx, T, cx - mx, cy - my);
707 this->quadTo( L, cy - sy, L, cy );
708 this->quadTo( L, cy + sy, cx - mx, cy + my);
709 this->quadTo(cx - sx, B, cx , B);
710 this->quadTo(cx + sx, B, cx + mx, cy + my);
711 this->quadTo( R, cy + sy, R, cy );
712 } else {
713 this->quadTo( R, cy + sy, cx + mx, cy + my);
714 this->quadTo(cx + sx, B, cx , B);
715 this->quadTo(cx - sx, B, cx - mx, cy + my);
716 this->quadTo( L, cy + sy, L, cy );
717 this->quadTo( L, 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( R, cy - sy, R, cy );
721 }
722#endif
723 this->close();
724}
725
726void SkPath::addCircle(SkScalar x, SkScalar y, SkScalar r, Direction dir) {
727 if (r > 0) {
728 SkRect rect;
729 rect.set(x - r, y - r, x + r, y + r);
730 this->addOval(rect, dir);
731 }
732}
733
734#include "SkGeometry.h"
735
736static int build_arc_points(const SkRect& oval, SkScalar startAngle,
737 SkScalar sweepAngle,
738 SkPoint pts[kSkBuildQuadArcStorage]) {
739 SkVector start, stop;
740
741 start.fY = SkScalarSinCos(SkDegreesToRadians(startAngle), &start.fX);
742 stop.fY = SkScalarSinCos(SkDegreesToRadians(startAngle + sweepAngle),
743 &stop.fX);
reed@android.comeebf5cb2010-02-09 18:30:59 +0000744
745 /* If the sweep angle is nearly (but less than) 360, then due to precision
746 loss in radians-conversion and/or sin/cos, we may end up with coincident
747 vectors, which will fool SkBuildQuadArc into doing nothing (bad) instead
748 of drawing a nearly complete circle (good).
749 e.g. canvas.drawArc(0, 359.99, ...)
750 -vs- canvas.drawArc(0, 359.9, ...)
751 We try to detect this edge case, and tweak the stop vector
752 */
753 if (start == stop) {
754 SkScalar sw = SkScalarAbs(sweepAngle);
755 if (sw < SkIntToScalar(360) && sw > SkIntToScalar(359)) {
756 SkScalar stopRad = SkDegreesToRadians(startAngle + sweepAngle);
757 // make a guess at a tiny angle (in radians) to tweak by
758 SkScalar deltaRad = SkScalarCopySign(SK_Scalar1/512, sweepAngle);
759 // not sure how much will be enough, so we use a loop
760 do {
761 stopRad -= deltaRad;
762 stop.fY = SkScalarSinCos(stopRad, &stop.fX);
763 } while (start == stop);
764 }
765 }
766
reed@android.com8a1c16f2008-12-17 15:59:43 +0000767 SkMatrix matrix;
reed@google.comabf15c12011-01-18 20:35:51 +0000768
reed@android.com8a1c16f2008-12-17 15:59:43 +0000769 matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height()));
770 matrix.postTranslate(oval.centerX(), oval.centerY());
reed@google.comabf15c12011-01-18 20:35:51 +0000771
reed@android.com8a1c16f2008-12-17 15:59:43 +0000772 return SkBuildQuadArc(start, stop,
773 sweepAngle > 0 ? kCW_SkRotationDirection : kCCW_SkRotationDirection,
774 &matrix, pts);
775}
776
777void SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
778 bool forceMoveTo) {
779 if (oval.width() < 0 || oval.height() < 0) {
780 return;
781 }
782
783 SkPoint pts[kSkBuildQuadArcStorage];
784 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
785 SkASSERT((count & 1) == 1);
786
787 if (fVerbs.count() == 0) {
788 forceMoveTo = true;
789 }
790 this->incReserve(count);
791 forceMoveTo ? this->moveTo(pts[0]) : this->lineTo(pts[0]);
792 for (int i = 1; i < count; i += 2) {
793 this->quadTo(pts[i], pts[i+1]);
794 }
795}
796
797void SkPath::addArc(const SkRect& oval, SkScalar startAngle,
798 SkScalar sweepAngle) {
799 if (oval.isEmpty() || 0 == sweepAngle) {
800 return;
801 }
802
803 const SkScalar kFullCircleAngle = SkIntToScalar(360);
804
805 if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
806 this->addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction);
807 return;
808 }
809
810 SkPoint pts[kSkBuildQuadArcStorage];
811 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
812
813 this->incReserve(count);
814 this->moveTo(pts[0]);
815 for (int i = 1; i < count; i += 2) {
816 this->quadTo(pts[i], pts[i+1]);
817 }
818}
819
820/*
821 Need to handle the case when the angle is sharp, and our computed end-points
822 for the arc go behind pt1 and/or p2...
823*/
824void SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
825 SkScalar radius) {
826 SkVector before, after;
reed@google.comabf15c12011-01-18 20:35:51 +0000827
reed@android.com8a1c16f2008-12-17 15:59:43 +0000828 // need to know our prev pt so we can construct tangent vectors
829 {
830 SkPoint start;
831 this->getLastPt(&start);
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +0000832 // Handle degenerate cases by adding a line to the first point and
833 // bailing out.
834 if ((x1 == start.fX && y1 == start.fY) ||
835 (x1 == x2 && y1 == y2) ||
836 radius == 0) {
837 this->lineTo(x1, y1);
838 return;
839 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000840 before.setNormalize(x1 - start.fX, y1 - start.fY);
841 after.setNormalize(x2 - x1, y2 - y1);
842 }
reed@google.comabf15c12011-01-18 20:35:51 +0000843
reed@android.com8a1c16f2008-12-17 15:59:43 +0000844 SkScalar cosh = SkPoint::DotProduct(before, after);
845 SkScalar sinh = SkPoint::CrossProduct(before, after);
846
847 if (SkScalarNearlyZero(sinh)) { // angle is too tight
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +0000848 this->lineTo(x1, y1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000849 return;
850 }
reed@google.comabf15c12011-01-18 20:35:51 +0000851
reed@android.com8a1c16f2008-12-17 15:59:43 +0000852 SkScalar dist = SkScalarMulDiv(radius, SK_Scalar1 - cosh, sinh);
853 if (dist < 0) {
854 dist = -dist;
855 }
856
857 SkScalar xx = x1 - SkScalarMul(dist, before.fX);
858 SkScalar yy = y1 - SkScalarMul(dist, before.fY);
859 SkRotationDirection arcDir;
860
861 // now turn before/after into normals
862 if (sinh > 0) {
863 before.rotateCCW();
864 after.rotateCCW();
865 arcDir = kCW_SkRotationDirection;
866 } else {
867 before.rotateCW();
868 after.rotateCW();
869 arcDir = kCCW_SkRotationDirection;
870 }
871
872 SkMatrix matrix;
873 SkPoint pts[kSkBuildQuadArcStorage];
reed@google.comabf15c12011-01-18 20:35:51 +0000874
reed@android.com8a1c16f2008-12-17 15:59:43 +0000875 matrix.setScale(radius, radius);
876 matrix.postTranslate(xx - SkScalarMul(radius, before.fX),
877 yy - SkScalarMul(radius, before.fY));
reed@google.comabf15c12011-01-18 20:35:51 +0000878
reed@android.com8a1c16f2008-12-17 15:59:43 +0000879 int count = SkBuildQuadArc(before, after, arcDir, &matrix, pts);
reed@google.comabf15c12011-01-18 20:35:51 +0000880
reed@android.com8a1c16f2008-12-17 15:59:43 +0000881 this->incReserve(count);
882 // [xx,yy] == pts[0]
883 this->lineTo(xx, yy);
884 for (int i = 1; i < count; i += 2) {
885 this->quadTo(pts[i], pts[i+1]);
886 }
887}
888
889///////////////////////////////////////////////////////////////////////////////
890
891void SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy) {
892 SkMatrix matrix;
893
894 matrix.setTranslate(dx, dy);
895 this->addPath(path, matrix);
896}
897
898void SkPath::addPath(const SkPath& path, const SkMatrix& matrix) {
899 this->incReserve(path.fPts.count());
900
901 Iter iter(path, false);
902 SkPoint pts[4];
903 Verb verb;
904
905 SkMatrix::MapPtsProc proc = matrix.getMapPtsProc();
906
907 while ((verb = iter.next(pts)) != kDone_Verb) {
908 switch (verb) {
909 case kMove_Verb:
910 proc(matrix, &pts[0], &pts[0], 1);
911 this->moveTo(pts[0]);
912 break;
913 case kLine_Verb:
914 proc(matrix, &pts[1], &pts[1], 1);
915 this->lineTo(pts[1]);
916 break;
917 case kQuad_Verb:
918 proc(matrix, &pts[1], &pts[1], 2);
919 this->quadTo(pts[1], pts[2]);
920 break;
921 case kCubic_Verb:
922 proc(matrix, &pts[1], &pts[1], 3);
923 this->cubicTo(pts[1], pts[2], pts[3]);
924 break;
925 case kClose_Verb:
926 this->close();
927 break;
928 default:
929 SkASSERT(!"unknown verb");
930 }
931 }
932}
933
934///////////////////////////////////////////////////////////////////////////////
935
936static const uint8_t gPtsInVerb[] = {
937 1, // kMove
938 1, // kLine
939 2, // kQuad
940 3, // kCubic
941 0, // kClose
942 0 // kDone
943};
944
945// ignore the initial moveto, and stop when the 1st contour ends
946void SkPath::pathTo(const SkPath& path) {
947 int i, vcount = path.fVerbs.count();
948 if (vcount == 0) {
949 return;
950 }
951
952 this->incReserve(vcount);
953
954 const uint8_t* verbs = path.fVerbs.begin();
955 const SkPoint* pts = path.fPts.begin() + 1; // 1 for the initial moveTo
956
957 SkASSERT(verbs[0] == kMove_Verb);
958 for (i = 1; i < vcount; i++) {
959 switch (verbs[i]) {
960 case kLine_Verb:
961 this->lineTo(pts[0].fX, pts[0].fY);
962 break;
963 case kQuad_Verb:
964 this->quadTo(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY);
965 break;
966 case kCubic_Verb:
967 this->cubicTo(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY,
968 pts[2].fX, pts[2].fY);
969 break;
970 case kClose_Verb:
971 return;
972 }
973 pts += gPtsInVerb[verbs[i]];
974 }
975}
976
977// ignore the last point of the 1st contour
978void SkPath::reversePathTo(const SkPath& path) {
979 int i, vcount = path.fVerbs.count();
980 if (vcount == 0) {
981 return;
982 }
983
984 this->incReserve(vcount);
985
986 const uint8_t* verbs = path.fVerbs.begin();
987 const SkPoint* pts = path.fPts.begin();
988
989 SkASSERT(verbs[0] == kMove_Verb);
990 for (i = 1; i < vcount; i++) {
991 int n = gPtsInVerb[verbs[i]];
992 if (n == 0) {
993 break;
994 }
995 pts += n;
996 }
997
998 while (--i > 0) {
999 switch (verbs[i]) {
1000 case kLine_Verb:
1001 this->lineTo(pts[-1].fX, pts[-1].fY);
1002 break;
1003 case kQuad_Verb:
1004 this->quadTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY);
1005 break;
1006 case kCubic_Verb:
1007 this->cubicTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY,
1008 pts[-3].fX, pts[-3].fY);
1009 break;
1010 default:
1011 SkASSERT(!"bad verb");
1012 break;
1013 }
1014 pts -= gPtsInVerb[verbs[i]];
1015 }
1016}
1017
1018///////////////////////////////////////////////////////////////////////////////
1019
1020void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
1021 SkMatrix matrix;
1022
1023 matrix.setTranslate(dx, dy);
1024 this->transform(matrix, dst);
1025}
1026
1027#include "SkGeometry.h"
1028
1029static void subdivide_quad_to(SkPath* path, const SkPoint pts[3],
1030 int level = 2) {
1031 if (--level >= 0) {
1032 SkPoint tmp[5];
1033
1034 SkChopQuadAtHalf(pts, tmp);
1035 subdivide_quad_to(path, &tmp[0], level);
1036 subdivide_quad_to(path, &tmp[2], level);
1037 } else {
1038 path->quadTo(pts[1], pts[2]);
1039 }
1040}
1041
1042static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
1043 int level = 2) {
1044 if (--level >= 0) {
1045 SkPoint tmp[7];
1046
1047 SkChopCubicAtHalf(pts, tmp);
1048 subdivide_cubic_to(path, &tmp[0], level);
1049 subdivide_cubic_to(path, &tmp[3], level);
1050 } else {
1051 path->cubicTo(pts[1], pts[2], pts[3]);
1052 }
1053}
1054
1055void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
1056 SkDEBUGCODE(this->validate();)
1057 if (dst == NULL) {
1058 dst = (SkPath*)this;
1059 }
1060
tomhudson@google.com8d430182011-06-06 19:11:19 +00001061 if (matrix.hasPerspective()) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001062 SkPath tmp;
1063 tmp.fFillType = fFillType;
1064
1065 SkPath::Iter iter(*this, false);
1066 SkPoint pts[4];
1067 SkPath::Verb verb;
1068
1069 while ((verb = iter.next(pts)) != kDone_Verb) {
1070 switch (verb) {
1071 case kMove_Verb:
1072 tmp.moveTo(pts[0]);
1073 break;
1074 case kLine_Verb:
1075 tmp.lineTo(pts[1]);
1076 break;
1077 case kQuad_Verb:
1078 subdivide_quad_to(&tmp, pts);
1079 break;
1080 case kCubic_Verb:
1081 subdivide_cubic_to(&tmp, pts);
1082 break;
1083 case kClose_Verb:
1084 tmp.close();
1085 break;
1086 default:
1087 SkASSERT(!"unknown verb");
1088 break;
1089 }
1090 }
1091
1092 dst->swap(tmp);
1093 matrix.mapPoints(dst->fPts.begin(), dst->fPts.count());
1094 } else {
1095 // remember that dst might == this, so be sure to check
reed@android.comd252db02009-04-01 18:31:44 +00001096 // fBoundsIsDirty before we set it
1097 if (!fBoundsIsDirty && matrix.rectStaysRect() && fPts.count() > 1) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001098 // if we're empty, fastbounds should not be mapped
reed@android.comd252db02009-04-01 18:31:44 +00001099 matrix.mapRect(&dst->fBounds, fBounds);
1100 dst->fBoundsIsDirty = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001101 } else {
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +00001102 GEN_ID_PTR_INC(dst);
reed@android.comd252db02009-04-01 18:31:44 +00001103 dst->fBoundsIsDirty = true;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001104 }
1105
1106 if (this != dst) {
1107 dst->fVerbs = fVerbs;
1108 dst->fPts.setCount(fPts.count());
1109 dst->fFillType = fFillType;
1110 }
1111 matrix.mapPoints(dst->fPts.begin(), fPts.begin(), fPts.count());
1112 SkDEBUGCODE(dst->validate();)
1113 }
1114}
1115
reed@android.com8a1c16f2008-12-17 15:59:43 +00001116///////////////////////////////////////////////////////////////////////////////
1117///////////////////////////////////////////////////////////////////////////////
1118
1119enum NeedMoveToState {
1120 kAfterClose_NeedMoveToState,
1121 kAfterCons_NeedMoveToState,
1122 kAfterPrefix_NeedMoveToState
1123};
1124
1125SkPath::Iter::Iter() {
1126#ifdef SK_DEBUG
1127 fPts = NULL;
1128 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
1129 fForceClose = fNeedMoveTo = fCloseLine = false;
1130#endif
1131 // need to init enough to make next() harmlessly return kDone_Verb
1132 fVerbs = NULL;
1133 fVerbStop = NULL;
1134 fNeedClose = false;
1135}
1136
1137SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
1138 this->setPath(path, forceClose);
1139}
1140
1141void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
1142 fPts = path.fPts.begin();
1143 fVerbs = path.fVerbs.begin();
1144 fVerbStop = path.fVerbs.end();
1145 fForceClose = SkToU8(forceClose);
1146 fNeedClose = false;
1147 fNeedMoveTo = kAfterPrefix_NeedMoveToState;
1148}
1149
1150bool SkPath::Iter::isClosedContour() const {
1151 if (fVerbs == NULL || fVerbs == fVerbStop) {
1152 return false;
1153 }
1154 if (fForceClose) {
1155 return true;
1156 }
1157
1158 const uint8_t* verbs = fVerbs;
1159 const uint8_t* stop = fVerbStop;
reed@google.comabf15c12011-01-18 20:35:51 +00001160
reed@android.com8a1c16f2008-12-17 15:59:43 +00001161 if (kMove_Verb == *verbs) {
1162 verbs += 1; // skip the initial moveto
1163 }
1164
1165 while (verbs < stop) {
reed@google.comabf15c12011-01-18 20:35:51 +00001166 unsigned v = *verbs++;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001167 if (kMove_Verb == v) {
1168 break;
1169 }
1170 if (kClose_Verb == v) {
1171 return true;
1172 }
1173 }
1174 return false;
1175}
1176
1177SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
1178 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001179 // A special case: if both points are NaN, SkPoint::operation== returns
1180 // false, but the iterator expects that they are treated as the same.
1181 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001182 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1183 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001184 return kClose_Verb;
1185 }
1186
reed@android.com8a1c16f2008-12-17 15:59:43 +00001187 if (pts) {
1188 pts[0] = fLastPt;
1189 pts[1] = fMoveTo;
1190 }
1191 fLastPt = fMoveTo;
1192 fCloseLine = true;
1193 return kLine_Verb;
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +00001194 } else {
1195 pts[0] = fMoveTo;
1196 return kClose_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001197 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001198}
1199
1200bool SkPath::Iter::cons_moveTo(SkPoint pts[1]) {
1201 if (fNeedMoveTo == kAfterClose_NeedMoveToState) {
1202 if (pts) {
1203 *pts = fMoveTo;
1204 }
1205 fNeedClose = fForceClose;
1206 fNeedMoveTo = kAfterCons_NeedMoveToState;
1207 fVerbs -= 1;
1208 return true;
1209 }
1210
1211 if (fNeedMoveTo == kAfterCons_NeedMoveToState) {
1212 if (pts) {
1213 *pts = fMoveTo;
1214 }
1215 fNeedMoveTo = kAfterPrefix_NeedMoveToState;
1216 } else {
1217 SkASSERT(fNeedMoveTo == kAfterPrefix_NeedMoveToState);
1218 if (pts) {
1219 *pts = fPts[-1];
1220 }
1221 }
1222 return false;
1223}
1224
1225SkPath::Verb SkPath::Iter::next(SkPoint pts[4]) {
1226 if (fVerbs == fVerbStop) {
1227 if (fNeedClose) {
1228 if (kLine_Verb == this->autoClose(pts)) {
1229 return kLine_Verb;
1230 }
1231 fNeedClose = false;
1232 return kClose_Verb;
1233 }
1234 return kDone_Verb;
1235 }
1236
1237 unsigned verb = *fVerbs++;
1238 const SkPoint* srcPts = fPts;
1239
1240 switch (verb) {
1241 case kMove_Verb:
1242 if (fNeedClose) {
1243 fVerbs -= 1;
1244 verb = this->autoClose(pts);
1245 if (verb == kClose_Verb) {
1246 fNeedClose = false;
1247 }
1248 return (Verb)verb;
1249 }
1250 if (fVerbs == fVerbStop) { // might be a trailing moveto
1251 return kDone_Verb;
1252 }
1253 fMoveTo = *srcPts;
1254 if (pts) {
1255 pts[0] = *srcPts;
1256 }
1257 srcPts += 1;
1258 fNeedMoveTo = kAfterCons_NeedMoveToState;
1259 fNeedClose = fForceClose;
1260 break;
1261 case kLine_Verb:
1262 if (this->cons_moveTo(pts)) {
1263 return kMove_Verb;
1264 }
1265 if (pts) {
1266 pts[1] = srcPts[0];
1267 }
1268 fLastPt = srcPts[0];
1269 fCloseLine = false;
1270 srcPts += 1;
1271 break;
1272 case kQuad_Verb:
1273 if (this->cons_moveTo(pts)) {
1274 return kMove_Verb;
1275 }
1276 if (pts) {
1277 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
1278 }
1279 fLastPt = srcPts[1];
1280 srcPts += 2;
1281 break;
1282 case kCubic_Verb:
1283 if (this->cons_moveTo(pts)) {
1284 return kMove_Verb;
1285 }
1286 if (pts) {
1287 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
1288 }
1289 fLastPt = srcPts[2];
1290 srcPts += 3;
1291 break;
1292 case kClose_Verb:
1293 verb = this->autoClose(pts);
1294 if (verb == kLine_Verb) {
1295 fVerbs -= 1;
1296 } else {
1297 fNeedClose = false;
1298 }
1299 fNeedMoveTo = kAfterClose_NeedMoveToState;
1300 break;
1301 }
1302 fPts = srcPts;
1303 return (Verb)verb;
1304}
1305
1306///////////////////////////////////////////////////////////////////////////////
1307
reed@android.com8a1c16f2008-12-17 15:59:43 +00001308/*
1309 Format in flattened buffer: [ptCount, verbCount, pts[], verbs[]]
1310*/
1311
reed@google.com73945652011-04-25 19:04:27 +00001312void SkPath::flatten(SkWriter32& buffer) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001313 SkDEBUGCODE(this->validate();)
1314
1315 buffer.write32(fPts.count());
1316 buffer.write32(fVerbs.count());
1317 buffer.write32(fFillType);
1318 buffer.writeMul4(fPts.begin(), sizeof(SkPoint) * fPts.count());
1319 buffer.writePad(fVerbs.begin(), fVerbs.count());
1320}
1321
reed@google.com73945652011-04-25 19:04:27 +00001322void SkPath::unflatten(SkReader32& buffer) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001323 fPts.setCount(buffer.readS32());
1324 fVerbs.setCount(buffer.readS32());
1325 fFillType = buffer.readS32();
1326 buffer.read(fPts.begin(), sizeof(SkPoint) * fPts.count());
1327 buffer.read(fVerbs.begin(), fVerbs.count());
reed@google.comabf15c12011-01-18 20:35:51 +00001328
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +00001329 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +00001330 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001331
1332 SkDEBUGCODE(this->validate();)
1333}
1334
1335///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00001336///////////////////////////////////////////////////////////////////////////////
1337
reed@android.com8a1c16f2008-12-17 15:59:43 +00001338void SkPath::dump(bool forceClose, const char title[]) const {
1339 Iter iter(*this, forceClose);
1340 SkPoint pts[4];
1341 Verb verb;
1342
1343 SkDebugf("path: forceClose=%s %s\n", forceClose ? "true" : "false",
1344 title ? title : "");
1345
1346 while ((verb = iter.next(pts)) != kDone_Verb) {
1347 switch (verb) {
1348 case kMove_Verb:
1349#ifdef SK_CAN_USE_FLOAT
1350 SkDebugf(" path: moveTo [%g %g]\n",
1351 SkScalarToFloat(pts[0].fX), SkScalarToFloat(pts[0].fY));
1352#else
1353 SkDebugf(" path: moveTo [%x %x]\n", pts[0].fX, pts[0].fY);
1354#endif
1355 break;
1356 case kLine_Verb:
1357#ifdef SK_CAN_USE_FLOAT
1358 SkDebugf(" path: lineTo [%g %g]\n",
1359 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY));
1360#else
1361 SkDebugf(" path: lineTo [%x %x]\n", pts[1].fX, pts[1].fY);
1362#endif
1363 break;
1364 case kQuad_Verb:
1365#ifdef SK_CAN_USE_FLOAT
1366 SkDebugf(" path: quadTo [%g %g] [%g %g]\n",
1367 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY),
1368 SkScalarToFloat(pts[2].fX), SkScalarToFloat(pts[2].fY));
1369#else
1370 SkDebugf(" path: quadTo [%x %x] [%x %x]\n",
1371 pts[1].fX, pts[1].fY, pts[2].fX, pts[2].fY);
1372#endif
1373 break;
1374 case kCubic_Verb:
1375#ifdef SK_CAN_USE_FLOAT
1376 SkDebugf(" path: cubeTo [%g %g] [%g %g] [%g %g]\n",
1377 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY),
1378 SkScalarToFloat(pts[2].fX), SkScalarToFloat(pts[2].fY),
1379 SkScalarToFloat(pts[3].fX), SkScalarToFloat(pts[3].fY));
1380#else
1381 SkDebugf(" path: cubeTo [%x %x] [%x %x] [%x %x]\n",
1382 pts[1].fX, pts[1].fY, pts[2].fX, pts[2].fY,
1383 pts[3].fX, pts[3].fY);
1384#endif
1385 break;
1386 case kClose_Verb:
1387 SkDebugf(" path: close\n");
1388 break;
1389 default:
1390 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
1391 verb = kDone_Verb; // stop the loop
1392 break;
1393 }
1394 }
1395 SkDebugf("path: done %s\n", title ? title : "");
1396}
1397
reed@android.come522ca52009-11-23 20:10:41 +00001398void SkPath::dump() const {
1399 this->dump(false);
1400}
1401
1402#ifdef SK_DEBUG
1403void SkPath::validate() const {
1404 SkASSERT(this != NULL);
1405 SkASSERT((fFillType & ~3) == 0);
1406 fPts.validate();
1407 fVerbs.validate();
reed@google.comabf15c12011-01-18 20:35:51 +00001408
reed@android.come522ca52009-11-23 20:10:41 +00001409 if (!fBoundsIsDirty) {
1410 SkRect bounds;
1411 compute_pt_bounds(&bounds, fPts);
1412 if (fPts.count() <= 1) {
1413 // if we're empty, fBounds may be empty but translated, so we can't
1414 // necessarily compare to bounds directly
1415 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
1416 // be [2, 2, 2, 2]
1417 SkASSERT(bounds.isEmpty());
1418 SkASSERT(fBounds.isEmpty());
1419 } else {
1420 fBounds.contains(bounds);
1421 }
1422 }
1423}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001424#endif
reed@android.come522ca52009-11-23 20:10:41 +00001425
reed@google.com04863fa2011-05-15 04:08:24 +00001426///////////////////////////////////////////////////////////////////////////////
1427
1428/**
1429 * Returns -1 || 0 || 1 depending on the sign of value:
1430 * -1 if value < 0
1431 * 0 if vlaue == 0
1432 * 1 if value > 0
1433 */
1434static int SkScalarSign(SkScalar value) {
1435 return value < 0 ? -1 : (value > 0);
1436}
1437
reed@google.com0b7b9822011-05-16 12:29:27 +00001438static int sign(SkScalar x) { return x < 0; }
1439#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00001440
reed@google.com04863fa2011-05-15 04:08:24 +00001441static int CrossProductSign(const SkVector& a, const SkVector& b) {
1442 return SkScalarSign(SkPoint::CrossProduct(a, b));
1443}
1444
1445// only valid for a single contour
1446struct Convexicator {
reed@google.comb54455e2011-05-16 14:16:04 +00001447 Convexicator() : fPtCount(0), fConvexity(SkPath::kConvex_Convexity) {
reed@google.com04863fa2011-05-15 04:08:24 +00001448 fSign = 0;
1449 // warnings
1450 fCurrPt.set(0, 0);
1451 fVec0.set(0, 0);
1452 fVec1.set(0, 0);
1453 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00001454
1455 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00001456 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00001457 }
1458
1459 SkPath::Convexity getConvexity() const { return fConvexity; }
1460
1461 void addPt(const SkPoint& pt) {
1462 if (SkPath::kConcave_Convexity == fConvexity) {
1463 return;
1464 }
1465
1466 if (0 == fPtCount) {
1467 fCurrPt = pt;
1468 ++fPtCount;
1469 } else {
1470 SkVector vec = pt - fCurrPt;
1471 if (vec.fX || vec.fY) {
1472 fCurrPt = pt;
1473 if (++fPtCount == 2) {
1474 fFirstVec = fVec1 = vec;
1475 } else {
1476 SkASSERT(fPtCount > 2);
1477 this->addVec(vec);
1478 }
reed@google.com85b6e392011-05-15 20:25:17 +00001479
1480 int sx = sign(vec.fX);
1481 int sy = sign(vec.fY);
1482 fDx += (sx != fSx);
1483 fDy += (sy != fSy);
1484 fSx = sx;
1485 fSy = sy;
1486
1487 if (fDx > 3 || fDy > 3) {
1488 fConvexity = SkPath::kConcave_Convexity;
1489 }
reed@google.com04863fa2011-05-15 04:08:24 +00001490 }
1491 }
1492 }
1493
1494 void close() {
1495 if (fPtCount > 2) {
1496 this->addVec(fFirstVec);
1497 }
1498 }
1499
1500private:
1501 void addVec(const SkVector& vec) {
1502 SkASSERT(vec.fX || vec.fY);
1503 fVec0 = fVec1;
1504 fVec1 = vec;
1505 int sign = CrossProductSign(fVec0, fVec1);
1506 if (0 == fSign) {
1507 fSign = sign;
1508 } else if (sign) {
reed@google.comb54455e2011-05-16 14:16:04 +00001509 if (fSign != sign) {
reed@google.com04863fa2011-05-15 04:08:24 +00001510 fConvexity = SkPath::kConcave_Convexity;
1511 }
1512 }
1513 }
1514
1515 SkPoint fCurrPt;
1516 SkVector fVec0, fVec1, fFirstVec;
1517 int fPtCount; // non-degenerate points
1518 int fSign;
1519 SkPath::Convexity fConvexity;
reed@google.com0b7b9822011-05-16 12:29:27 +00001520 int fDx, fDy, fSx, fSy;
reed@google.com04863fa2011-05-15 04:08:24 +00001521};
1522
1523SkPath::Convexity SkPath::ComputeConvexity(const SkPath& path) {
1524 SkPoint pts[4];
1525 SkPath::Verb verb;
1526 SkPath::Iter iter(path, true);
1527
1528 int contourCount = 0;
1529 int count;
1530 Convexicator state;
1531
1532 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
1533 switch (verb) {
1534 case kMove_Verb:
1535 if (++contourCount > 1) {
1536 return kConcave_Convexity;
1537 }
1538 pts[1] = pts[0];
1539 count = 1;
1540 break;
1541 case kLine_Verb: count = 1; break;
1542 case kQuad_Verb: count = 2; break;
1543 case kCubic_Verb: count = 3; break;
1544 case kClose_Verb:
1545 state.close();
1546 count = 0;
1547 break;
1548 default:
1549 SkASSERT(!"bad verb");
1550 return kConcave_Convexity;
1551 }
1552
1553 for (int i = 1; i <= count; i++) {
1554 state.addPt(pts[i]);
1555 }
1556 // early exit
1557 if (kConcave_Convexity == state.getConvexity()) {
1558 return kConcave_Convexity;
1559 }
1560 }
1561 return state.getConvexity();
1562}