jvr | 9e58c90 | 2003-08-22 14:56:48 +0000 | [diff] [blame^] | 1 | """Affine 2D transformation class.""" |
| 2 | |
| 3 | |
| 4 | _EPSILON = 1e-15 |
| 5 | _ONE_EPSILON = 1 - _EPSILON |
| 6 | _MINUS_ONE_EPSILON = -1 + _EPSILON |
| 7 | |
| 8 | |
| 9 | def _normSinCos(v): |
| 10 | if abs(v) < _EPSILON: |
| 11 | v = 0 |
| 12 | elif v > _ONE_EPSILON: |
| 13 | v = 1 |
| 14 | elif v < _MINUS_ONE_EPSILON: |
| 15 | v = -1 |
| 16 | return v |
| 17 | |
| 18 | |
| 19 | class Transform: |
| 20 | |
| 21 | """2x2 transformation matrix plus offset, a.k.a. Affine transform. |
| 22 | Transform instances are "immutable": all transforming methods, eg. |
| 23 | rotate(), return a new Transform instance.""" |
| 24 | |
| 25 | def __init__(self, xx=1, xy=0, yx=0, yy=1, dx=0, dy=0): |
| 26 | self.__affine = xx, xy, yx, yy, dx, dy |
| 27 | |
| 28 | def transformPoint(self, (x, y)): |
| 29 | """Transform a point.""" |
| 30 | xx, xy, yx, yy, dx, dy = self.__affine |
| 31 | return (xx*x + yx*y + dx, xy*x + yy*y + dy) |
| 32 | |
| 33 | def translate(self, x=0, y=0): |
| 34 | return self.transform((1, 0, 0, 1, x, y)) |
| 35 | |
| 36 | def scale(self, x=1, y=None): |
| 37 | if y is None: |
| 38 | y = x |
| 39 | return self.transform((x, 0, 0, y, 0, 0)) |
| 40 | |
| 41 | def rotate(self, angle): |
| 42 | import math |
| 43 | c = _normSinCos(math.cos(angle)) |
| 44 | s = _normSinCos(math.sin(angle)) |
| 45 | return self.transform((c, s, -s, c, 0, 0)) |
| 46 | |
| 47 | def skew(self, x=0, y=0): |
| 48 | import math |
| 49 | return self.transform((1, math.tan(y), math.tan(x), 1, 0, 0)) |
| 50 | |
| 51 | def transform(self, other): |
| 52 | xx1, xy1, yx1, yy1, dx1, dy1 = other |
| 53 | xx2, xy2, yx2, yy2, dx2, dy2 = self.__affine |
| 54 | return self.__class__( |
| 55 | xx1*xx2 + xy1*yx2, |
| 56 | xx1*xy2 + xy1*yy2, |
| 57 | yx1*xx2 + yy1*yx2, |
| 58 | yx1*xy2 + yy1*yy2, |
| 59 | xx2*dx1 + yx2*dy1 + dx2, |
| 60 | xy2*dx1 + yy2*dy1 + dy2) |
| 61 | |
| 62 | def reverseTransform(self, other): |
| 63 | xx1, xy1, yx1, yy1, dx1, dy1 = self.__affine |
| 64 | xx2, xy2, yx2, yy2, dx2, dy2 = other |
| 65 | return self.__class__( |
| 66 | xx1*xx2 + xy1*yx2, |
| 67 | xx1*xy2 + xy1*yy2, |
| 68 | yx1*xx2 + yy1*yx2, |
| 69 | yx1*xy2 + yy1*yy2, |
| 70 | xx2*dx1 + yx2*dy1 + dx2, |
| 71 | xy2*dx1 + yy2*dy1 + dy2) |
| 72 | |
| 73 | def inverse(self): |
| 74 | "Return the inverse transform." |
| 75 | if self.__affine == (1, 0, 0, 1, 0, 0): |
| 76 | return self |
| 77 | xx, xy, yx, yy, dx, dy = self.__affine |
| 78 | det = float(xx*yy - yx*xy) |
| 79 | xx, xy, yx, yy = yy/det, -xy/det, -yx/det, xx/det |
| 80 | dx, dy = -xx*dx - yx*dy, -xy*dx - yy*dy |
| 81 | return self.__class__(xx, xy, yx, yy, dx, dy) |
| 82 | |
| 83 | def toPS(self): |
| 84 | return "[%s %s %s %s %s %s]" % self.__affine |
| 85 | |
| 86 | def __len__(self): |
| 87 | return 6 |
| 88 | |
| 89 | def __getitem__(self, index): |
| 90 | return self.__affine[index] |
| 91 | |
| 92 | def __getslice__(self, i, j): |
| 93 | return self.__affine[i:j] |
| 94 | |
| 95 | def __cmp__(self, other): |
| 96 | xx1, xy1, yx1, yy1, dx1, dy1 = self.__affine |
| 97 | xx2, xy2, yx2, yy2, dx2, dy2 = other |
| 98 | return cmp((xx1, xy1, yx1, yy1, dx1, dy1), |
| 99 | (xx2, xy2, yx2, yy2, dx2, dy2)) |
| 100 | |
| 101 | def __hash__(self): |
| 102 | return hash(self.__affine) |
| 103 | |
| 104 | def __repr__(self): |
| 105 | return "<%s [%s %s %s %s %s %s]>" % ((self.__class__.__name__,) |
| 106 | + tuple(map(str, self.__affine))) |
| 107 | |
| 108 | |
| 109 | Identity = Transform() |
| 110 | |
| 111 | def Offset(x=0, y=0): |
| 112 | return Transform(1, 0, 0, 1, x, y) |
| 113 | |
| 114 | def Scale(x, y=None): |
| 115 | if y is None: |
| 116 | y = x |
| 117 | return Transform(x, 0, 0, y, 0, 0) |