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: |
jvr | 6385a4e | 2003-08-24 16:17:11 +0000 | [diff] [blame^] | 20 | |
jvr | 9e58c90 | 2003-08-22 14:56:48 +0000 | [diff] [blame] | 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.""" |
jvr | 6385a4e | 2003-08-24 16:17:11 +0000 | [diff] [blame^] | 24 | |
jvr | 9e58c90 | 2003-08-22 14:56:48 +0000 | [diff] [blame] | 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 |
jvr | 6385a4e | 2003-08-24 16:17:11 +0000 | [diff] [blame^] | 27 | |
jvr | 9e58c90 | 2003-08-22 14:56:48 +0000 | [diff] [blame] | 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) |
jvr | 6385a4e | 2003-08-24 16:17:11 +0000 | [diff] [blame^] | 32 | |
jvr | 9e58c90 | 2003-08-22 14:56:48 +0000 | [diff] [blame] | 33 | def translate(self, x=0, y=0): |
| 34 | return self.transform((1, 0, 0, 1, x, y)) |
jvr | 6385a4e | 2003-08-24 16:17:11 +0000 | [diff] [blame^] | 35 | |
jvr | 9e58c90 | 2003-08-22 14:56:48 +0000 | [diff] [blame] | 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)) |
jvr | 6385a4e | 2003-08-24 16:17:11 +0000 | [diff] [blame^] | 40 | |
jvr | 9e58c90 | 2003-08-22 14:56:48 +0000 | [diff] [blame] | 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)) |
jvr | 6385a4e | 2003-08-24 16:17:11 +0000 | [diff] [blame^] | 46 | |
jvr | 9e58c90 | 2003-08-22 14:56:48 +0000 | [diff] [blame] | 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)) |
jvr | 6385a4e | 2003-08-24 16:17:11 +0000 | [diff] [blame^] | 50 | |
jvr | 9e58c90 | 2003-08-22 14:56:48 +0000 | [diff] [blame] | 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) |
jvr | 6385a4e | 2003-08-24 16:17:11 +0000 | [diff] [blame^] | 61 | |
jvr | 9e58c90 | 2003-08-22 14:56:48 +0000 | [diff] [blame] | 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) |
jvr | 6385a4e | 2003-08-24 16:17:11 +0000 | [diff] [blame^] | 72 | |
jvr | 9e58c90 | 2003-08-22 14:56:48 +0000 | [diff] [blame] | 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) |
jvr | 6385a4e | 2003-08-24 16:17:11 +0000 | [diff] [blame^] | 82 | |
jvr | 9e58c90 | 2003-08-22 14:56:48 +0000 | [diff] [blame] | 83 | def toPS(self): |
| 84 | return "[%s %s %s %s %s %s]" % self.__affine |
jvr | 6385a4e | 2003-08-24 16:17:11 +0000 | [diff] [blame^] | 85 | |
jvr | 9e58c90 | 2003-08-22 14:56:48 +0000 | [diff] [blame] | 86 | def __len__(self): |
| 87 | return 6 |
jvr | 6385a4e | 2003-08-24 16:17:11 +0000 | [diff] [blame^] | 88 | |
jvr | 9e58c90 | 2003-08-22 14:56:48 +0000 | [diff] [blame] | 89 | def __getitem__(self, index): |
| 90 | return self.__affine[index] |
jvr | 6385a4e | 2003-08-24 16:17:11 +0000 | [diff] [blame^] | 91 | |
jvr | 9e58c90 | 2003-08-22 14:56:48 +0000 | [diff] [blame] | 92 | def __getslice__(self, i, j): |
| 93 | return self.__affine[i:j] |
jvr | 6385a4e | 2003-08-24 16:17:11 +0000 | [diff] [blame^] | 94 | |
jvr | 9e58c90 | 2003-08-22 14:56:48 +0000 | [diff] [blame] | 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)) |
jvr | 6385a4e | 2003-08-24 16:17:11 +0000 | [diff] [blame^] | 100 | |
jvr | 9e58c90 | 2003-08-22 14:56:48 +0000 | [diff] [blame] | 101 | def __hash__(self): |
| 102 | return hash(self.__affine) |
jvr | 6385a4e | 2003-08-24 16:17:11 +0000 | [diff] [blame^] | 103 | |
jvr | 9e58c90 | 2003-08-22 14:56:48 +0000 | [diff] [blame] | 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) |