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