1 2/* 3 * Copyright 2006 The Android Open Source Project 4 * 5 * Use of this source code is governed by a BSD-style license that can be 6 * found in the LICENSE file. 7 */ 8 9 10#ifndef SkMatrix_DEFINED 11#define SkMatrix_DEFINED 12 13#include "SkRect.h" 14 15class SkString; 16 17#ifdef SK_SCALAR_IS_FLOAT 18 typedef SkScalar SkPersp; 19 #define SkScalarToPersp(x) (x) 20 #define SkPerspToScalar(x) (x) 21#else 22 typedef SkFract SkPersp; 23 #define SkScalarToPersp(x) SkFixedToFract(x) 24 #define SkPerspToScalar(x) SkFractToFixed(x) 25#endif 26 27/** \class SkMatrix 28 29 The SkMatrix class holds a 3x3 matrix for transforming coordinates. 30 SkMatrix does not have a constructor, so it must be explicitly initialized 31 using either reset() - to construct an identity matrix, or one of the set 32 functions (e.g. setTranslate, setRotate, etc.). 33*/ 34class SK_API SkMatrix { 35public: 36 /** Enum of bit fields for the mask return by getType(). 37 Use this to identify the complexity of the matrix. 38 */ 39 enum TypeMask { 40 kIdentity_Mask = 0, 41 kTranslate_Mask = 0x01, //!< set if the matrix has translation 42 kScale_Mask = 0x02, //!< set if the matrix has X or Y scale 43 kAffine_Mask = 0x04, //!< set if the matrix skews or rotates 44 kPerspective_Mask = 0x08 //!< set if the matrix is in perspective 45 }; 46 47 /** Returns a mask bitfield describing the types of transformations 48 that the matrix will perform. This information is used by routines 49 like mapPoints, to optimize its inner loops to only perform as much 50 arithmetic as is necessary. 51 */ 52 TypeMask getType() const { 53 if (fTypeMask & kUnknown_Mask) { 54 fTypeMask = this->computeTypeMask(); 55 } 56 // only return the public masks 57 return (TypeMask)(fTypeMask & 0xF); 58 } 59 60 /** Returns true if the matrix is identity. 61 */ 62 bool isIdentity() const { 63 return this->getType() == 0; 64 } 65 66 /** Returns true if will map a rectangle to another rectangle. This can be 67 true if the matrix is identity, scale-only, or rotates a multiple of 68 90 degrees. 69 */ 70 bool rectStaysRect() const { 71 if (fTypeMask & kUnknown_Mask) { 72 fTypeMask = this->computeTypeMask(); 73 } 74 return (fTypeMask & kRectStaysRect_Mask) != 0; 75 } 76 // alias for rectStaysRect() 77 bool preservesAxisAlignment() const { return this->rectStaysRect(); } 78 79 /** 80 * Returns true if the matrix contains perspective elements. 81 */ 82 bool hasPerspective() const { 83 return SkToBool(this->getPerspectiveTypeMaskOnly() & 84 kPerspective_Mask); 85 } 86 87 enum { 88 kMScaleX, 89 kMSkewX, 90 kMTransX, 91 kMSkewY, 92 kMScaleY, 93 kMTransY, 94 kMPersp0, 95 kMPersp1, 96 kMPersp2 97 }; 98 99 /** Affine arrays are in column major order 100 because that's how PDF and XPS like it. 101 */ 102 enum { 103 kAScaleX, 104 kASkewY, 105 kASkewX, 106 kAScaleY, 107 kATransX, 108 kATransY 109 }; 110 111 SkScalar operator[](int index) const { 112 SkASSERT((unsigned)index < 9); 113 return fMat[index]; 114 } 115 116 SkScalar get(int index) const { 117 SkASSERT((unsigned)index < 9); 118 return fMat[index]; 119 } 120 121 SkScalar getScaleX() const { return fMat[kMScaleX]; } 122 SkScalar getScaleY() const { return fMat[kMScaleY]; } 123 SkScalar getSkewY() const { return fMat[kMSkewY]; } 124 SkScalar getSkewX() const { return fMat[kMSkewX]; } 125 SkScalar getTranslateX() const { return fMat[kMTransX]; } 126 SkScalar getTranslateY() const { return fMat[kMTransY]; } 127 SkPersp getPerspX() const { return fMat[kMPersp0]; } 128 SkPersp getPerspY() const { return fMat[kMPersp1]; } 129 130 SkScalar& operator[](int index) { 131 SkASSERT((unsigned)index < 9); 132 this->setTypeMask(kUnknown_Mask); 133 return fMat[index]; 134 } 135 136 void set(int index, SkScalar value) { 137 SkASSERT((unsigned)index < 9); 138 fMat[index] = value; 139 this->setTypeMask(kUnknown_Mask); 140 } 141 142 void setScaleX(SkScalar v) { this->set(kMScaleX, v); } 143 void setScaleY(SkScalar v) { this->set(kMScaleY, v); } 144 void setSkewY(SkScalar v) { this->set(kMSkewY, v); } 145 void setSkewX(SkScalar v) { this->set(kMSkewX, v); } 146 void setTranslateX(SkScalar v) { this->set(kMTransX, v); } 147 void setTranslateY(SkScalar v) { this->set(kMTransY, v); } 148 void setPerspX(SkPersp v) { this->set(kMPersp0, v); } 149 void setPerspY(SkPersp v) { this->set(kMPersp1, v); } 150 151 void setAll(SkScalar scaleX, SkScalar skewX, SkScalar transX, 152 SkScalar skewY, SkScalar scaleY, SkScalar transY, 153 SkPersp persp0, SkPersp persp1, SkPersp persp2) { 154 fMat[kMScaleX] = scaleX; 155 fMat[kMSkewX] = skewX; 156 fMat[kMTransX] = transX; 157 fMat[kMSkewY] = skewY; 158 fMat[kMScaleY] = scaleY; 159 fMat[kMTransY] = transY; 160 fMat[kMPersp0] = persp0; 161 fMat[kMPersp1] = persp1; 162 fMat[kMPersp2] = persp2; 163 this->setTypeMask(kUnknown_Mask); 164 } 165 166 /** Set the matrix to identity 167 */ 168 void reset(); 169 // alias for reset() 170 void setIdentity() { this->reset(); } 171 172 /** Set the matrix to translate by (dx, dy). 173 */ 174 void setTranslate(SkScalar dx, SkScalar dy); 175 /** Set the matrix to scale by sx and sy, with a pivot point at (px, py). 176 The pivot point is the coordinate that should remain unchanged by the 177 specified transformation. 178 */ 179 void setScale(SkScalar sx, SkScalar sy, SkScalar px, SkScalar py); 180 /** Set the matrix to scale by sx and sy. 181 */ 182 void setScale(SkScalar sx, SkScalar sy); 183 /** Set the matrix to scale by 1/divx and 1/divy. Returns false and doesn't 184 touch the matrix if either divx or divy is zero. 185 */ 186 bool setIDiv(int divx, int divy); 187 /** Set the matrix to rotate by the specified number of degrees, with a 188 pivot point at (px, py). The pivot point is the coordinate that should 189 remain unchanged by the specified transformation. 190 */ 191 void setRotate(SkScalar degrees, SkScalar px, SkScalar py); 192 /** Set the matrix to rotate about (0,0) by the specified number of degrees. 193 */ 194 void setRotate(SkScalar degrees); 195 /** Set the matrix to rotate by the specified sine and cosine values, with 196 a pivot point at (px, py). The pivot point is the coordinate that 197 should remain unchanged by the specified transformation. 198 */ 199 void setSinCos(SkScalar sinValue, SkScalar cosValue, 200 SkScalar px, SkScalar py); 201 /** Set the matrix to rotate by the specified sine and cosine values. 202 */ 203 void setSinCos(SkScalar sinValue, SkScalar cosValue); 204 /** Set the matrix to skew by sx and sy, with a pivot point at (px, py). 205 The pivot point is the coordinate that should remain unchanged by the 206 specified transformation. 207 */ 208 void setSkew(SkScalar kx, SkScalar ky, SkScalar px, SkScalar py); 209 /** Set the matrix to skew by sx and sy. 210 */ 211 void setSkew(SkScalar kx, SkScalar ky); 212 /** Set the matrix to the concatenation of the two specified matrices, 213 returning true if the the result can be represented. Either of the 214 two matrices may also be the target matrix. *this = a * b; 215 */ 216 bool setConcat(const SkMatrix& a, const SkMatrix& b); 217 218 /** Preconcats the matrix with the specified translation. 219 M' = M * T(dx, dy) 220 */ 221 bool preTranslate(SkScalar dx, SkScalar dy); 222 /** Preconcats the matrix with the specified scale. 223 M' = M * S(sx, sy, px, py) 224 */ 225 bool preScale(SkScalar sx, SkScalar sy, SkScalar px, SkScalar py); 226 /** Preconcats the matrix with the specified scale. 227 M' = M * S(sx, sy) 228 */ 229 bool preScale(SkScalar sx, SkScalar sy); 230 /** Preconcats the matrix with the specified rotation. 231 M' = M * R(degrees, px, py) 232 */ 233 bool preRotate(SkScalar degrees, SkScalar px, SkScalar py); 234 /** Preconcats the matrix with the specified rotation. 235 M' = M * R(degrees) 236 */ 237 bool preRotate(SkScalar degrees); 238 /** Preconcats the matrix with the specified skew. 239 M' = M * K(kx, ky, px, py) 240 */ 241 bool preSkew(SkScalar kx, SkScalar ky, SkScalar px, SkScalar py); 242 /** Preconcats the matrix with the specified skew. 243 M' = M * K(kx, ky) 244 */ 245 bool preSkew(SkScalar kx, SkScalar ky); 246 /** Preconcats the matrix with the specified matrix. 247 M' = M * other 248 */ 249 bool preConcat(const SkMatrix& other); 250 251 /** Postconcats the matrix with the specified translation. 252 M' = T(dx, dy) * M 253 */ 254 bool postTranslate(SkScalar dx, SkScalar dy); 255 /** Postconcats the matrix with the specified scale. 256 M' = S(sx, sy, px, py) * M 257 */ 258 bool postScale(SkScalar sx, SkScalar sy, SkScalar px, SkScalar py); 259 /** Postconcats the matrix with the specified scale. 260 M' = S(sx, sy) * M 261 */ 262 bool postScale(SkScalar sx, SkScalar sy); 263 /** Postconcats the matrix by dividing it by the specified integers. 264 M' = S(1/divx, 1/divy, 0, 0) * M 265 */ 266 bool postIDiv(int divx, int divy); 267 /** Postconcats the matrix with the specified rotation. 268 M' = R(degrees, px, py) * M 269 */ 270 bool postRotate(SkScalar degrees, SkScalar px, SkScalar py); 271 /** Postconcats the matrix with the specified rotation. 272 M' = R(degrees) * M 273 */ 274 bool postRotate(SkScalar degrees); 275 /** Postconcats the matrix with the specified skew. 276 M' = K(kx, ky, px, py) * M 277 */ 278 bool postSkew(SkScalar kx, SkScalar ky, SkScalar px, SkScalar py); 279 /** Postconcats the matrix with the specified skew. 280 M' = K(kx, ky) * M 281 */ 282 bool postSkew(SkScalar kx, SkScalar ky); 283 /** Postconcats the matrix with the specified matrix. 284 M' = other * M 285 */ 286 bool postConcat(const SkMatrix& other); 287 288 enum ScaleToFit { 289 /** 290 * Scale in X and Y independently, so that src matches dst exactly. 291 * This may change the aspect ratio of the src. 292 */ 293 kFill_ScaleToFit, 294 /** 295 * Compute a scale that will maintain the original src aspect ratio, 296 * but will also ensure that src fits entirely inside dst. At least one 297 * axis (X or Y) will fit exactly. kStart aligns the result to the 298 * left and top edges of dst. 299 */ 300 kStart_ScaleToFit, 301 /** 302 * Compute a scale that will maintain the original src aspect ratio, 303 * but will also ensure that src fits entirely inside dst. At least one 304 * axis (X or Y) will fit exactly. The result is centered inside dst. 305 */ 306 kCenter_ScaleToFit, 307 /** 308 * Compute a scale that will maintain the original src aspect ratio, 309 * but will also ensure that src fits entirely inside dst. At least one 310 * axis (X or Y) will fit exactly. kEnd aligns the result to the 311 * right and bottom edges of dst. 312 */ 313 kEnd_ScaleToFit 314 }; 315 316 /** Set the matrix to the scale and translate values that map the source 317 rectangle to the destination rectangle, returning true if the the result 318 can be represented. 319 @param src the source rectangle to map from. 320 @param dst the destination rectangle to map to. 321 @param stf the ScaleToFit option 322 @return true if the matrix can be represented by the rectangle mapping. 323 */ 324 bool setRectToRect(const SkRect& src, const SkRect& dst, ScaleToFit stf); 325 326 /** Set the matrix such that the specified src points would map to the 327 specified dst points. count must be within [0..4]. 328 @param src The array of src points 329 @param dst The array of dst points 330 @param count The number of points to use for the transformation 331 @return true if the matrix was set to the specified transformation 332 */ 333 bool setPolyToPoly(const SkPoint src[], const SkPoint dst[], int count); 334 335 /** If this matrix can be inverted, return true and if inverse is not null, 336 set inverse to be the inverse of this matrix. If this matrix cannot be 337 inverted, ignore inverse and return false 338 */ 339 bool invert(SkMatrix* inverse) const; 340 341 /** Fills the passed array with affine identity values 342 in column major order. 343 @param affine The array to fill with affine identity values. 344 Must not be NULL. 345 */ 346 static void SetAffineIdentity(SkScalar affine[6]); 347 348 /** Fills the passed array with the affine values in column major order. 349 If the matrix is a perspective transform, returns false 350 and does not change the passed array. 351 @param affine The array to fill with affine values. Ignored if NULL. 352 */ 353 bool asAffine(SkScalar affine[6]) const; 354 355 /** Apply this matrix to the array of points specified by src, and write 356 the transformed points into the array of points specified by dst. 357 dst[] = M * src[] 358 @param dst Where the transformed coordinates are written. It must 359 contain at least count entries 360 @param src The original coordinates that are to be transformed. It 361 must contain at least count entries 362 @param count The number of points in src to read, and then transform 363 into dst. 364 */ 365 void mapPoints(SkPoint dst[], const SkPoint src[], int count) const; 366 367 /** Apply this matrix to the array of points, overwriting it with the 368 transformed values. 369 dst[] = M * pts[] 370 @param pts The points to be transformed. It must contain at least 371 count entries 372 @param count The number of points in pts. 373 */ 374 void mapPoints(SkPoint pts[], int count) const { 375 this->mapPoints(pts, pts, count); 376 } 377 378 /** Like mapPoints but with custom byte stride between the points. Stride 379 * should be a multiple of sizeof(SkScalar). 380 */ 381 void mapPointsWithStride(SkPoint pts[], size_t stride, int count) const { 382 SkASSERT(stride >= sizeof(SkPoint)); 383 SkASSERT(0 == stride % sizeof(SkScalar)); 384 for (int i = 0; i < count; ++i) { 385 this->mapPoints(pts, pts, 1); 386 pts = (SkPoint*)((intptr_t)pts + stride); 387 } 388 } 389 390 /** Like mapPoints but with custom byte stride between the points. 391 */ 392 void mapPointsWithStride(SkPoint dst[], SkPoint src[], 393 size_t stride, int count) const { 394 SkASSERT(stride >= sizeof(SkPoint)); 395 SkASSERT(0 == stride % sizeof(SkScalar)); 396 for (int i = 0; i < count; ++i) { 397 this->mapPoints(dst, src, 1); 398 src = (SkPoint*)((intptr_t)src + stride); 399 dst = (SkPoint*)((intptr_t)dst + stride); 400 } 401 } 402 403 void mapXY(SkScalar x, SkScalar y, SkPoint* result) const { 404 SkASSERT(result); 405 this->getMapXYProc()(*this, x, y, result); 406 } 407 408 /** Apply this matrix to the array of vectors specified by src, and write 409 the transformed vectors into the array of vectors specified by dst. 410 This is similar to mapPoints, but ignores any translation in the matrix. 411 @param dst Where the transformed coordinates are written. It must 412 contain at least count entries 413 @param src The original coordinates that are to be transformed. It 414 must contain at least count entries 415 @param count The number of vectors in src to read, and then transform 416 into dst. 417 */ 418 void mapVectors(SkVector dst[], const SkVector src[], int count) const; 419 420 /** Apply this matrix to the array of vectors specified by src, and write 421 the transformed vectors into the array of vectors specified by dst. 422 This is similar to mapPoints, but ignores any translation in the matrix. 423 @param vecs The vectors to be transformed. It must contain at least 424 count entries 425 @param count The number of vectors in vecs. 426 */ 427 void mapVectors(SkVector vecs[], int count) const { 428 this->mapVectors(vecs, vecs, count); 429 } 430 431 /** Apply this matrix to the src rectangle, and write the transformed 432 rectangle into dst. This is accomplished by transforming the 4 corners 433 of src, and then setting dst to the bounds of those points. 434 @param dst Where the transformed rectangle is written. 435 @param src The original rectangle to be transformed. 436 @return the result of calling rectStaysRect() 437 */ 438 bool mapRect(SkRect* dst, const SkRect& src) const; 439 440 /** Apply this matrix to the rectangle, and write the transformed rectangle 441 back into it. This is accomplished by transforming the 4 corners of 442 rect, and then setting it to the bounds of those points 443 @param rect The rectangle to transform. 444 @return the result of calling rectStaysRect() 445 */ 446 bool mapRect(SkRect* rect) const { 447 return this->mapRect(rect, *rect); 448 } 449 450 /** Return the mean radius of a circle after it has been mapped by 451 this matrix. NOTE: in perspective this value assumes the circle 452 has its center at the origin. 453 */ 454 SkScalar mapRadius(SkScalar radius) const; 455 456 typedef void (*MapXYProc)(const SkMatrix& mat, SkScalar x, SkScalar y, 457 SkPoint* result); 458 459 static MapXYProc GetMapXYProc(TypeMask mask) { 460 SkASSERT((mask & ~kAllMasks) == 0); 461 return gMapXYProcs[mask & kAllMasks]; 462 } 463 464 MapXYProc getMapXYProc() const { 465 return GetMapXYProc(this->getType()); 466 } 467 468 typedef void (*MapPtsProc)(const SkMatrix& mat, SkPoint dst[], 469 const SkPoint src[], int count); 470 471 static MapPtsProc GetMapPtsProc(TypeMask mask) { 472 SkASSERT((mask & ~kAllMasks) == 0); 473 return gMapPtsProcs[mask & kAllMasks]; 474 } 475 476 MapPtsProc getMapPtsProc() const { 477 return GetMapPtsProc(this->getType()); 478 } 479 480 /** If the matrix can be stepped in X (not complex perspective) 481 then return true and if step[XY] is not null, return the step[XY] value. 482 If it cannot, return false and ignore step. 483 */ 484 bool fixedStepInX(SkScalar y, SkFixed* stepX, SkFixed* stepY) const; 485 486#ifdef SK_SCALAR_IS_FIXED 487 friend bool operator==(const SkMatrix& a, const SkMatrix& b) { 488 return memcmp(a.fMat, b.fMat, sizeof(a.fMat)) == 0; 489 } 490 491 friend bool operator!=(const SkMatrix& a, const SkMatrix& b) { 492 return memcmp(a.fMat, b.fMat, sizeof(a.fMat)) != 0; 493 } 494#else 495 friend bool operator==(const SkMatrix& a, const SkMatrix& b); 496 friend bool operator!=(const SkMatrix& a, const SkMatrix& b) { 497 return !(a == b); 498 } 499#endif 500 501 enum { 502 // flatten/unflatten will never return a value larger than this 503 kMaxFlattenSize = 9 * sizeof(SkScalar) + sizeof(uint32_t) 504 }; 505 // return the number of bytes written, whether or not buffer is null 506 uint32_t flatten(void* buffer) const; 507 // return the number of bytes read 508 uint32_t unflatten(const void* buffer); 509 510 void dump() const; 511 void toDumpString(SkString*) const; 512 513 /** 514 * Calculates the maximum stretching factor of the matrix. If the matrix has 515 * perspective -1 is returned. 516 * 517 * @return maximum strecthing factor 518 */ 519 SkScalar getMaxStretch() const; 520 521 /** 522 * Return a reference to a const identity matrix 523 */ 524 static const SkMatrix& I(); 525 526 /** 527 * Return a reference to a const matrix that is "invalid", one that could 528 * never be used. 529 */ 530 static const SkMatrix& InvalidMatrix(); 531 532 /** 533 * Testing routine; the matrix's type cache should never need to be 534 * manually invalidated during normal use. 535 */ 536 void dirtyMatrixTypeCache() { 537 this->setTypeMask(kUnknown_Mask); 538 } 539 540private: 541 enum { 542 /** Set if the matrix will map a rectangle to another rectangle. This 543 can be true if the matrix is scale-only, or rotates a multiple of 544 90 degrees. This bit is not set if the matrix is identity. 545 546 This bit will be set on identity matrices 547 */ 548 kRectStaysRect_Mask = 0x10, 549 550 /** Set if the perspective bit is valid even though the rest of 551 the matrix is Unknown. 552 */ 553 kOnlyPerspectiveValid_Mask = 0x40, 554 555 kUnknown_Mask = 0x80, 556 557 kORableMasks = kTranslate_Mask | 558 kScale_Mask | 559 kAffine_Mask | 560 kPerspective_Mask, 561 562 kAllMasks = kTranslate_Mask | 563 kScale_Mask | 564 kAffine_Mask | 565 kPerspective_Mask | 566 kRectStaysRect_Mask 567 }; 568 569 SkScalar fMat[9]; 570 mutable uint8_t fTypeMask; 571 572 uint8_t computeTypeMask() const; 573 uint8_t computePerspectiveTypeMask() const; 574 575 void setTypeMask(int mask) { 576 // allow kUnknown or a valid mask 577 SkASSERT(kUnknown_Mask == mask || (mask & kAllMasks) == mask || 578 ((kUnknown_Mask | kOnlyPerspectiveValid_Mask | kPerspective_Mask) & mask) 579 == mask); 580 fTypeMask = SkToU8(mask); 581 } 582 583 void orTypeMask(int mask) { 584 SkASSERT((mask & kORableMasks) == mask); 585 fTypeMask = SkToU8(fTypeMask | mask); 586 } 587 588 void clearTypeMask(int mask) { 589 // only allow a valid mask 590 SkASSERT((mask & kAllMasks) == mask); 591 fTypeMask &= ~mask; 592 } 593 594 TypeMask getPerspectiveTypeMaskOnly() const { 595 if ((fTypeMask & kUnknown_Mask) && 596 !(fTypeMask & kOnlyPerspectiveValid_Mask)) { 597 fTypeMask = this->computePerspectiveTypeMask(); 598 } 599 return (TypeMask)(fTypeMask & 0xF); 600 } 601 602 /** Returns true if we already know that the matrix is identity; 603 false otherwise. 604 */ 605 bool isTriviallyIdentity() const { 606 if (fTypeMask & kUnknown_Mask) { 607 return false; 608 } 609 return ((fTypeMask & 0xF) == 0); 610 } 611 612 static bool Poly2Proc(const SkPoint[], SkMatrix*, const SkPoint& scale); 613 static bool Poly3Proc(const SkPoint[], SkMatrix*, const SkPoint& scale); 614 static bool Poly4Proc(const SkPoint[], SkMatrix*, const SkPoint& scale); 615 616 static void Identity_xy(const SkMatrix&, SkScalar, SkScalar, SkPoint*); 617 static void Trans_xy(const SkMatrix&, SkScalar, SkScalar, SkPoint*); 618 static void Scale_xy(const SkMatrix&, SkScalar, SkScalar, SkPoint*); 619 static void ScaleTrans_xy(const SkMatrix&, SkScalar, SkScalar, SkPoint*); 620 static void Rot_xy(const SkMatrix&, SkScalar, SkScalar, SkPoint*); 621 static void RotTrans_xy(const SkMatrix&, SkScalar, SkScalar, SkPoint*); 622 static void Persp_xy(const SkMatrix&, SkScalar, SkScalar, SkPoint*); 623 624 static const MapXYProc gMapXYProcs[]; 625 626 static void Identity_pts(const SkMatrix&, SkPoint[], const SkPoint[], int); 627 static void Trans_pts(const SkMatrix&, SkPoint dst[], const SkPoint[], int); 628 static void Scale_pts(const SkMatrix&, SkPoint dst[], const SkPoint[], int); 629 static void ScaleTrans_pts(const SkMatrix&, SkPoint dst[], const SkPoint[], 630 int count); 631 static void Rot_pts(const SkMatrix&, SkPoint dst[], const SkPoint[], int); 632 static void RotTrans_pts(const SkMatrix&, SkPoint dst[], const SkPoint[], 633 int count); 634 static void Persp_pts(const SkMatrix&, SkPoint dst[], const SkPoint[], int); 635 636 static const MapPtsProc gMapPtsProcs[]; 637 638 friend class SkPerspIter; 639}; 640 641#endif 642