blob: 82eb98068f0189492de82d0b582993fef71bbc6e [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**
5** 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
8**
9** http://www.apache.org/licenses/LICENSE-2.0
10**
11** 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
15** limitations under the License.
16*/
17
18#include "SkPath.h"
19#include "SkFlattenable.h"
20#include "SkMath.h"
21
22////////////////////////////////////////////////////////////////////////////
23
24/* This guy's constructor/destructor bracket a path editing operation. It is
25 used when we know the bounds of the amount we are going to add to the path
26 (usually a new contour, but not required).
27
28 It captures some state about the path up front (i.e. if it already has a
29 cached bounds), and the if it can, it updates the cache bounds explicitly,
30 avoiding the need to revisit all of the points in computeBounds().
31 */
32class SkAutoPathBoundsUpdate {
33public:
34 SkAutoPathBoundsUpdate(SkPath* path, const SkRect& r) : fRect(r) {
35 this->init(path);
36 }
37
38 SkAutoPathBoundsUpdate(SkPath* path, SkScalar left, SkScalar top,
39 SkScalar right, SkScalar bottom) {
40 fRect.set(left, top, right, bottom);
41 this->init(path);
42 }
43
44 ~SkAutoPathBoundsUpdate() {
45 if (fEmpty) {
46 fPath->fFastBounds = fRect;
47 fPath->fFastBoundsIsDirty = false;
48 } else if (!fDirty) {
49 fPath->fFastBounds.join(fRect);
50 fPath->fFastBoundsIsDirty = false;
51 }
52 }
53
54private:
55 const SkPath* fPath;
56 SkRect fRect;
57 bool fDirty;
58 bool fEmpty;
59
60 // returns true if we should proceed
61 void init(const SkPath* path) {
62 fPath = path;
63 fDirty = path->fFastBoundsIsDirty;
64 fEmpty = path->isEmpty();
65 }
66};
67
68static void compute_fast_bounds(SkRect* bounds, const SkTDArray<SkPoint>& pts) {
69 if (pts.count() <= 1) { // we ignore just 1 point (moveto)
70 bounds->set(0, 0, 0, 0);
71 } else {
72 bounds->set(pts.begin(), pts.count());
73// SkDebugf("------- compute bounds %p %d", &pts, pts.count());
74 }
75}
76
77////////////////////////////////////////////////////////////////////////////
78
79/*
80 Stores the verbs and points as they are given to us, with exceptions:
81 - we only record "Close" if it was immediately preceeded by Line | Quad | Cubic
82 - we insert a Move(0,0) if Line | Quad | Cubic is our first command
83
84 The iterator does more cleanup, especially if forceClose == true
85 1. if we encounter Close, return a cons'd up Line() first (if the curr-pt != start-pt)
86 2. if we encounter Move without a preceeding Close, and forceClose is true, goto #1
87 3. if we encounter Line | Quad | Cubic after Close, cons up a Move
88*/
89
90////////////////////////////////////////////////////////////////////////////
91
92SkPath::SkPath() : fFastBoundsIsDirty(true), fFillType(kWinding_FillType) {}
93
94SkPath::SkPath(const SkPath& src) {
95 SkDEBUGCODE(src.validate();)
96 *this = src;
97}
98
99SkPath::~SkPath() {
100 SkDEBUGCODE(this->validate();)
101}
102
103SkPath& SkPath::operator=(const SkPath& src) {
104 SkDEBUGCODE(src.validate();)
105
106 if (this != &src) {
107 fFastBounds = src.fFastBounds;
108 fPts = src.fPts;
109 fVerbs = src.fVerbs;
110 fFillType = src.fFillType;
111 fFastBoundsIsDirty = src.fFastBoundsIsDirty;
112 }
113 SkDEBUGCODE(this->validate();)
114 return *this;
115}
116
117void SkPath::swap(SkPath& other) {
118 SkASSERT(&other != NULL);
119
120 if (this != &other) {
121 SkTSwap<SkRect>(fFastBounds, other.fFastBounds);
122 fPts.swap(other.fPts);
123 fVerbs.swap(other.fVerbs);
124 SkTSwap<uint8_t>(fFillType, other.fFillType);
125 SkTSwap<uint8_t>(fFastBoundsIsDirty, other.fFastBoundsIsDirty);
126 }
127}
128
129void SkPath::reset() {
130 SkDEBUGCODE(this->validate();)
131
132 fPts.reset();
133 fVerbs.reset();
134 fFastBoundsIsDirty = true;
135}
136
137void SkPath::rewind() {
138 SkDEBUGCODE(this->validate();)
139
140 fPts.rewind();
141 fVerbs.rewind();
142 fFastBoundsIsDirty = true;
143}
144
145bool SkPath::isEmpty() const {
146 SkDEBUGCODE(this->validate();)
147
148 int count = fVerbs.count();
149 return count == 0 || (count == 1 && fVerbs[0] == kMove_Verb);
150}
151
152bool SkPath::isRect(SkRect*) const {
153 SkDEBUGCODE(this->validate();)
154
155 SkASSERT(!"unimplemented");
156 return false;
157}
158
159int SkPath::getPoints(SkPoint copy[], int max) const {
160 SkDEBUGCODE(this->validate();)
161
162 SkASSERT(max >= 0);
163 int count = fPts.count();
164 if (copy && max > 0 && count > 0) {
165 memcpy(copy, fPts.begin(), sizeof(SkPoint) * SkMin32(max, count));
166 }
167 return count;
168}
169
170void SkPath::getLastPt(SkPoint* lastPt) const {
171 SkDEBUGCODE(this->validate();)
172
173 if (lastPt) {
174 int count = fPts.count();
175 if (count == 0) {
176 lastPt->set(0, 0);
177 } else {
178 *lastPt = fPts[count - 1];
179 }
180 }
181}
182
183void SkPath::setLastPt(SkScalar x, SkScalar y) {
184 SkDEBUGCODE(this->validate();)
185
186 int count = fPts.count();
187 if (count == 0) {
188 this->moveTo(x, y);
189 } else {
190 fPts[count - 1].set(x, y);
191 }
192}
193
194#define ALWAYS_FAST_BOUNDS_FOR_NOW true
195
196void SkPath::computeBounds(SkRect* bounds, BoundsType bt) const {
197 SkDEBUGCODE(this->validate();)
198
199 SkASSERT(bounds);
200
201 // we BoundsType for now
202
203 if (fFastBoundsIsDirty) {
204 fFastBoundsIsDirty = false;
205 compute_fast_bounds(&fFastBounds, fPts);
206 }
207 *bounds = fFastBounds;
208}
209
210//////////////////////////////////////////////////////////////////////////////
211// Construction methods
212
213void SkPath::incReserve(U16CPU inc) {
214 SkDEBUGCODE(this->validate();)
215
216 fVerbs.setReserve(fVerbs.count() + inc);
217 fPts.setReserve(fPts.count() + inc);
218
219 SkDEBUGCODE(this->validate();)
220}
221
222void SkPath::moveTo(SkScalar x, SkScalar y) {
223 SkDEBUGCODE(this->validate();)
224
225 int vc = fVerbs.count();
226 SkPoint* pt;
227
228 if (vc > 0 && fVerbs[vc - 1] == kMove_Verb) {
229 pt = &fPts[fPts.count() - 1];
230 } else {
231 pt = fPts.append();
232 *fVerbs.append() = kMove_Verb;
233 }
234 pt->set(x, y);
235
236 fFastBoundsIsDirty = true;
237}
238
239void SkPath::rMoveTo(SkScalar x, SkScalar y) {
240 SkPoint pt;
241 this->getLastPt(&pt);
242 this->moveTo(pt.fX + x, pt.fY + y);
243}
244
245void SkPath::lineTo(SkScalar x, SkScalar y) {
246 SkDEBUGCODE(this->validate();)
247
248 if (fVerbs.count() == 0) {
249 fPts.append()->set(0, 0);
250 *fVerbs.append() = kMove_Verb;
251 }
252 fPts.append()->set(x, y);
253 *fVerbs.append() = kLine_Verb;
254
255 fFastBoundsIsDirty = true;
256}
257
258void SkPath::rLineTo(SkScalar x, SkScalar y) {
259 SkPoint pt;
260 this->getLastPt(&pt);
261 this->lineTo(pt.fX + x, pt.fY + y);
262}
263
264void SkPath::quadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
265 SkDEBUGCODE(this->validate();)
266
267 if (fVerbs.count() == 0) {
268 fPts.append()->set(0, 0);
269 *fVerbs.append() = kMove_Verb;
270 }
271
272 SkPoint* pts = fPts.append(2);
273 pts[0].set(x1, y1);
274 pts[1].set(x2, y2);
275 *fVerbs.append() = kQuad_Verb;
276
277 fFastBoundsIsDirty = true;
278}
279
280void SkPath::rQuadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
281 SkPoint pt;
282 this->getLastPt(&pt);
283 this->quadTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2);
284}
285
286void SkPath::cubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
287 SkScalar x3, SkScalar y3) {
288 SkDEBUGCODE(this->validate();)
289
290 if (fVerbs.count() == 0) {
291 fPts.append()->set(0, 0);
292 *fVerbs.append() = kMove_Verb;
293 }
294 SkPoint* pts = fPts.append(3);
295 pts[0].set(x1, y1);
296 pts[1].set(x2, y2);
297 pts[2].set(x3, y3);
298 *fVerbs.append() = kCubic_Verb;
299
300 fFastBoundsIsDirty = true;
301}
302
303void SkPath::rCubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
304 SkScalar x3, SkScalar y3) {
305 SkPoint pt;
306 this->getLastPt(&pt);
307 this->cubicTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2,
308 pt.fX + x3, pt.fY + y3);
309}
310
311void SkPath::close() {
312 SkDEBUGCODE(this->validate();)
313
314 int count = fVerbs.count();
315 if (count > 0) {
316 switch (fVerbs[count - 1]) {
317 case kLine_Verb:
318 case kQuad_Verb:
319 case kCubic_Verb:
320 *fVerbs.append() = kClose_Verb;
321 break;
322 default:
323 // don't add a close if the prev wasn't a primitive
324 break;
325 }
326 }
327}
328
329///////////////////////////////////////////////////////////////////////////////
330
331void SkPath::addRect(const SkRect& rect, Direction dir) {
332 this->addRect(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, dir);
333}
334
335void SkPath::addRect(SkScalar left, SkScalar top, SkScalar right,
336 SkScalar bottom, Direction dir) {
337 SkAutoPathBoundsUpdate apbu(this, left, top, right, bottom);
338
339 this->incReserve(5);
340
341 this->moveTo(left, top);
342 if (dir == kCCW_Direction) {
343 this->lineTo(left, bottom);
344 this->lineTo(right, bottom);
345 this->lineTo(right, top);
346 } else {
347 this->lineTo(right, top);
348 this->lineTo(right, bottom);
349 this->lineTo(left, bottom);
350 }
351 this->close();
352}
353
354#define CUBIC_ARC_FACTOR ((SK_ScalarSqrt2 - SK_Scalar1) * 4 / 3)
355
356void SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
357 Direction dir) {
358 SkAutoPathBoundsUpdate apbu(this, rect);
359
360 SkScalar w = rect.width();
361 SkScalar halfW = SkScalarHalf(w);
362 SkScalar h = rect.height();
363 SkScalar halfH = SkScalarHalf(h);
364
365 if (halfW <= 0 || halfH <= 0) {
366 return;
367 }
368
369 bool skip_hori = rx >= halfW;
370 bool skip_vert = ry >= halfH;
371
372 if (skip_hori && skip_vert) {
373 this->addOval(rect, dir);
374 return;
375 }
376 if (skip_hori) {
377 rx = halfW;
378 } else if (skip_vert) {
379 ry = halfH;
380 }
381
382 SkScalar sx = SkScalarMul(rx, CUBIC_ARC_FACTOR);
383 SkScalar sy = SkScalarMul(ry, CUBIC_ARC_FACTOR);
384
385 this->incReserve(17);
386 this->moveTo(rect.fRight - rx, rect.fTop);
387 if (dir == kCCW_Direction) {
388 if (!skip_hori) {
389 this->lineTo(rect.fLeft + rx, rect.fTop); // top
390 }
391 this->cubicTo(rect.fLeft + rx - sx, rect.fTop,
392 rect.fLeft, rect.fTop + ry - sy,
393 rect.fLeft, rect.fTop + ry); // top-left
394 if (!skip_vert) {
395 this->lineTo(rect.fLeft, rect.fBottom - ry); // left
396 }
397 this->cubicTo(rect.fLeft, rect.fBottom - ry + sy,
398 rect.fLeft + rx - sx, rect.fBottom,
399 rect.fLeft + rx, rect.fBottom); // bot-left
400 if (!skip_hori) {
401 this->lineTo(rect.fRight - rx, rect.fBottom); // bottom
402 }
403 this->cubicTo(rect.fRight - rx + sx, rect.fBottom,
404 rect.fRight, rect.fBottom - ry + sy,
405 rect.fRight, rect.fBottom - ry); // bot-right
406 if (!skip_vert) {
407 this->lineTo(rect.fRight, rect.fTop + ry);
408 }
409 this->cubicTo(rect.fRight, rect.fTop + ry - sy,
410 rect.fRight - rx + sx, rect.fTop,
411 rect.fRight - rx, rect.fTop); // top-right
412 } else {
413 this->cubicTo(rect.fRight - rx + sx, rect.fTop,
414 rect.fRight, rect.fTop + ry - sy,
415 rect.fRight, rect.fTop + ry); // top-right
416 if (!skip_vert) {
417 this->lineTo(rect.fRight, rect.fBottom - ry);
418 }
419 this->cubicTo(rect.fRight, rect.fBottom - ry + sy,
420 rect.fRight - rx + sx, rect.fBottom,
421 rect.fRight - rx, rect.fBottom); // bot-right
422 if (!skip_hori) {
423 this->lineTo(rect.fLeft + rx, rect.fBottom); // bottom
424 }
425 this->cubicTo(rect.fLeft + rx - sx, rect.fBottom,
426 rect.fLeft, rect.fBottom - ry + sy,
427 rect.fLeft, rect.fBottom - ry); // bot-left
428 if (!skip_vert) {
429 this->lineTo(rect.fLeft, rect.fTop + ry); // left
430 }
431 this->cubicTo(rect.fLeft, rect.fTop + ry - sy,
432 rect.fLeft + rx - sx, rect.fTop,
433 rect.fLeft + rx, rect.fTop); // top-left
434 if (!skip_hori) {
435 this->lineTo(rect.fRight - rx, rect.fTop); // top
436 }
437 }
438 this->close();
439}
440
441static void add_corner_arc(SkPath* path, const SkRect& rect,
442 SkScalar rx, SkScalar ry, int startAngle,
443 SkPath::Direction dir, bool forceMoveTo) {
444 rx = SkMinScalar(SkScalarHalf(rect.width()), rx);
445 ry = SkMinScalar(SkScalarHalf(rect.height()), ry);
446
447 SkRect r;
448 r.set(-rx, -ry, rx, ry);
449
450 switch (startAngle) {
451 case 0:
452 r.offset(rect.fRight - r.fRight, rect.fBottom - r.fBottom);
453 break;
454 case 90:
455 r.offset(rect.fLeft - r.fLeft, rect.fBottom - r.fBottom);
456 break;
457 case 180: r.offset(rect.fLeft - r.fLeft, rect.fTop - r.fTop); break;
458 case 270: r.offset(rect.fRight - r.fRight, rect.fTop - r.fTop); break;
459 default: SkASSERT(!"unexpected startAngle in add_corner_arc");
460 }
461
462 SkScalar start = SkIntToScalar(startAngle);
463 SkScalar sweep = SkIntToScalar(90);
464 if (SkPath::kCCW_Direction == dir) {
465 start += sweep;
466 sweep = -sweep;
467 }
468
469 path->arcTo(r, start, sweep, forceMoveTo);
470}
471
472void SkPath::addRoundRect(const SkRect& rect, const SkScalar rad[],
473 Direction dir) {
474 SkAutoPathBoundsUpdate apbu(this, rect);
475
476 if (kCW_Direction == dir) {
477 add_corner_arc(this, rect, rad[0], rad[1], 180, dir, true);
478 add_corner_arc(this, rect, rad[2], rad[3], 270, dir, false);
479 add_corner_arc(this, rect, rad[4], rad[5], 0, dir, false);
480 add_corner_arc(this, rect, rad[6], rad[7], 90, dir, false);
481 } else {
482 add_corner_arc(this, rect, rad[0], rad[1], 180, dir, true);
483 add_corner_arc(this, rect, rad[6], rad[7], 90, dir, false);
484 add_corner_arc(this, rect, rad[4], rad[5], 0, dir, false);
485 add_corner_arc(this, rect, rad[2], rad[3], 270, dir, false);
486 }
487 this->close();
488}
489
490void SkPath::addOval(const SkRect& oval, Direction dir) {
491 SkAutoPathBoundsUpdate apbu(this, oval);
492
493 SkScalar cx = oval.centerX();
494 SkScalar cy = oval.centerY();
495 SkScalar rx = SkScalarHalf(oval.width());
496 SkScalar ry = SkScalarHalf(oval.height());
497#if 0 // these seem faster than using quads (1/2 the number of edges)
498 SkScalar sx = SkScalarMul(rx, CUBIC_ARC_FACTOR);
499 SkScalar sy = SkScalarMul(ry, CUBIC_ARC_FACTOR);
500
501 this->incReserve(13);
502 this->moveTo(cx + rx, cy);
503 if (dir == kCCW_Direction) {
504 this->cubicTo(cx + rx, cy - sy, cx + sx, cy - ry, cx, cy - ry);
505 this->cubicTo(cx - sx, cy - ry, cx - rx, cy - sy, cx - rx, cy);
506 this->cubicTo(cx - rx, cy + sy, cx - sx, cy + ry, cx, cy + ry);
507 this->cubicTo(cx + sx, cy + ry, cx + rx, cy + sy, cx + rx, cy);
508 } else {
509 this->cubicTo(cx + rx, cy + sy, cx + sx, cy + ry, cx, cy + ry);
510 this->cubicTo(cx - sx, cy + ry, cx - rx, cy + sy, cx - rx, cy);
511 this->cubicTo(cx - rx, cy - sy, cx - sx, cy - ry, cx, cy - ry);
512 this->cubicTo(cx + sx, cy - ry, cx + rx, cy - sy, cx + rx, cy);
513 }
514#else
515 SkScalar sx = SkScalarMul(rx, SK_ScalarTanPIOver8);
516 SkScalar sy = SkScalarMul(ry, SK_ScalarTanPIOver8);
517 SkScalar mx = SkScalarMul(rx, SK_ScalarRoot2Over2);
518 SkScalar my = SkScalarMul(ry, SK_ScalarRoot2Over2);
519
520 /*
521 To handle imprecision in computing the center and radii, we revert to
522 the provided bounds when we can (i.e. use oval.fLeft instead of cx-rx)
523 to ensure that we don't exceed the oval's bounds *ever*, since we want
524 to use oval for our fast-bounds, rather than have to recompute it.
525 */
526 const SkScalar L = oval.fLeft; // cx - rx
527 const SkScalar T = oval.fTop; // cy - ry
528 const SkScalar R = oval.fRight; // cx + rx
529 const SkScalar B = oval.fBottom; // cy + ry
530
531 this->incReserve(17); // 8 quads + close
532 this->moveTo(R, cy);
533 if (dir == kCCW_Direction) {
534 this->quadTo( R, cy - sy, cx + mx, cy - my);
535 this->quadTo(cx + sx, T, cx , T);
536 this->quadTo(cx - sx, T, cx - mx, cy - my);
537 this->quadTo( L, cy - sy, L, cy );
538 this->quadTo( L, cy + sy, cx - mx, cy + my);
539 this->quadTo(cx - sx, B, cx , B);
540 this->quadTo(cx + sx, B, cx + mx, cy + my);
541 this->quadTo( R, cy + sy, R, cy );
542 } else {
543 this->quadTo( R, cy + sy, cx + mx, cy + my);
544 this->quadTo(cx + sx, B, cx , B);
545 this->quadTo(cx - sx, B, cx - mx, cy + my);
546 this->quadTo( L, cy + sy, L, cy );
547 this->quadTo( L, cy - sy, cx - mx, cy - my);
548 this->quadTo(cx - sx, T, cx , T);
549 this->quadTo(cx + sx, T, cx + mx, cy - my);
550 this->quadTo( R, cy - sy, R, cy );
551 }
552#endif
553 this->close();
554}
555
556void SkPath::addCircle(SkScalar x, SkScalar y, SkScalar r, Direction dir) {
557 if (r > 0) {
558 SkRect rect;
559 rect.set(x - r, y - r, x + r, y + r);
560 this->addOval(rect, dir);
561 }
562}
563
564#include "SkGeometry.h"
565
566static int build_arc_points(const SkRect& oval, SkScalar startAngle,
567 SkScalar sweepAngle,
568 SkPoint pts[kSkBuildQuadArcStorage]) {
569 SkVector start, stop;
570
571 start.fY = SkScalarSinCos(SkDegreesToRadians(startAngle), &start.fX);
572 stop.fY = SkScalarSinCos(SkDegreesToRadians(startAngle + sweepAngle),
573 &stop.fX);
574
575 SkMatrix matrix;
576
577 matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height()));
578 matrix.postTranslate(oval.centerX(), oval.centerY());
579
580 return SkBuildQuadArc(start, stop,
581 sweepAngle > 0 ? kCW_SkRotationDirection : kCCW_SkRotationDirection,
582 &matrix, pts);
583}
584
585void SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
586 bool forceMoveTo) {
587 if (oval.width() < 0 || oval.height() < 0) {
588 return;
589 }
590
591 SkPoint pts[kSkBuildQuadArcStorage];
592 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
593 SkASSERT((count & 1) == 1);
594
595 if (fVerbs.count() == 0) {
596 forceMoveTo = true;
597 }
598 this->incReserve(count);
599 forceMoveTo ? this->moveTo(pts[0]) : this->lineTo(pts[0]);
600 for (int i = 1; i < count; i += 2) {
601 this->quadTo(pts[i], pts[i+1]);
602 }
603}
604
605void SkPath::addArc(const SkRect& oval, SkScalar startAngle,
606 SkScalar sweepAngle) {
607 if (oval.isEmpty() || 0 == sweepAngle) {
608 return;
609 }
610
611 const SkScalar kFullCircleAngle = SkIntToScalar(360);
612
613 if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
614 this->addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction);
615 return;
616 }
617
618 SkPoint pts[kSkBuildQuadArcStorage];
619 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
620
621 this->incReserve(count);
622 this->moveTo(pts[0]);
623 for (int i = 1; i < count; i += 2) {
624 this->quadTo(pts[i], pts[i+1]);
625 }
626}
627
628/*
629 Need to handle the case when the angle is sharp, and our computed end-points
630 for the arc go behind pt1 and/or p2...
631*/
632void SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
633 SkScalar radius) {
634 SkVector before, after;
635
636 // need to know our prev pt so we can construct tangent vectors
637 {
638 SkPoint start;
639 this->getLastPt(&start);
640 before.setNormalize(x1 - start.fX, y1 - start.fY);
641 after.setNormalize(x2 - x1, y2 - y1);
642 }
643
644 SkScalar cosh = SkPoint::DotProduct(before, after);
645 SkScalar sinh = SkPoint::CrossProduct(before, after);
646
647 if (SkScalarNearlyZero(sinh)) { // angle is too tight
648 return;
649 }
650
651 SkScalar dist = SkScalarMulDiv(radius, SK_Scalar1 - cosh, sinh);
652 if (dist < 0) {
653 dist = -dist;
654 }
655
656 SkScalar xx = x1 - SkScalarMul(dist, before.fX);
657 SkScalar yy = y1 - SkScalarMul(dist, before.fY);
658 SkRotationDirection arcDir;
659
660 // now turn before/after into normals
661 if (sinh > 0) {
662 before.rotateCCW();
663 after.rotateCCW();
664 arcDir = kCW_SkRotationDirection;
665 } else {
666 before.rotateCW();
667 after.rotateCW();
668 arcDir = kCCW_SkRotationDirection;
669 }
670
671 SkMatrix matrix;
672 SkPoint pts[kSkBuildQuadArcStorage];
673
674 matrix.setScale(radius, radius);
675 matrix.postTranslate(xx - SkScalarMul(radius, before.fX),
676 yy - SkScalarMul(radius, before.fY));
677
678 int count = SkBuildQuadArc(before, after, arcDir, &matrix, pts);
679
680 this->incReserve(count);
681 // [xx,yy] == pts[0]
682 this->lineTo(xx, yy);
683 for (int i = 1; i < count; i += 2) {
684 this->quadTo(pts[i], pts[i+1]);
685 }
686}
687
688///////////////////////////////////////////////////////////////////////////////
689
690void SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy) {
691 SkMatrix matrix;
692
693 matrix.setTranslate(dx, dy);
694 this->addPath(path, matrix);
695}
696
697void SkPath::addPath(const SkPath& path, const SkMatrix& matrix) {
698 this->incReserve(path.fPts.count());
699
700 Iter iter(path, false);
701 SkPoint pts[4];
702 Verb verb;
703
704 SkMatrix::MapPtsProc proc = matrix.getMapPtsProc();
705
706 while ((verb = iter.next(pts)) != kDone_Verb) {
707 switch (verb) {
708 case kMove_Verb:
709 proc(matrix, &pts[0], &pts[0], 1);
710 this->moveTo(pts[0]);
711 break;
712 case kLine_Verb:
713 proc(matrix, &pts[1], &pts[1], 1);
714 this->lineTo(pts[1]);
715 break;
716 case kQuad_Verb:
717 proc(matrix, &pts[1], &pts[1], 2);
718 this->quadTo(pts[1], pts[2]);
719 break;
720 case kCubic_Verb:
721 proc(matrix, &pts[1], &pts[1], 3);
722 this->cubicTo(pts[1], pts[2], pts[3]);
723 break;
724 case kClose_Verb:
725 this->close();
726 break;
727 default:
728 SkASSERT(!"unknown verb");
729 }
730 }
731}
732
733///////////////////////////////////////////////////////////////////////////////
734
735static const uint8_t gPtsInVerb[] = {
736 1, // kMove
737 1, // kLine
738 2, // kQuad
739 3, // kCubic
740 0, // kClose
741 0 // kDone
742};
743
744// ignore the initial moveto, and stop when the 1st contour ends
745void SkPath::pathTo(const SkPath& path) {
746 int i, vcount = path.fVerbs.count();
747 if (vcount == 0) {
748 return;
749 }
750
751 this->incReserve(vcount);
752
753 const uint8_t* verbs = path.fVerbs.begin();
754 const SkPoint* pts = path.fPts.begin() + 1; // 1 for the initial moveTo
755
756 SkASSERT(verbs[0] == kMove_Verb);
757 for (i = 1; i < vcount; i++) {
758 switch (verbs[i]) {
759 case kLine_Verb:
760 this->lineTo(pts[0].fX, pts[0].fY);
761 break;
762 case kQuad_Verb:
763 this->quadTo(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY);
764 break;
765 case kCubic_Verb:
766 this->cubicTo(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY,
767 pts[2].fX, pts[2].fY);
768 break;
769 case kClose_Verb:
770 return;
771 }
772 pts += gPtsInVerb[verbs[i]];
773 }
774}
775
776// ignore the last point of the 1st contour
777void SkPath::reversePathTo(const SkPath& path) {
778 int i, vcount = path.fVerbs.count();
779 if (vcount == 0) {
780 return;
781 }
782
783 this->incReserve(vcount);
784
785 const uint8_t* verbs = path.fVerbs.begin();
786 const SkPoint* pts = path.fPts.begin();
787
788 SkASSERT(verbs[0] == kMove_Verb);
789 for (i = 1; i < vcount; i++) {
790 int n = gPtsInVerb[verbs[i]];
791 if (n == 0) {
792 break;
793 }
794 pts += n;
795 }
796
797 while (--i > 0) {
798 switch (verbs[i]) {
799 case kLine_Verb:
800 this->lineTo(pts[-1].fX, pts[-1].fY);
801 break;
802 case kQuad_Verb:
803 this->quadTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY);
804 break;
805 case kCubic_Verb:
806 this->cubicTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY,
807 pts[-3].fX, pts[-3].fY);
808 break;
809 default:
810 SkASSERT(!"bad verb");
811 break;
812 }
813 pts -= gPtsInVerb[verbs[i]];
814 }
815}
816
817///////////////////////////////////////////////////////////////////////////////
818
819void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
820 SkMatrix matrix;
821
822 matrix.setTranslate(dx, dy);
823 this->transform(matrix, dst);
824}
825
826#include "SkGeometry.h"
827
828static void subdivide_quad_to(SkPath* path, const SkPoint pts[3],
829 int level = 2) {
830 if (--level >= 0) {
831 SkPoint tmp[5];
832
833 SkChopQuadAtHalf(pts, tmp);
834 subdivide_quad_to(path, &tmp[0], level);
835 subdivide_quad_to(path, &tmp[2], level);
836 } else {
837 path->quadTo(pts[1], pts[2]);
838 }
839}
840
841static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
842 int level = 2) {
843 if (--level >= 0) {
844 SkPoint tmp[7];
845
846 SkChopCubicAtHalf(pts, tmp);
847 subdivide_cubic_to(path, &tmp[0], level);
848 subdivide_cubic_to(path, &tmp[3], level);
849 } else {
850 path->cubicTo(pts[1], pts[2], pts[3]);
851 }
852}
853
854void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
855 SkDEBUGCODE(this->validate();)
856 if (dst == NULL) {
857 dst = (SkPath*)this;
858 }
859
860 if (matrix.getType() & SkMatrix::kPerspective_Mask) {
861 SkPath tmp;
862 tmp.fFillType = fFillType;
863
864 SkPath::Iter iter(*this, false);
865 SkPoint pts[4];
866 SkPath::Verb verb;
867
868 while ((verb = iter.next(pts)) != kDone_Verb) {
869 switch (verb) {
870 case kMove_Verb:
871 tmp.moveTo(pts[0]);
872 break;
873 case kLine_Verb:
874 tmp.lineTo(pts[1]);
875 break;
876 case kQuad_Verb:
877 subdivide_quad_to(&tmp, pts);
878 break;
879 case kCubic_Verb:
880 subdivide_cubic_to(&tmp, pts);
881 break;
882 case kClose_Verb:
883 tmp.close();
884 break;
885 default:
886 SkASSERT(!"unknown verb");
887 break;
888 }
889 }
890
891 dst->swap(tmp);
892 matrix.mapPoints(dst->fPts.begin(), dst->fPts.count());
893 } else {
894 // remember that dst might == this, so be sure to check
895 // fFastBoundsIsDirty before we set it
896 if (!fFastBoundsIsDirty && matrix.rectStaysRect() && fPts.count() > 1) {
897 // if we're empty, fastbounds should not be mapped
898 matrix.mapRect(&dst->fFastBounds, fFastBounds);
899 dst->fFastBoundsIsDirty = false;
900 } else {
901 dst->fFastBoundsIsDirty = true;
902 }
903
904 if (this != dst) {
905 dst->fVerbs = fVerbs;
906 dst->fPts.setCount(fPts.count());
907 dst->fFillType = fFillType;
908 }
909 matrix.mapPoints(dst->fPts.begin(), fPts.begin(), fPts.count());
910 SkDEBUGCODE(dst->validate();)
911 }
912}
913
914void SkPath::updateBoundsCache() const {
915 if (fFastBoundsIsDirty) {
916 SkRect r;
917 this->computeBounds(&r, kFast_BoundsType);
918 SkASSERT(!fFastBoundsIsDirty);
919 }
920}
921
922///////////////////////////////////////////////////////////////////////////////
923///////////////////////////////////////////////////////////////////////////////
924
925enum NeedMoveToState {
926 kAfterClose_NeedMoveToState,
927 kAfterCons_NeedMoveToState,
928 kAfterPrefix_NeedMoveToState
929};
930
931SkPath::Iter::Iter() {
932#ifdef SK_DEBUG
933 fPts = NULL;
934 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
935 fForceClose = fNeedMoveTo = fCloseLine = false;
936#endif
937 // need to init enough to make next() harmlessly return kDone_Verb
938 fVerbs = NULL;
939 fVerbStop = NULL;
940 fNeedClose = false;
941}
942
943SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
944 this->setPath(path, forceClose);
945}
946
947void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
948 fPts = path.fPts.begin();
949 fVerbs = path.fVerbs.begin();
950 fVerbStop = path.fVerbs.end();
951 fForceClose = SkToU8(forceClose);
952 fNeedClose = false;
953 fNeedMoveTo = kAfterPrefix_NeedMoveToState;
954}
955
956bool SkPath::Iter::isClosedContour() const {
957 if (fVerbs == NULL || fVerbs == fVerbStop) {
958 return false;
959 }
960 if (fForceClose) {
961 return true;
962 }
963
964 const uint8_t* verbs = fVerbs;
965 const uint8_t* stop = fVerbStop;
966
967 if (kMove_Verb == *verbs) {
968 verbs += 1; // skip the initial moveto
969 }
970
971 while (verbs < stop) {
972 unsigned v = *verbs++;
973 if (kMove_Verb == v) {
974 break;
975 }
976 if (kClose_Verb == v) {
977 return true;
978 }
979 }
980 return false;
981}
982
983SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
984 if (fLastPt != fMoveTo) {
985 if (pts) {
986 pts[0] = fLastPt;
987 pts[1] = fMoveTo;
988 }
989 fLastPt = fMoveTo;
990 fCloseLine = true;
991 return kLine_Verb;
992 }
993 return kClose_Verb;
994}
995
996bool SkPath::Iter::cons_moveTo(SkPoint pts[1]) {
997 if (fNeedMoveTo == kAfterClose_NeedMoveToState) {
998 if (pts) {
999 *pts = fMoveTo;
1000 }
1001 fNeedClose = fForceClose;
1002 fNeedMoveTo = kAfterCons_NeedMoveToState;
1003 fVerbs -= 1;
1004 return true;
1005 }
1006
1007 if (fNeedMoveTo == kAfterCons_NeedMoveToState) {
1008 if (pts) {
1009 *pts = fMoveTo;
1010 }
1011 fNeedMoveTo = kAfterPrefix_NeedMoveToState;
1012 } else {
1013 SkASSERT(fNeedMoveTo == kAfterPrefix_NeedMoveToState);
1014 if (pts) {
1015 *pts = fPts[-1];
1016 }
1017 }
1018 return false;
1019}
1020
1021SkPath::Verb SkPath::Iter::next(SkPoint pts[4]) {
1022 if (fVerbs == fVerbStop) {
1023 if (fNeedClose) {
1024 if (kLine_Verb == this->autoClose(pts)) {
1025 return kLine_Verb;
1026 }
1027 fNeedClose = false;
1028 return kClose_Verb;
1029 }
1030 return kDone_Verb;
1031 }
1032
1033 unsigned verb = *fVerbs++;
1034 const SkPoint* srcPts = fPts;
1035
1036 switch (verb) {
1037 case kMove_Verb:
1038 if (fNeedClose) {
1039 fVerbs -= 1;
1040 verb = this->autoClose(pts);
1041 if (verb == kClose_Verb) {
1042 fNeedClose = false;
1043 }
1044 return (Verb)verb;
1045 }
1046 if (fVerbs == fVerbStop) { // might be a trailing moveto
1047 return kDone_Verb;
1048 }
1049 fMoveTo = *srcPts;
1050 if (pts) {
1051 pts[0] = *srcPts;
1052 }
1053 srcPts += 1;
1054 fNeedMoveTo = kAfterCons_NeedMoveToState;
1055 fNeedClose = fForceClose;
1056 break;
1057 case kLine_Verb:
1058 if (this->cons_moveTo(pts)) {
1059 return kMove_Verb;
1060 }
1061 if (pts) {
1062 pts[1] = srcPts[0];
1063 }
1064 fLastPt = srcPts[0];
1065 fCloseLine = false;
1066 srcPts += 1;
1067 break;
1068 case kQuad_Verb:
1069 if (this->cons_moveTo(pts)) {
1070 return kMove_Verb;
1071 }
1072 if (pts) {
1073 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
1074 }
1075 fLastPt = srcPts[1];
1076 srcPts += 2;
1077 break;
1078 case kCubic_Verb:
1079 if (this->cons_moveTo(pts)) {
1080 return kMove_Verb;
1081 }
1082 if (pts) {
1083 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
1084 }
1085 fLastPt = srcPts[2];
1086 srcPts += 3;
1087 break;
1088 case kClose_Verb:
1089 verb = this->autoClose(pts);
1090 if (verb == kLine_Verb) {
1091 fVerbs -= 1;
1092 } else {
1093 fNeedClose = false;
1094 }
1095 fNeedMoveTo = kAfterClose_NeedMoveToState;
1096 break;
1097 }
1098 fPts = srcPts;
1099 return (Verb)verb;
1100}
1101
1102///////////////////////////////////////////////////////////////////////////////
1103
1104static bool exceeds_dist(const SkScalar p[], const SkScalar q[], SkScalar dist,
1105 int count) {
1106 SkASSERT(dist > 0);
1107
1108 count *= 2;
1109 for (int i = 0; i < count; i++) {
1110 if (SkScalarAbs(p[i] - q[i]) > dist) {
1111 return true;
1112 }
1113 }
1114 return false;
1115}
1116
1117static void subdivide_quad(SkPath* dst, const SkPoint pts[3], SkScalar dist,
1118 int subLevel = 4) {
1119 if (--subLevel >= 0 && exceeds_dist(&pts[0].fX, &pts[1].fX, dist, 4)) {
1120 SkPoint tmp[5];
1121 SkChopQuadAtHalf(pts, tmp);
1122
1123 subdivide_quad(dst, &tmp[0], dist, subLevel);
1124 subdivide_quad(dst, &tmp[2], dist, subLevel);
1125 } else {
1126 dst->quadTo(pts[1], pts[2]);
1127 }
1128}
1129
1130static void subdivide_cubic(SkPath* dst, const SkPoint pts[4], SkScalar dist,
1131 int subLevel = 4) {
1132 if (--subLevel >= 0 && exceeds_dist(&pts[0].fX, &pts[1].fX, dist, 6)) {
1133 SkPoint tmp[7];
1134 SkChopCubicAtHalf(pts, tmp);
1135
1136 subdivide_cubic(dst, &tmp[0], dist, subLevel);
1137 subdivide_cubic(dst, &tmp[3], dist, subLevel);
1138 } else {
1139 dst->cubicTo(pts[1], pts[2], pts[3]);
1140 }
1141}
1142
1143void SkPath::subdivide(SkScalar dist, bool bendLines, SkPath* dst) const {
1144 SkPath tmpPath;
1145 if (NULL == dst || this == dst) {
1146 dst = &tmpPath;
1147 }
1148
1149 SkPath::Iter iter(*this, false);
1150 SkPoint pts[4];
1151
1152 for (;;) {
1153 switch (iter.next(pts)) {
1154 case SkPath::kMove_Verb:
1155 dst->moveTo(pts[0]);
1156 break;
1157 case SkPath::kLine_Verb:
1158 if (!bendLines) {
1159 dst->lineTo(pts[1]);
1160 break;
1161 }
1162 // construct a quad from the line
1163 pts[2] = pts[1];
1164 pts[1].set(SkScalarAve(pts[0].fX, pts[2].fX),
1165 SkScalarAve(pts[0].fY, pts[2].fY));
1166 // fall through to the quad case
1167 case SkPath::kQuad_Verb:
1168 subdivide_quad(dst, pts, dist);
1169 break;
1170 case SkPath::kCubic_Verb:
1171 subdivide_cubic(dst, pts, dist);
1172 break;
1173 case SkPath::kClose_Verb:
1174 dst->close();
1175 break;
1176 case SkPath::kDone_Verb:
1177 goto DONE;
1178 }
1179 }
1180DONE:
1181 if (&tmpPath == dst) { // i.e. the dst should be us
1182 dst->swap(*(SkPath*)this);
1183 }
1184}
1185
1186///////////////////////////////////////////////////////////////////////
1187/*
1188 Format in flattened buffer: [ptCount, verbCount, pts[], verbs[]]
1189*/
1190
1191void SkPath::flatten(SkFlattenableWriteBuffer& buffer) const {
1192 SkDEBUGCODE(this->validate();)
1193
1194 buffer.write32(fPts.count());
1195 buffer.write32(fVerbs.count());
1196 buffer.write32(fFillType);
1197 buffer.writeMul4(fPts.begin(), sizeof(SkPoint) * fPts.count());
1198 buffer.writePad(fVerbs.begin(), fVerbs.count());
1199}
1200
1201void SkPath::unflatten(SkFlattenableReadBuffer& buffer) {
1202 fPts.setCount(buffer.readS32());
1203 fVerbs.setCount(buffer.readS32());
1204 fFillType = buffer.readS32();
1205 buffer.read(fPts.begin(), sizeof(SkPoint) * fPts.count());
1206 buffer.read(fVerbs.begin(), fVerbs.count());
1207
1208 fFastBoundsIsDirty = true;
1209
1210 SkDEBUGCODE(this->validate();)
1211}
1212
1213///////////////////////////////////////////////////////////////////////////////
1214
1215#include "SkString.h"
1216#include "SkStream.h"
1217
1218static void write_scalar(SkWStream* stream, SkScalar value) {
1219 char buffer[SkStrAppendScalar_MaxSize];
1220 char* stop = SkStrAppendScalar(buffer, value);
1221 stream->write(buffer, stop - buffer);
1222}
1223
1224static void append_scalars(SkWStream* stream, char verb, const SkScalar data[],
1225 int count) {
1226 stream->write(&verb, 1);
1227 write_scalar(stream, data[0]);
1228 for (int i = 1; i < count; i++) {
1229 if (data[i] >= 0) {
1230 // can skip the separater if data[i] is negative
1231 stream->write(" ", 1);
1232 }
1233 write_scalar(stream, data[i]);
1234 }
1235}
1236
1237void SkPath::toString(SkString* str) const {
1238 SkDynamicMemoryWStream stream;
1239
1240 SkPath::Iter iter(*this, false);
1241 SkPoint pts[4];
1242
1243 for (;;) {
1244 switch (iter.next(pts)) {
1245 case SkPath::kMove_Verb:
1246 append_scalars(&stream, 'M', &pts[0].fX, 2);
1247 break;
1248 case SkPath::kLine_Verb:
1249 append_scalars(&stream, 'L', &pts[1].fX, 2);
1250 break;
1251 case SkPath::kQuad_Verb:
1252 append_scalars(&stream, 'Q', &pts[1].fX, 4);
1253 break;
1254 case SkPath::kCubic_Verb:
1255 append_scalars(&stream, 'C', &pts[1].fX, 6);
1256 break;
1257 case SkPath::kClose_Verb:
1258 stream.write("Z", 1);
1259 break;
1260 case SkPath::kDone_Verb:
1261 str->resize(stream.getOffset());
1262 stream.copyTo(str->writable_str());
1263 return;
1264 }
1265 }
1266}
1267
1268///////////////////////////////////////////////////////////////////////////////
1269///////////////////////////////////////////////////////////////////////////////
1270
1271#ifdef SK_DEBUG
1272
1273void SkPath::validate() const {
1274 SkASSERT(this != NULL);
1275 SkASSERT((fFillType & ~3) == 0);
1276 fPts.validate();
1277 fVerbs.validate();
1278
1279 if (!fFastBoundsIsDirty) {
1280 SkRect bounds;
1281 compute_fast_bounds(&bounds, fPts);
1282 // can't call contains(), since it returns false if the rect is empty
1283 SkASSERT(fFastBounds.fLeft <= bounds.fLeft);
1284 SkASSERT(fFastBounds.fTop <= bounds.fTop);
1285 SkASSERT(fFastBounds.fRight >= bounds.fRight);
1286 SkASSERT(fFastBounds.fBottom >= bounds.fBottom);
1287 }
1288}
1289
1290#if 0 // test to ensure that the iterator returns the same data as the path
1291void SkPath::test() const
1292{
1293 Iter iter(*this, false);
1294 SkPoint pts[4];
1295 Verb verb;
1296
1297 const uint8_t* verbs = fVerbs.begin();
1298 const SkPoint* points = fPts.begin();
1299
1300 while ((verb = iter.next(pts)) != kDone_Verb)
1301 {
1302 SkASSERT(*verbs == verb);
1303 verbs += 1;
1304
1305 int count;
1306 switch (verb) {
1307 case kMove_Verb:
1308 count = 1;
1309 break;
1310 case kLine_Verb:
1311 count = 2;
1312 break;
1313 case kQuad_Verb:
1314 count = 3;
1315 break;
1316 case kCubic_Verb:
1317 count = 4;
1318 break;
1319 case kClose_Verb:
1320 default:
1321 count = 0;
1322 break;
1323 }
1324 if (count > 1)
1325 points -= 1;
1326 SkASSERT(memcmp(pts, points, count * sizeof(SkPoint)) == 0);
1327 points += count;
1328 }
1329
1330 int vc = fVerbs.count(), pc = fPts.count();
1331 if (vc && fVerbs.begin()[vc-1] == kMove_Verb)
1332 {
1333 vc -= 1;
1334 pc -= 1;
1335 }
1336 SkASSERT(verbs - fVerbs.begin() == vc);
1337 SkASSERT(points - fPts.begin() == pc);
1338}
1339#endif
1340
1341void SkPath::dump(bool forceClose, const char title[]) const {
1342 Iter iter(*this, forceClose);
1343 SkPoint pts[4];
1344 Verb verb;
1345
1346 SkDebugf("path: forceClose=%s %s\n", forceClose ? "true" : "false",
1347 title ? title : "");
1348
1349 while ((verb = iter.next(pts)) != kDone_Verb) {
1350 switch (verb) {
1351 case kMove_Verb:
1352#ifdef SK_CAN_USE_FLOAT
1353 SkDebugf(" path: moveTo [%g %g]\n",
1354 SkScalarToFloat(pts[0].fX), SkScalarToFloat(pts[0].fY));
1355#else
1356 SkDebugf(" path: moveTo [%x %x]\n", pts[0].fX, pts[0].fY);
1357#endif
1358 break;
1359 case kLine_Verb:
1360#ifdef SK_CAN_USE_FLOAT
1361 SkDebugf(" path: lineTo [%g %g]\n",
1362 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY));
1363#else
1364 SkDebugf(" path: lineTo [%x %x]\n", pts[1].fX, pts[1].fY);
1365#endif
1366 break;
1367 case kQuad_Verb:
1368#ifdef SK_CAN_USE_FLOAT
1369 SkDebugf(" path: quadTo [%g %g] [%g %g]\n",
1370 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY),
1371 SkScalarToFloat(pts[2].fX), SkScalarToFloat(pts[2].fY));
1372#else
1373 SkDebugf(" path: quadTo [%x %x] [%x %x]\n",
1374 pts[1].fX, pts[1].fY, pts[2].fX, pts[2].fY);
1375#endif
1376 break;
1377 case kCubic_Verb:
1378#ifdef SK_CAN_USE_FLOAT
1379 SkDebugf(" path: cubeTo [%g %g] [%g %g] [%g %g]\n",
1380 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY),
1381 SkScalarToFloat(pts[2].fX), SkScalarToFloat(pts[2].fY),
1382 SkScalarToFloat(pts[3].fX), SkScalarToFloat(pts[3].fY));
1383#else
1384 SkDebugf(" path: cubeTo [%x %x] [%x %x] [%x %x]\n",
1385 pts[1].fX, pts[1].fY, pts[2].fX, pts[2].fY,
1386 pts[3].fX, pts[3].fY);
1387#endif
1388 break;
1389 case kClose_Verb:
1390 SkDebugf(" path: close\n");
1391 break;
1392 default:
1393 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
1394 verb = kDone_Verb; // stop the loop
1395 break;
1396 }
1397 }
1398 SkDebugf("path: done %s\n", title ? title : "");
1399}
1400
1401#include "SkTSort.h"
1402
1403void SkPath::UnitTest() {
1404#ifdef SK_SUPPORT_UNITTEST
1405 SkPath p;
1406 SkRect r;
1407
1408 r.set(0, 0, 10, 20);
1409 p.addRect(r);
1410 p.dump(false);
1411 p.dump(true);
1412
1413 {
1414 int array[] = { 5, 3, 7, 2, 6, 1, 2, 9, 5, 0 };
1415 int i;
1416
1417 for (i = 0; i < (int)SK_ARRAY_COUNT(array); i++) {
1418 SkDebugf(" %d", array[i]);
1419 }
1420 SkDebugf("\n");
1421 SkTHeapSort<int>(array, SK_ARRAY_COUNT(array));
1422 for (i = 0; i < (int)SK_ARRAY_COUNT(array); i++)
1423 SkDebugf(" %d", array[i]);
1424 SkDebugf("\n");
1425 }
1426
1427 {
1428 SkPath p;
1429 SkPoint pt;
1430
1431 p.moveTo(SK_Scalar1, 0);
1432 p.getLastPt(&pt);
1433 SkASSERT(pt.fX == SK_Scalar1);
1434 }
1435#endif
1436}
1437
1438#endif