blob: fa6b3b9f66d9204485c8c1834d6fca7419ceaecf [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
189bool SkPath::isRect(SkRect*) const {
190 SkDEBUGCODE(this->validate();)
reed@google.comabf15c12011-01-18 20:35:51 +0000191
reed@android.com8a1c16f2008-12-17 15:59:43 +0000192 SkASSERT(!"unimplemented");
193 return false;
194}
195
196int SkPath::getPoints(SkPoint copy[], int max) const {
197 SkDEBUGCODE(this->validate();)
198
199 SkASSERT(max >= 0);
200 int count = fPts.count();
201 if (copy && max > 0 && count > 0) {
202 memcpy(copy, fPts.begin(), sizeof(SkPoint) * SkMin32(max, count));
203 }
204 return count;
205}
206
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000207SkPoint SkPath::getPoint(int index) const {
208 if ((unsigned)index < (unsigned)fPts.count()) {
209 return fPts[index];
210 }
211 return SkPoint::Make(0, 0);
212}
213
reed@android.com8a1c16f2008-12-17 15:59:43 +0000214void SkPath::getLastPt(SkPoint* lastPt) const {
215 SkDEBUGCODE(this->validate();)
216
217 if (lastPt) {
218 int count = fPts.count();
219 if (count == 0) {
220 lastPt->set(0, 0);
221 } else {
222 *lastPt = fPts[count - 1];
223 }
224 }
225}
226
227void SkPath::setLastPt(SkScalar x, SkScalar y) {
228 SkDEBUGCODE(this->validate();)
229
230 int count = fPts.count();
231 if (count == 0) {
232 this->moveTo(x, y);
233 } else {
234 fPts[count - 1].set(x, y);
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000235 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000236 }
237}
238
reed@android.comd252db02009-04-01 18:31:44 +0000239void SkPath::computeBounds() const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000240 SkDEBUGCODE(this->validate();)
reed@android.comd252db02009-04-01 18:31:44 +0000241 SkASSERT(fBoundsIsDirty);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000242
reed@android.comd252db02009-04-01 18:31:44 +0000243 fBoundsIsDirty = false;
244 compute_pt_bounds(&fBounds, fPts);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000245}
246
reed@google.com04863fa2011-05-15 04:08:24 +0000247void SkPath::setConvexity(Convexity c) {
248 if (fConvexity != c) {
249 fConvexity = c;
250 GEN_ID_INC;
251 }
252}
253
reed@android.com8a1c16f2008-12-17 15:59:43 +0000254//////////////////////////////////////////////////////////////////////////////
255// Construction methods
256
257void SkPath::incReserve(U16CPU inc) {
258 SkDEBUGCODE(this->validate();)
259
260 fVerbs.setReserve(fVerbs.count() + inc);
261 fPts.setReserve(fPts.count() + inc);
262
263 SkDEBUGCODE(this->validate();)
264}
265
266void SkPath::moveTo(SkScalar x, SkScalar y) {
267 SkDEBUGCODE(this->validate();)
268
269 int vc = fVerbs.count();
270 SkPoint* pt;
271
272 if (vc > 0 && fVerbs[vc - 1] == kMove_Verb) {
273 pt = &fPts[fPts.count() - 1];
274 } else {
275 pt = fPts.append();
276 *fVerbs.append() = kMove_Verb;
277 }
278 pt->set(x, y);
279
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000280 GEN_ID_INC;
reed@android.comd252db02009-04-01 18:31:44 +0000281 fBoundsIsDirty = true;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000282}
283
284void SkPath::rMoveTo(SkScalar x, SkScalar y) {
285 SkPoint pt;
286 this->getLastPt(&pt);
287 this->moveTo(pt.fX + x, pt.fY + y);
288}
289
290void SkPath::lineTo(SkScalar x, SkScalar y) {
291 SkDEBUGCODE(this->validate();)
292
293 if (fVerbs.count() == 0) {
294 fPts.append()->set(0, 0);
295 *fVerbs.append() = kMove_Verb;
296 }
297 fPts.append()->set(x, y);
298 *fVerbs.append() = kLine_Verb;
299
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000300 GEN_ID_INC;
reed@android.comd252db02009-04-01 18:31:44 +0000301 fBoundsIsDirty = true;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000302}
303
304void SkPath::rLineTo(SkScalar x, SkScalar y) {
305 SkPoint pt;
306 this->getLastPt(&pt);
307 this->lineTo(pt.fX + x, pt.fY + y);
308}
309
310void SkPath::quadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
311 SkDEBUGCODE(this->validate();)
312
313 if (fVerbs.count() == 0) {
314 fPts.append()->set(0, 0);
315 *fVerbs.append() = kMove_Verb;
316 }
317
318 SkPoint* pts = fPts.append(2);
319 pts[0].set(x1, y1);
320 pts[1].set(x2, y2);
321 *fVerbs.append() = kQuad_Verb;
322
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000323 GEN_ID_INC;
reed@android.comd252db02009-04-01 18:31:44 +0000324 fBoundsIsDirty = true;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000325}
326
327void SkPath::rQuadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
328 SkPoint pt;
329 this->getLastPt(&pt);
330 this->quadTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2);
331}
332
333void SkPath::cubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
334 SkScalar x3, SkScalar y3) {
335 SkDEBUGCODE(this->validate();)
336
337 if (fVerbs.count() == 0) {
338 fPts.append()->set(0, 0);
339 *fVerbs.append() = kMove_Verb;
340 }
341 SkPoint* pts = fPts.append(3);
342 pts[0].set(x1, y1);
343 pts[1].set(x2, y2);
344 pts[2].set(x3, y3);
345 *fVerbs.append() = kCubic_Verb;
346
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000347 GEN_ID_INC;
reed@android.comd252db02009-04-01 18:31:44 +0000348 fBoundsIsDirty = true;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000349}
350
351void SkPath::rCubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
352 SkScalar x3, SkScalar y3) {
353 SkPoint pt;
354 this->getLastPt(&pt);
355 this->cubicTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2,
356 pt.fX + x3, pt.fY + y3);
357}
358
359void SkPath::close() {
360 SkDEBUGCODE(this->validate();)
361
362 int count = fVerbs.count();
363 if (count > 0) {
364 switch (fVerbs[count - 1]) {
365 case kLine_Verb:
366 case kQuad_Verb:
367 case kCubic_Verb:
368 *fVerbs.append() = kClose_Verb;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000369 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000370 break;
371 default:
372 // don't add a close if the prev wasn't a primitive
373 break;
374 }
375 }
376}
377
378///////////////////////////////////////////////////////////////////////////////
reed@google.comabf15c12011-01-18 20:35:51 +0000379
reed@android.com8a1c16f2008-12-17 15:59:43 +0000380void SkPath::addRect(const SkRect& rect, Direction dir) {
381 this->addRect(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, dir);
382}
383
384void SkPath::addRect(SkScalar left, SkScalar top, SkScalar right,
385 SkScalar bottom, Direction dir) {
386 SkAutoPathBoundsUpdate apbu(this, left, top, right, bottom);
387
388 this->incReserve(5);
389
390 this->moveTo(left, top);
391 if (dir == kCCW_Direction) {
392 this->lineTo(left, bottom);
393 this->lineTo(right, bottom);
394 this->lineTo(right, top);
395 } else {
396 this->lineTo(right, top);
397 this->lineTo(right, bottom);
398 this->lineTo(left, bottom);
399 }
400 this->close();
401}
402
403#define CUBIC_ARC_FACTOR ((SK_ScalarSqrt2 - SK_Scalar1) * 4 / 3)
404
405void SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
406 Direction dir) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000407 SkScalar w = rect.width();
408 SkScalar halfW = SkScalarHalf(w);
409 SkScalar h = rect.height();
410 SkScalar halfH = SkScalarHalf(h);
411
412 if (halfW <= 0 || halfH <= 0) {
413 return;
414 }
415
reed@google.comabf15c12011-01-18 20:35:51 +0000416 bool skip_hori = rx >= halfW;
417 bool skip_vert = ry >= halfH;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000418
419 if (skip_hori && skip_vert) {
420 this->addOval(rect, dir);
421 return;
422 }
reed@google.comabf15c12011-01-18 20:35:51 +0000423
424 SkAutoPathBoundsUpdate apbu(this, rect);
425
reed@android.com8a1c16f2008-12-17 15:59:43 +0000426 if (skip_hori) {
427 rx = halfW;
428 } else if (skip_vert) {
429 ry = halfH;
430 }
431
432 SkScalar sx = SkScalarMul(rx, CUBIC_ARC_FACTOR);
433 SkScalar sy = SkScalarMul(ry, CUBIC_ARC_FACTOR);
434
435 this->incReserve(17);
436 this->moveTo(rect.fRight - rx, rect.fTop);
437 if (dir == kCCW_Direction) {
438 if (!skip_hori) {
439 this->lineTo(rect.fLeft + rx, rect.fTop); // top
440 }
441 this->cubicTo(rect.fLeft + rx - sx, rect.fTop,
442 rect.fLeft, rect.fTop + ry - sy,
443 rect.fLeft, rect.fTop + ry); // top-left
444 if (!skip_vert) {
445 this->lineTo(rect.fLeft, rect.fBottom - ry); // left
446 }
447 this->cubicTo(rect.fLeft, rect.fBottom - ry + sy,
448 rect.fLeft + rx - sx, rect.fBottom,
449 rect.fLeft + rx, rect.fBottom); // bot-left
450 if (!skip_hori) {
451 this->lineTo(rect.fRight - rx, rect.fBottom); // bottom
452 }
453 this->cubicTo(rect.fRight - rx + sx, rect.fBottom,
454 rect.fRight, rect.fBottom - ry + sy,
455 rect.fRight, rect.fBottom - ry); // bot-right
456 if (!skip_vert) {
457 this->lineTo(rect.fRight, rect.fTop + ry);
458 }
459 this->cubicTo(rect.fRight, rect.fTop + ry - sy,
460 rect.fRight - rx + sx, rect.fTop,
461 rect.fRight - rx, rect.fTop); // top-right
462 } else {
463 this->cubicTo(rect.fRight - rx + sx, rect.fTop,
464 rect.fRight, rect.fTop + ry - sy,
465 rect.fRight, rect.fTop + ry); // top-right
466 if (!skip_vert) {
467 this->lineTo(rect.fRight, rect.fBottom - ry);
468 }
469 this->cubicTo(rect.fRight, rect.fBottom - ry + sy,
470 rect.fRight - rx + sx, rect.fBottom,
471 rect.fRight - rx, rect.fBottom); // bot-right
472 if (!skip_hori) {
473 this->lineTo(rect.fLeft + rx, rect.fBottom); // bottom
474 }
475 this->cubicTo(rect.fLeft + rx - sx, rect.fBottom,
476 rect.fLeft, rect.fBottom - ry + sy,
477 rect.fLeft, rect.fBottom - ry); // bot-left
478 if (!skip_vert) {
479 this->lineTo(rect.fLeft, rect.fTop + ry); // left
480 }
481 this->cubicTo(rect.fLeft, rect.fTop + ry - sy,
482 rect.fLeft + rx - sx, rect.fTop,
483 rect.fLeft + rx, rect.fTop); // top-left
484 if (!skip_hori) {
485 this->lineTo(rect.fRight - rx, rect.fTop); // top
486 }
487 }
488 this->close();
489}
490
491static void add_corner_arc(SkPath* path, const SkRect& rect,
492 SkScalar rx, SkScalar ry, int startAngle,
493 SkPath::Direction dir, bool forceMoveTo) {
494 rx = SkMinScalar(SkScalarHalf(rect.width()), rx);
495 ry = SkMinScalar(SkScalarHalf(rect.height()), ry);
reed@google.comabf15c12011-01-18 20:35:51 +0000496
reed@android.com8a1c16f2008-12-17 15:59:43 +0000497 SkRect r;
498 r.set(-rx, -ry, rx, ry);
499
500 switch (startAngle) {
501 case 0:
502 r.offset(rect.fRight - r.fRight, rect.fBottom - r.fBottom);
503 break;
504 case 90:
505 r.offset(rect.fLeft - r.fLeft, rect.fBottom - r.fBottom);
506 break;
507 case 180: r.offset(rect.fLeft - r.fLeft, rect.fTop - r.fTop); break;
508 case 270: r.offset(rect.fRight - r.fRight, rect.fTop - r.fTop); break;
509 default: SkASSERT(!"unexpected startAngle in add_corner_arc");
510 }
reed@google.comabf15c12011-01-18 20:35:51 +0000511
reed@android.com8a1c16f2008-12-17 15:59:43 +0000512 SkScalar start = SkIntToScalar(startAngle);
513 SkScalar sweep = SkIntToScalar(90);
514 if (SkPath::kCCW_Direction == dir) {
515 start += sweep;
516 sweep = -sweep;
517 }
reed@google.comabf15c12011-01-18 20:35:51 +0000518
reed@android.com8a1c16f2008-12-17 15:59:43 +0000519 path->arcTo(r, start, sweep, forceMoveTo);
520}
521
522void SkPath::addRoundRect(const SkRect& rect, const SkScalar rad[],
523 Direction dir) {
reed@google.com44b2c732011-01-18 20:55:57 +0000524 // abort before we invoke SkAutoPathBoundsUpdate()
525 if (rect.isEmpty()) {
526 return;
527 }
528
reed@android.com8a1c16f2008-12-17 15:59:43 +0000529 SkAutoPathBoundsUpdate apbu(this, rect);
530
531 if (kCW_Direction == dir) {
532 add_corner_arc(this, rect, rad[0], rad[1], 180, dir, true);
533 add_corner_arc(this, rect, rad[2], rad[3], 270, dir, false);
534 add_corner_arc(this, rect, rad[4], rad[5], 0, dir, false);
535 add_corner_arc(this, rect, rad[6], rad[7], 90, dir, false);
536 } else {
537 add_corner_arc(this, rect, rad[0], rad[1], 180, dir, true);
538 add_corner_arc(this, rect, rad[6], rad[7], 90, dir, false);
539 add_corner_arc(this, rect, rad[4], rad[5], 0, dir, false);
540 add_corner_arc(this, rect, rad[2], rad[3], 270, dir, false);
541 }
542 this->close();
543}
544
545void SkPath::addOval(const SkRect& oval, Direction dir) {
546 SkAutoPathBoundsUpdate apbu(this, oval);
547
548 SkScalar cx = oval.centerX();
549 SkScalar cy = oval.centerY();
550 SkScalar rx = SkScalarHalf(oval.width());
551 SkScalar ry = SkScalarHalf(oval.height());
552#if 0 // these seem faster than using quads (1/2 the number of edges)
553 SkScalar sx = SkScalarMul(rx, CUBIC_ARC_FACTOR);
554 SkScalar sy = SkScalarMul(ry, CUBIC_ARC_FACTOR);
555
556 this->incReserve(13);
557 this->moveTo(cx + rx, cy);
558 if (dir == kCCW_Direction) {
559 this->cubicTo(cx + rx, cy - sy, cx + sx, cy - ry, cx, cy - ry);
560 this->cubicTo(cx - sx, cy - ry, cx - rx, cy - sy, cx - rx, cy);
561 this->cubicTo(cx - rx, cy + sy, cx - sx, cy + ry, cx, cy + ry);
562 this->cubicTo(cx + sx, cy + ry, cx + rx, cy + sy, cx + rx, cy);
563 } else {
564 this->cubicTo(cx + rx, cy + sy, cx + sx, cy + ry, cx, cy + ry);
565 this->cubicTo(cx - sx, cy + ry, cx - rx, cy + sy, cx - rx, cy);
566 this->cubicTo(cx - rx, cy - sy, cx - sx, cy - ry, cx, cy - ry);
567 this->cubicTo(cx + sx, cy - ry, cx + rx, cy - sy, cx + rx, cy);
568 }
569#else
570 SkScalar sx = SkScalarMul(rx, SK_ScalarTanPIOver8);
571 SkScalar sy = SkScalarMul(ry, SK_ScalarTanPIOver8);
572 SkScalar mx = SkScalarMul(rx, SK_ScalarRoot2Over2);
573 SkScalar my = SkScalarMul(ry, SK_ScalarRoot2Over2);
574
575 /*
576 To handle imprecision in computing the center and radii, we revert to
577 the provided bounds when we can (i.e. use oval.fLeft instead of cx-rx)
578 to ensure that we don't exceed the oval's bounds *ever*, since we want
579 to use oval for our fast-bounds, rather than have to recompute it.
580 */
581 const SkScalar L = oval.fLeft; // cx - rx
582 const SkScalar T = oval.fTop; // cy - ry
583 const SkScalar R = oval.fRight; // cx + rx
584 const SkScalar B = oval.fBottom; // cy + ry
585
586 this->incReserve(17); // 8 quads + close
587 this->moveTo(R, cy);
588 if (dir == kCCW_Direction) {
589 this->quadTo( R, cy - sy, cx + mx, cy - my);
590 this->quadTo(cx + sx, T, cx , T);
591 this->quadTo(cx - sx, T, cx - mx, cy - my);
592 this->quadTo( L, cy - sy, L, cy );
593 this->quadTo( L, cy + sy, cx - mx, cy + my);
594 this->quadTo(cx - sx, B, cx , B);
595 this->quadTo(cx + sx, B, cx + mx, cy + my);
596 this->quadTo( R, cy + sy, R, cy );
597 } else {
598 this->quadTo( R, cy + sy, cx + mx, cy + my);
599 this->quadTo(cx + sx, B, cx , B);
600 this->quadTo(cx - sx, B, cx - mx, cy + my);
601 this->quadTo( L, cy + sy, L, cy );
602 this->quadTo( L, cy - sy, cx - mx, cy - my);
603 this->quadTo(cx - sx, T, cx , T);
604 this->quadTo(cx + sx, T, cx + mx, cy - my);
605 this->quadTo( R, cy - sy, R, cy );
606 }
607#endif
608 this->close();
609}
610
611void SkPath::addCircle(SkScalar x, SkScalar y, SkScalar r, Direction dir) {
612 if (r > 0) {
613 SkRect rect;
614 rect.set(x - r, y - r, x + r, y + r);
615 this->addOval(rect, dir);
616 }
617}
618
619#include "SkGeometry.h"
620
621static int build_arc_points(const SkRect& oval, SkScalar startAngle,
622 SkScalar sweepAngle,
623 SkPoint pts[kSkBuildQuadArcStorage]) {
624 SkVector start, stop;
625
626 start.fY = SkScalarSinCos(SkDegreesToRadians(startAngle), &start.fX);
627 stop.fY = SkScalarSinCos(SkDegreesToRadians(startAngle + sweepAngle),
628 &stop.fX);
reed@android.comeebf5cb2010-02-09 18:30:59 +0000629
630 /* If the sweep angle is nearly (but less than) 360, then due to precision
631 loss in radians-conversion and/or sin/cos, we may end up with coincident
632 vectors, which will fool SkBuildQuadArc into doing nothing (bad) instead
633 of drawing a nearly complete circle (good).
634 e.g. canvas.drawArc(0, 359.99, ...)
635 -vs- canvas.drawArc(0, 359.9, ...)
636 We try to detect this edge case, and tweak the stop vector
637 */
638 if (start == stop) {
639 SkScalar sw = SkScalarAbs(sweepAngle);
640 if (sw < SkIntToScalar(360) && sw > SkIntToScalar(359)) {
641 SkScalar stopRad = SkDegreesToRadians(startAngle + sweepAngle);
642 // make a guess at a tiny angle (in radians) to tweak by
643 SkScalar deltaRad = SkScalarCopySign(SK_Scalar1/512, sweepAngle);
644 // not sure how much will be enough, so we use a loop
645 do {
646 stopRad -= deltaRad;
647 stop.fY = SkScalarSinCos(stopRad, &stop.fX);
648 } while (start == stop);
649 }
650 }
651
reed@android.com8a1c16f2008-12-17 15:59:43 +0000652 SkMatrix matrix;
reed@google.comabf15c12011-01-18 20:35:51 +0000653
reed@android.com8a1c16f2008-12-17 15:59:43 +0000654 matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height()));
655 matrix.postTranslate(oval.centerX(), oval.centerY());
reed@google.comabf15c12011-01-18 20:35:51 +0000656
reed@android.com8a1c16f2008-12-17 15:59:43 +0000657 return SkBuildQuadArc(start, stop,
658 sweepAngle > 0 ? kCW_SkRotationDirection : kCCW_SkRotationDirection,
659 &matrix, pts);
660}
661
662void SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
663 bool forceMoveTo) {
664 if (oval.width() < 0 || oval.height() < 0) {
665 return;
666 }
667
668 SkPoint pts[kSkBuildQuadArcStorage];
669 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
670 SkASSERT((count & 1) == 1);
671
672 if (fVerbs.count() == 0) {
673 forceMoveTo = true;
674 }
675 this->incReserve(count);
676 forceMoveTo ? this->moveTo(pts[0]) : this->lineTo(pts[0]);
677 for (int i = 1; i < count; i += 2) {
678 this->quadTo(pts[i], pts[i+1]);
679 }
680}
681
682void SkPath::addArc(const SkRect& oval, SkScalar startAngle,
683 SkScalar sweepAngle) {
684 if (oval.isEmpty() || 0 == sweepAngle) {
685 return;
686 }
687
688 const SkScalar kFullCircleAngle = SkIntToScalar(360);
689
690 if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
691 this->addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction);
692 return;
693 }
694
695 SkPoint pts[kSkBuildQuadArcStorage];
696 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
697
698 this->incReserve(count);
699 this->moveTo(pts[0]);
700 for (int i = 1; i < count; i += 2) {
701 this->quadTo(pts[i], pts[i+1]);
702 }
703}
704
705/*
706 Need to handle the case when the angle is sharp, and our computed end-points
707 for the arc go behind pt1 and/or p2...
708*/
709void SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
710 SkScalar radius) {
711 SkVector before, after;
reed@google.comabf15c12011-01-18 20:35:51 +0000712
reed@android.com8a1c16f2008-12-17 15:59:43 +0000713 // need to know our prev pt so we can construct tangent vectors
714 {
715 SkPoint start;
716 this->getLastPt(&start);
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +0000717 // Handle degenerate cases by adding a line to the first point and
718 // bailing out.
719 if ((x1 == start.fX && y1 == start.fY) ||
720 (x1 == x2 && y1 == y2) ||
721 radius == 0) {
722 this->lineTo(x1, y1);
723 return;
724 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000725 before.setNormalize(x1 - start.fX, y1 - start.fY);
726 after.setNormalize(x2 - x1, y2 - y1);
727 }
reed@google.comabf15c12011-01-18 20:35:51 +0000728
reed@android.com8a1c16f2008-12-17 15:59:43 +0000729 SkScalar cosh = SkPoint::DotProduct(before, after);
730 SkScalar sinh = SkPoint::CrossProduct(before, after);
731
732 if (SkScalarNearlyZero(sinh)) { // angle is too tight
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +0000733 this->lineTo(x1, y1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000734 return;
735 }
reed@google.comabf15c12011-01-18 20:35:51 +0000736
reed@android.com8a1c16f2008-12-17 15:59:43 +0000737 SkScalar dist = SkScalarMulDiv(radius, SK_Scalar1 - cosh, sinh);
738 if (dist < 0) {
739 dist = -dist;
740 }
741
742 SkScalar xx = x1 - SkScalarMul(dist, before.fX);
743 SkScalar yy = y1 - SkScalarMul(dist, before.fY);
744 SkRotationDirection arcDir;
745
746 // now turn before/after into normals
747 if (sinh > 0) {
748 before.rotateCCW();
749 after.rotateCCW();
750 arcDir = kCW_SkRotationDirection;
751 } else {
752 before.rotateCW();
753 after.rotateCW();
754 arcDir = kCCW_SkRotationDirection;
755 }
756
757 SkMatrix matrix;
758 SkPoint pts[kSkBuildQuadArcStorage];
reed@google.comabf15c12011-01-18 20:35:51 +0000759
reed@android.com8a1c16f2008-12-17 15:59:43 +0000760 matrix.setScale(radius, radius);
761 matrix.postTranslate(xx - SkScalarMul(radius, before.fX),
762 yy - SkScalarMul(radius, before.fY));
reed@google.comabf15c12011-01-18 20:35:51 +0000763
reed@android.com8a1c16f2008-12-17 15:59:43 +0000764 int count = SkBuildQuadArc(before, after, arcDir, &matrix, pts);
reed@google.comabf15c12011-01-18 20:35:51 +0000765
reed@android.com8a1c16f2008-12-17 15:59:43 +0000766 this->incReserve(count);
767 // [xx,yy] == pts[0]
768 this->lineTo(xx, yy);
769 for (int i = 1; i < count; i += 2) {
770 this->quadTo(pts[i], pts[i+1]);
771 }
772}
773
774///////////////////////////////////////////////////////////////////////////////
775
776void SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy) {
777 SkMatrix matrix;
778
779 matrix.setTranslate(dx, dy);
780 this->addPath(path, matrix);
781}
782
783void SkPath::addPath(const SkPath& path, const SkMatrix& matrix) {
784 this->incReserve(path.fPts.count());
785
786 Iter iter(path, false);
787 SkPoint pts[4];
788 Verb verb;
789
790 SkMatrix::MapPtsProc proc = matrix.getMapPtsProc();
791
792 while ((verb = iter.next(pts)) != kDone_Verb) {
793 switch (verb) {
794 case kMove_Verb:
795 proc(matrix, &pts[0], &pts[0], 1);
796 this->moveTo(pts[0]);
797 break;
798 case kLine_Verb:
799 proc(matrix, &pts[1], &pts[1], 1);
800 this->lineTo(pts[1]);
801 break;
802 case kQuad_Verb:
803 proc(matrix, &pts[1], &pts[1], 2);
804 this->quadTo(pts[1], pts[2]);
805 break;
806 case kCubic_Verb:
807 proc(matrix, &pts[1], &pts[1], 3);
808 this->cubicTo(pts[1], pts[2], pts[3]);
809 break;
810 case kClose_Verb:
811 this->close();
812 break;
813 default:
814 SkASSERT(!"unknown verb");
815 }
816 }
817}
818
819///////////////////////////////////////////////////////////////////////////////
820
821static const uint8_t gPtsInVerb[] = {
822 1, // kMove
823 1, // kLine
824 2, // kQuad
825 3, // kCubic
826 0, // kClose
827 0 // kDone
828};
829
830// ignore the initial moveto, and stop when the 1st contour ends
831void SkPath::pathTo(const SkPath& path) {
832 int i, vcount = path.fVerbs.count();
833 if (vcount == 0) {
834 return;
835 }
836
837 this->incReserve(vcount);
838
839 const uint8_t* verbs = path.fVerbs.begin();
840 const SkPoint* pts = path.fPts.begin() + 1; // 1 for the initial moveTo
841
842 SkASSERT(verbs[0] == kMove_Verb);
843 for (i = 1; i < vcount; i++) {
844 switch (verbs[i]) {
845 case kLine_Verb:
846 this->lineTo(pts[0].fX, pts[0].fY);
847 break;
848 case kQuad_Verb:
849 this->quadTo(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY);
850 break;
851 case kCubic_Verb:
852 this->cubicTo(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY,
853 pts[2].fX, pts[2].fY);
854 break;
855 case kClose_Verb:
856 return;
857 }
858 pts += gPtsInVerb[verbs[i]];
859 }
860}
861
862// ignore the last point of the 1st contour
863void SkPath::reversePathTo(const SkPath& path) {
864 int i, vcount = path.fVerbs.count();
865 if (vcount == 0) {
866 return;
867 }
868
869 this->incReserve(vcount);
870
871 const uint8_t* verbs = path.fVerbs.begin();
872 const SkPoint* pts = path.fPts.begin();
873
874 SkASSERT(verbs[0] == kMove_Verb);
875 for (i = 1; i < vcount; i++) {
876 int n = gPtsInVerb[verbs[i]];
877 if (n == 0) {
878 break;
879 }
880 pts += n;
881 }
882
883 while (--i > 0) {
884 switch (verbs[i]) {
885 case kLine_Verb:
886 this->lineTo(pts[-1].fX, pts[-1].fY);
887 break;
888 case kQuad_Verb:
889 this->quadTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY);
890 break;
891 case kCubic_Verb:
892 this->cubicTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY,
893 pts[-3].fX, pts[-3].fY);
894 break;
895 default:
896 SkASSERT(!"bad verb");
897 break;
898 }
899 pts -= gPtsInVerb[verbs[i]];
900 }
901}
902
903///////////////////////////////////////////////////////////////////////////////
904
905void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
906 SkMatrix matrix;
907
908 matrix.setTranslate(dx, dy);
909 this->transform(matrix, dst);
910}
911
912#include "SkGeometry.h"
913
914static void subdivide_quad_to(SkPath* path, const SkPoint pts[3],
915 int level = 2) {
916 if (--level >= 0) {
917 SkPoint tmp[5];
918
919 SkChopQuadAtHalf(pts, tmp);
920 subdivide_quad_to(path, &tmp[0], level);
921 subdivide_quad_to(path, &tmp[2], level);
922 } else {
923 path->quadTo(pts[1], pts[2]);
924 }
925}
926
927static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
928 int level = 2) {
929 if (--level >= 0) {
930 SkPoint tmp[7];
931
932 SkChopCubicAtHalf(pts, tmp);
933 subdivide_cubic_to(path, &tmp[0], level);
934 subdivide_cubic_to(path, &tmp[3], level);
935 } else {
936 path->cubicTo(pts[1], pts[2], pts[3]);
937 }
938}
939
940void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
941 SkDEBUGCODE(this->validate();)
942 if (dst == NULL) {
943 dst = (SkPath*)this;
944 }
945
946 if (matrix.getType() & SkMatrix::kPerspective_Mask) {
947 SkPath tmp;
948 tmp.fFillType = fFillType;
949
950 SkPath::Iter iter(*this, false);
951 SkPoint pts[4];
952 SkPath::Verb verb;
953
954 while ((verb = iter.next(pts)) != kDone_Verb) {
955 switch (verb) {
956 case kMove_Verb:
957 tmp.moveTo(pts[0]);
958 break;
959 case kLine_Verb:
960 tmp.lineTo(pts[1]);
961 break;
962 case kQuad_Verb:
963 subdivide_quad_to(&tmp, pts);
964 break;
965 case kCubic_Verb:
966 subdivide_cubic_to(&tmp, pts);
967 break;
968 case kClose_Verb:
969 tmp.close();
970 break;
971 default:
972 SkASSERT(!"unknown verb");
973 break;
974 }
975 }
976
977 dst->swap(tmp);
978 matrix.mapPoints(dst->fPts.begin(), dst->fPts.count());
979 } else {
980 // remember that dst might == this, so be sure to check
reed@android.comd252db02009-04-01 18:31:44 +0000981 // fBoundsIsDirty before we set it
982 if (!fBoundsIsDirty && matrix.rectStaysRect() && fPts.count() > 1) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000983 // if we're empty, fastbounds should not be mapped
reed@android.comd252db02009-04-01 18:31:44 +0000984 matrix.mapRect(&dst->fBounds, fBounds);
985 dst->fBoundsIsDirty = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000986 } else {
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000987 GEN_ID_PTR_INC(dst);
reed@android.comd252db02009-04-01 18:31:44 +0000988 dst->fBoundsIsDirty = true;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000989 }
990
991 if (this != dst) {
992 dst->fVerbs = fVerbs;
993 dst->fPts.setCount(fPts.count());
994 dst->fFillType = fFillType;
995 }
996 matrix.mapPoints(dst->fPts.begin(), fPts.begin(), fPts.count());
997 SkDEBUGCODE(dst->validate();)
998 }
999}
1000
reed@android.com8a1c16f2008-12-17 15:59:43 +00001001///////////////////////////////////////////////////////////////////////////////
1002///////////////////////////////////////////////////////////////////////////////
1003
1004enum NeedMoveToState {
1005 kAfterClose_NeedMoveToState,
1006 kAfterCons_NeedMoveToState,
1007 kAfterPrefix_NeedMoveToState
1008};
1009
1010SkPath::Iter::Iter() {
1011#ifdef SK_DEBUG
1012 fPts = NULL;
1013 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
1014 fForceClose = fNeedMoveTo = fCloseLine = false;
1015#endif
1016 // need to init enough to make next() harmlessly return kDone_Verb
1017 fVerbs = NULL;
1018 fVerbStop = NULL;
1019 fNeedClose = false;
1020}
1021
1022SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
1023 this->setPath(path, forceClose);
1024}
1025
1026void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
1027 fPts = path.fPts.begin();
1028 fVerbs = path.fVerbs.begin();
1029 fVerbStop = path.fVerbs.end();
1030 fForceClose = SkToU8(forceClose);
1031 fNeedClose = false;
1032 fNeedMoveTo = kAfterPrefix_NeedMoveToState;
1033}
1034
1035bool SkPath::Iter::isClosedContour() const {
1036 if (fVerbs == NULL || fVerbs == fVerbStop) {
1037 return false;
1038 }
1039 if (fForceClose) {
1040 return true;
1041 }
1042
1043 const uint8_t* verbs = fVerbs;
1044 const uint8_t* stop = fVerbStop;
reed@google.comabf15c12011-01-18 20:35:51 +00001045
reed@android.com8a1c16f2008-12-17 15:59:43 +00001046 if (kMove_Verb == *verbs) {
1047 verbs += 1; // skip the initial moveto
1048 }
1049
1050 while (verbs < stop) {
reed@google.comabf15c12011-01-18 20:35:51 +00001051 unsigned v = *verbs++;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001052 if (kMove_Verb == v) {
1053 break;
1054 }
1055 if (kClose_Verb == v) {
1056 return true;
1057 }
1058 }
1059 return false;
1060}
1061
1062SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
1063 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001064 // A special case: if both points are NaN, SkPoint::operation== returns
1065 // false, but the iterator expects that they are treated as the same.
1066 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001067 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1068 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001069 return kClose_Verb;
1070 }
1071
reed@android.com8a1c16f2008-12-17 15:59:43 +00001072 if (pts) {
1073 pts[0] = fLastPt;
1074 pts[1] = fMoveTo;
1075 }
1076 fLastPt = fMoveTo;
1077 fCloseLine = true;
1078 return kLine_Verb;
1079 }
1080 return kClose_Verb;
1081}
1082
1083bool SkPath::Iter::cons_moveTo(SkPoint pts[1]) {
1084 if (fNeedMoveTo == kAfterClose_NeedMoveToState) {
1085 if (pts) {
1086 *pts = fMoveTo;
1087 }
1088 fNeedClose = fForceClose;
1089 fNeedMoveTo = kAfterCons_NeedMoveToState;
1090 fVerbs -= 1;
1091 return true;
1092 }
1093
1094 if (fNeedMoveTo == kAfterCons_NeedMoveToState) {
1095 if (pts) {
1096 *pts = fMoveTo;
1097 }
1098 fNeedMoveTo = kAfterPrefix_NeedMoveToState;
1099 } else {
1100 SkASSERT(fNeedMoveTo == kAfterPrefix_NeedMoveToState);
1101 if (pts) {
1102 *pts = fPts[-1];
1103 }
1104 }
1105 return false;
1106}
1107
1108SkPath::Verb SkPath::Iter::next(SkPoint pts[4]) {
1109 if (fVerbs == fVerbStop) {
1110 if (fNeedClose) {
1111 if (kLine_Verb == this->autoClose(pts)) {
1112 return kLine_Verb;
1113 }
1114 fNeedClose = false;
1115 return kClose_Verb;
1116 }
1117 return kDone_Verb;
1118 }
1119
1120 unsigned verb = *fVerbs++;
1121 const SkPoint* srcPts = fPts;
1122
1123 switch (verb) {
1124 case kMove_Verb:
1125 if (fNeedClose) {
1126 fVerbs -= 1;
1127 verb = this->autoClose(pts);
1128 if (verb == kClose_Verb) {
1129 fNeedClose = false;
1130 }
1131 return (Verb)verb;
1132 }
1133 if (fVerbs == fVerbStop) { // might be a trailing moveto
1134 return kDone_Verb;
1135 }
1136 fMoveTo = *srcPts;
1137 if (pts) {
1138 pts[0] = *srcPts;
1139 }
1140 srcPts += 1;
1141 fNeedMoveTo = kAfterCons_NeedMoveToState;
1142 fNeedClose = fForceClose;
1143 break;
1144 case kLine_Verb:
1145 if (this->cons_moveTo(pts)) {
1146 return kMove_Verb;
1147 }
1148 if (pts) {
1149 pts[1] = srcPts[0];
1150 }
1151 fLastPt = srcPts[0];
1152 fCloseLine = false;
1153 srcPts += 1;
1154 break;
1155 case kQuad_Verb:
1156 if (this->cons_moveTo(pts)) {
1157 return kMove_Verb;
1158 }
1159 if (pts) {
1160 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
1161 }
1162 fLastPt = srcPts[1];
1163 srcPts += 2;
1164 break;
1165 case kCubic_Verb:
1166 if (this->cons_moveTo(pts)) {
1167 return kMove_Verb;
1168 }
1169 if (pts) {
1170 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
1171 }
1172 fLastPt = srcPts[2];
1173 srcPts += 3;
1174 break;
1175 case kClose_Verb:
1176 verb = this->autoClose(pts);
1177 if (verb == kLine_Verb) {
1178 fVerbs -= 1;
1179 } else {
1180 fNeedClose = false;
1181 }
1182 fNeedMoveTo = kAfterClose_NeedMoveToState;
1183 break;
1184 }
1185 fPts = srcPts;
1186 return (Verb)verb;
1187}
1188
1189///////////////////////////////////////////////////////////////////////////////
1190
1191static bool exceeds_dist(const SkScalar p[], const SkScalar q[], SkScalar dist,
1192 int count) {
1193 SkASSERT(dist > 0);
1194
1195 count *= 2;
1196 for (int i = 0; i < count; i++) {
1197 if (SkScalarAbs(p[i] - q[i]) > dist) {
1198 return true;
1199 }
1200 }
1201 return false;
1202}
1203
1204static void subdivide_quad(SkPath* dst, const SkPoint pts[3], SkScalar dist,
1205 int subLevel = 4) {
1206 if (--subLevel >= 0 && exceeds_dist(&pts[0].fX, &pts[1].fX, dist, 4)) {
1207 SkPoint tmp[5];
1208 SkChopQuadAtHalf(pts, tmp);
1209
1210 subdivide_quad(dst, &tmp[0], dist, subLevel);
1211 subdivide_quad(dst, &tmp[2], dist, subLevel);
1212 } else {
1213 dst->quadTo(pts[1], pts[2]);
1214 }
1215}
1216
1217static void subdivide_cubic(SkPath* dst, const SkPoint pts[4], SkScalar dist,
1218 int subLevel = 4) {
1219 if (--subLevel >= 0 && exceeds_dist(&pts[0].fX, &pts[1].fX, dist, 6)) {
1220 SkPoint tmp[7];
1221 SkChopCubicAtHalf(pts, tmp);
1222
1223 subdivide_cubic(dst, &tmp[0], dist, subLevel);
1224 subdivide_cubic(dst, &tmp[3], dist, subLevel);
1225 } else {
1226 dst->cubicTo(pts[1], pts[2], pts[3]);
1227 }
1228}
1229
1230void SkPath::subdivide(SkScalar dist, bool bendLines, SkPath* dst) const {
1231 SkPath tmpPath;
1232 if (NULL == dst || this == dst) {
1233 dst = &tmpPath;
1234 }
1235
1236 SkPath::Iter iter(*this, false);
1237 SkPoint pts[4];
1238
1239 for (;;) {
1240 switch (iter.next(pts)) {
1241 case SkPath::kMove_Verb:
1242 dst->moveTo(pts[0]);
1243 break;
1244 case SkPath::kLine_Verb:
1245 if (!bendLines) {
1246 dst->lineTo(pts[1]);
1247 break;
1248 }
1249 // construct a quad from the line
1250 pts[2] = pts[1];
1251 pts[1].set(SkScalarAve(pts[0].fX, pts[2].fX),
1252 SkScalarAve(pts[0].fY, pts[2].fY));
1253 // fall through to the quad case
1254 case SkPath::kQuad_Verb:
1255 subdivide_quad(dst, pts, dist);
1256 break;
1257 case SkPath::kCubic_Verb:
1258 subdivide_cubic(dst, pts, dist);
1259 break;
1260 case SkPath::kClose_Verb:
1261 dst->close();
1262 break;
1263 case SkPath::kDone_Verb:
1264 goto DONE;
1265 }
1266 }
1267DONE:
1268 if (&tmpPath == dst) { // i.e. the dst should be us
1269 dst->swap(*(SkPath*)this);
1270 }
1271}
1272
1273///////////////////////////////////////////////////////////////////////
1274/*
1275 Format in flattened buffer: [ptCount, verbCount, pts[], verbs[]]
1276*/
1277
reed@google.com73945652011-04-25 19:04:27 +00001278void SkPath::flatten(SkWriter32& buffer) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001279 SkDEBUGCODE(this->validate();)
1280
1281 buffer.write32(fPts.count());
1282 buffer.write32(fVerbs.count());
1283 buffer.write32(fFillType);
1284 buffer.writeMul4(fPts.begin(), sizeof(SkPoint) * fPts.count());
1285 buffer.writePad(fVerbs.begin(), fVerbs.count());
1286}
1287
reed@google.com73945652011-04-25 19:04:27 +00001288void SkPath::unflatten(SkReader32& buffer) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001289 fPts.setCount(buffer.readS32());
1290 fVerbs.setCount(buffer.readS32());
1291 fFillType = buffer.readS32();
1292 buffer.read(fPts.begin(), sizeof(SkPoint) * fPts.count());
1293 buffer.read(fVerbs.begin(), fVerbs.count());
reed@google.comabf15c12011-01-18 20:35:51 +00001294
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +00001295 GEN_ID_INC;
reed@android.comd252db02009-04-01 18:31:44 +00001296 fBoundsIsDirty = true;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001297
1298 SkDEBUGCODE(this->validate();)
1299}
1300
1301///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00001302///////////////////////////////////////////////////////////////////////////////
1303
reed@android.com8a1c16f2008-12-17 15:59:43 +00001304void SkPath::dump(bool forceClose, const char title[]) const {
1305 Iter iter(*this, forceClose);
1306 SkPoint pts[4];
1307 Verb verb;
1308
1309 SkDebugf("path: forceClose=%s %s\n", forceClose ? "true" : "false",
1310 title ? title : "");
1311
1312 while ((verb = iter.next(pts)) != kDone_Verb) {
1313 switch (verb) {
1314 case kMove_Verb:
1315#ifdef SK_CAN_USE_FLOAT
1316 SkDebugf(" path: moveTo [%g %g]\n",
1317 SkScalarToFloat(pts[0].fX), SkScalarToFloat(pts[0].fY));
1318#else
1319 SkDebugf(" path: moveTo [%x %x]\n", pts[0].fX, pts[0].fY);
1320#endif
1321 break;
1322 case kLine_Verb:
1323#ifdef SK_CAN_USE_FLOAT
1324 SkDebugf(" path: lineTo [%g %g]\n",
1325 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY));
1326#else
1327 SkDebugf(" path: lineTo [%x %x]\n", pts[1].fX, pts[1].fY);
1328#endif
1329 break;
1330 case kQuad_Verb:
1331#ifdef SK_CAN_USE_FLOAT
1332 SkDebugf(" path: quadTo [%g %g] [%g %g]\n",
1333 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY),
1334 SkScalarToFloat(pts[2].fX), SkScalarToFloat(pts[2].fY));
1335#else
1336 SkDebugf(" path: quadTo [%x %x] [%x %x]\n",
1337 pts[1].fX, pts[1].fY, pts[2].fX, pts[2].fY);
1338#endif
1339 break;
1340 case kCubic_Verb:
1341#ifdef SK_CAN_USE_FLOAT
1342 SkDebugf(" path: cubeTo [%g %g] [%g %g] [%g %g]\n",
1343 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY),
1344 SkScalarToFloat(pts[2].fX), SkScalarToFloat(pts[2].fY),
1345 SkScalarToFloat(pts[3].fX), SkScalarToFloat(pts[3].fY));
1346#else
1347 SkDebugf(" path: cubeTo [%x %x] [%x %x] [%x %x]\n",
1348 pts[1].fX, pts[1].fY, pts[2].fX, pts[2].fY,
1349 pts[3].fX, pts[3].fY);
1350#endif
1351 break;
1352 case kClose_Verb:
1353 SkDebugf(" path: close\n");
1354 break;
1355 default:
1356 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
1357 verb = kDone_Verb; // stop the loop
1358 break;
1359 }
1360 }
1361 SkDebugf("path: done %s\n", title ? title : "");
1362}
1363
reed@android.come522ca52009-11-23 20:10:41 +00001364void SkPath::dump() const {
1365 this->dump(false);
1366}
1367
1368#ifdef SK_DEBUG
1369void SkPath::validate() const {
1370 SkASSERT(this != NULL);
1371 SkASSERT((fFillType & ~3) == 0);
1372 fPts.validate();
1373 fVerbs.validate();
reed@google.comabf15c12011-01-18 20:35:51 +00001374
reed@android.come522ca52009-11-23 20:10:41 +00001375 if (!fBoundsIsDirty) {
1376 SkRect bounds;
1377 compute_pt_bounds(&bounds, fPts);
1378 if (fPts.count() <= 1) {
1379 // if we're empty, fBounds may be empty but translated, so we can't
1380 // necessarily compare to bounds directly
1381 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
1382 // be [2, 2, 2, 2]
1383 SkASSERT(bounds.isEmpty());
1384 SkASSERT(fBounds.isEmpty());
1385 } else {
1386 fBounds.contains(bounds);
1387 }
1388 }
1389}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001390#endif
reed@android.come522ca52009-11-23 20:10:41 +00001391
reed@google.com04863fa2011-05-15 04:08:24 +00001392///////////////////////////////////////////////////////////////////////////////
1393
1394/**
1395 * Returns -1 || 0 || 1 depending on the sign of value:
1396 * -1 if value < 0
1397 * 0 if vlaue == 0
1398 * 1 if value > 0
1399 */
1400static int SkScalarSign(SkScalar value) {
1401 return value < 0 ? -1 : (value > 0);
1402}
1403
1404static int CrossProductSign(const SkVector& a, const SkVector& b) {
1405 return SkScalarSign(SkPoint::CrossProduct(a, b));
1406}
1407
1408// only valid for a single contour
1409struct Convexicator {
1410 Convexicator() : fPtCount(0), fConvexity(SkPath::kUnknown_Convexity) {
1411 fSign = 0;
1412 // warnings
1413 fCurrPt.set(0, 0);
1414 fVec0.set(0, 0);
1415 fVec1.set(0, 0);
1416 fFirstVec.set(0, 0);
1417 }
1418
1419 SkPath::Convexity getConvexity() const { return fConvexity; }
1420
1421 void addPt(const SkPoint& pt) {
1422 if (SkPath::kConcave_Convexity == fConvexity) {
1423 return;
1424 }
1425
1426 if (0 == fPtCount) {
1427 fCurrPt = pt;
1428 ++fPtCount;
1429 } else {
1430 SkVector vec = pt - fCurrPt;
1431 if (vec.fX || vec.fY) {
1432 fCurrPt = pt;
1433 if (++fPtCount == 2) {
1434 fFirstVec = fVec1 = vec;
1435 } else {
1436 SkASSERT(fPtCount > 2);
1437 this->addVec(vec);
1438 }
1439 }
1440 }
1441 }
1442
1443 void close() {
1444 if (fPtCount > 2) {
1445 this->addVec(fFirstVec);
1446 }
1447 }
1448
1449private:
1450 void addVec(const SkVector& vec) {
1451 SkASSERT(vec.fX || vec.fY);
1452 fVec0 = fVec1;
1453 fVec1 = vec;
1454 int sign = CrossProductSign(fVec0, fVec1);
1455 if (0 == fSign) {
1456 fSign = sign;
1457 } else if (sign) {
1458 if (fSign == sign) {
1459 fConvexity = SkPath::kConvex_Convexity;
1460 } else {
1461 fConvexity = SkPath::kConcave_Convexity;
1462 }
1463 }
1464 }
1465
1466 SkPoint fCurrPt;
1467 SkVector fVec0, fVec1, fFirstVec;
1468 int fPtCount; // non-degenerate points
1469 int fSign;
1470 SkPath::Convexity fConvexity;
1471};
1472
1473SkPath::Convexity SkPath::ComputeConvexity(const SkPath& path) {
1474 SkPoint pts[4];
1475 SkPath::Verb verb;
1476 SkPath::Iter iter(path, true);
1477
1478 int contourCount = 0;
1479 int count;
1480 Convexicator state;
1481
1482 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
1483 switch (verb) {
1484 case kMove_Verb:
1485 if (++contourCount > 1) {
1486 return kConcave_Convexity;
1487 }
1488 pts[1] = pts[0];
1489 count = 1;
1490 break;
1491 case kLine_Verb: count = 1; break;
1492 case kQuad_Verb: count = 2; break;
1493 case kCubic_Verb: count = 3; break;
1494 case kClose_Verb:
1495 state.close();
1496 count = 0;
1497 break;
1498 default:
1499 SkASSERT(!"bad verb");
1500 return kConcave_Convexity;
1501 }
1502
1503 for (int i = 1; i <= count; i++) {
1504 state.addPt(pts[i]);
1505 }
1506 // early exit
1507 if (kConcave_Convexity == state.getConvexity()) {
1508 return kConcave_Convexity;
1509 }
1510 }
1511 return state.getConvexity();
1512}
1513