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