blob: 224333bd7f54a0df90af33d266cea5cb124d7753 [file] [log] [blame]
jvr9e58c902003-08-22 14:56:48 +00001"""Affine 2D transformation class."""
2
3
4_EPSILON = 1e-15
5_ONE_EPSILON = 1 - _EPSILON
6_MINUS_ONE_EPSILON = -1 + _EPSILON
7
8
9def _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
19class Transform:
jvr6385a4e2003-08-24 16:17:11 +000020
jvr9e58c902003-08-22 14:56:48 +000021 """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."""
jvr6385a4e2003-08-24 16:17:11 +000024
jvr9e58c902003-08-22 14:56:48 +000025 def __init__(self, xx=1, xy=0, yx=0, yy=1, dx=0, dy=0):
26 self.__affine = xx, xy, yx, yy, dx, dy
jvr6385a4e2003-08-24 16:17:11 +000027
jvr9e58c902003-08-22 14:56:48 +000028 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)
jvr6385a4e2003-08-24 16:17:11 +000032
jvr9e58c902003-08-22 14:56:48 +000033 def translate(self, x=0, y=0):
34 return self.transform((1, 0, 0, 1, x, y))
jvr6385a4e2003-08-24 16:17:11 +000035
jvr9e58c902003-08-22 14:56:48 +000036 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))
jvr6385a4e2003-08-24 16:17:11 +000040
jvr9e58c902003-08-22 14:56:48 +000041 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))
jvr6385a4e2003-08-24 16:17:11 +000046
jvr9e58c902003-08-22 14:56:48 +000047 def skew(self, x=0, y=0):
48 import math
49 return self.transform((1, math.tan(y), math.tan(x), 1, 0, 0))
jvr6385a4e2003-08-24 16:17:11 +000050
jvr9e58c902003-08-22 14:56:48 +000051 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)
jvr6385a4e2003-08-24 16:17:11 +000061
jvr9e58c902003-08-22 14:56:48 +000062 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)
jvr6385a4e2003-08-24 16:17:11 +000072
jvr9e58c902003-08-22 14:56:48 +000073 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)
jvr6385a4e2003-08-24 16:17:11 +000082
jvr9e58c902003-08-22 14:56:48 +000083 def toPS(self):
84 return "[%s %s %s %s %s %s]" % self.__affine
jvr6385a4e2003-08-24 16:17:11 +000085
jvr9e58c902003-08-22 14:56:48 +000086 def __len__(self):
87 return 6
jvr6385a4e2003-08-24 16:17:11 +000088
jvr9e58c902003-08-22 14:56:48 +000089 def __getitem__(self, index):
90 return self.__affine[index]
jvr6385a4e2003-08-24 16:17:11 +000091
jvr9e58c902003-08-22 14:56:48 +000092 def __getslice__(self, i, j):
93 return self.__affine[i:j]
jvr6385a4e2003-08-24 16:17:11 +000094
jvr9e58c902003-08-22 14:56:48 +000095 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))
jvr6385a4e2003-08-24 16:17:11 +0000100
jvr9e58c902003-08-22 14:56:48 +0000101 def __hash__(self):
102 return hash(self.__affine)
jvr6385a4e2003-08-24 16:17:11 +0000103
jvr9e58c902003-08-22 14:56:48 +0000104 def __repr__(self):
105 return "<%s [%s %s %s %s %s %s]>" % ((self.__class__.__name__,)
106 + tuple(map(str, self.__affine)))
107
108
109Identity = Transform()
110
111def Offset(x=0, y=0):
112 return Transform(1, 0, 0, 1, x, y)
113
114def Scale(x, y=None):
115 if y is None:
116 y = x
117 return Transform(x, 0, 0, y, 0, 0)