blob: d419852370dc760261b39196f75e06fd57a4b60d [file] [log] [blame]
jvrf184f752003-09-16 11:01:40 +00001"""Affine 2D transformation matrix class.
2
3The Transform class implements various transformation matrix operations,
4both on the matrix itself, as well as on 2D coordinates.
5
6Transform instances are effectively immutable: all methods that operate on the
7transformation itself always return a new instance. This has as the
8interesting side effect that Transform instances are hashable, ie. they can be
9used as dictionary keys.
10
11This module exports the following symbols:
12
13 Transform -- this is the main class
14 Identity -- Transform instance set to the identity transformation
15 Offset -- Convenience function that returns a translating transformation
16 Scale -- Convenience function that returns a scaling transformation
17
18Examples:
19
20 >>> t = Transform(2, 0, 0, 3, 0, 0)
21 >>> t.transformPoint((100, 100))
22 (200, 300)
23 >>> t = Scale(2, 3)
24 >>> t.transformPoint((100, 100))
25 (200, 300)
26 >>> t.transformPoint((0, 0))
27 (0, 0)
28 >>> t = Offset(2, 3)
29 >>> t.transformPoint((100, 100))
30 (102, 103)
31 >>> t.transformPoint((0, 0))
32 (2, 3)
33 >>> t2 = t.scale(0.5)
34 >>> t2.transformPoint((100, 100))
35 (52.0, 53.0)
36 >>> import math
37 >>> t3 = t2.rotate(math.pi / 2)
38 >>> t3.transformPoint((0, 0))
39 (2.0, 3.0)
40 >>> t3.transformPoint((100, 100))
41 (-48.0, 53.0)
42 >>> t = Identity.scale(0.5).translate(100, 200).skew(0.1, 0.2)
43 >>> t.transformPoints([(0, 0), (1, 1), (100, 100)])
44 [(50.0, 100.0), (50.550167336042726, 100.60135501775433), (105.01673360427253, 160.13550177543362)]
45 >>>
46"""
47
48
49__all__ = ["Transform", "Identity", "Offset", "Scale"]
jvr9e58c902003-08-22 14:56:48 +000050
51
52_EPSILON = 1e-15
53_ONE_EPSILON = 1 - _EPSILON
54_MINUS_ONE_EPSILON = -1 + _EPSILON
55
56
57def _normSinCos(v):
58 if abs(v) < _EPSILON:
59 v = 0
60 elif v > _ONE_EPSILON:
61 v = 1
62 elif v < _MINUS_ONE_EPSILON:
63 v = -1
64 return v
65
66
67class Transform:
jvr6385a4e2003-08-24 16:17:11 +000068
jvr9e58c902003-08-22 14:56:48 +000069 """2x2 transformation matrix plus offset, a.k.a. Affine transform.
jvrf184f752003-09-16 11:01:40 +000070 Transform instances are immutable: all transforming methods, eg.
71 rotate(), return a new Transform instance.
72
73 Examples:
74 >>> t = Transform()
75 >>> t
76 <Transform [1 0 0 1 0 0]>
77 >>> t.scale(2)
78 <Transform [2 0 0 2 0 0]>
79 >>> t.scale(2.5, 5.5)
80 <Transform [2.5 0.0 0.0 5.5 0 0]>
81 >>>
82 >>> t.scale(2, 3).transformPoint((100, 100))
83 (200, 300)
84 """
jvr6385a4e2003-08-24 16:17:11 +000085
jvr9e58c902003-08-22 14:56:48 +000086 def __init__(self, xx=1, xy=0, yx=0, yy=1, dx=0, dy=0):
87 self.__affine = xx, xy, yx, yy, dx, dy
jvr6385a4e2003-08-24 16:17:11 +000088
jvr9e58c902003-08-22 14:56:48 +000089 def transformPoint(self, (x, y)):
jvrf184f752003-09-16 11:01:40 +000090 """Transform a point.
91
92 Example:
93 >>> t = Transform()
94 >>> t = t.scale(2.5, 5.5)
95 >>> t.transformPoint((100, 100))
96 (250.0, 550.0)
97 """
jvr9e58c902003-08-22 14:56:48 +000098 xx, xy, yx, yy, dx, dy = self.__affine
99 return (xx*x + yx*y + dx, xy*x + yy*y + dy)
jvr6385a4e2003-08-24 16:17:11 +0000100
jvrf184f752003-09-16 11:01:40 +0000101 def transformPoints(self, points):
102 """Transform a list of points.
103
104 Example:
105 >>> t = Scale(2, 3)
106 >>> t.transformPoints([(0, 0), (0, 100), (100, 100), (100, 0)])
107 [(0, 0), (0, 300), (200, 300), (200, 0)]
108 >>>
109 """
110 xx, xy, yx, yy, dx, dy = self.__affine
111 return [(xx*x + yx*y + dx, xy*x + yy*y + dy) for x, y in points]
112
jvr9e58c902003-08-22 14:56:48 +0000113 def translate(self, x=0, y=0):
jvrf184f752003-09-16 11:01:40 +0000114 """Return a new transformation, translated (offset) by x, y.
115
116 Example:
117 >>> t = Transform()
118 >>> t.translate(20, 30)
119 <Transform [1 0 0 1 20 30]>
120 >>>
121 """
jvr9e58c902003-08-22 14:56:48 +0000122 return self.transform((1, 0, 0, 1, x, y))
jvr6385a4e2003-08-24 16:17:11 +0000123
jvr9e58c902003-08-22 14:56:48 +0000124 def scale(self, x=1, y=None):
jvrf184f752003-09-16 11:01:40 +0000125 """Return a new transformation, scaled by x, y. The 'y' argument
126 may be None, which implies to use the x value for y as well.
127
128 Example:
129 >>> t = Transform()
130 >>> t.scale(5)
131 <Transform [5 0 0 5 0 0]>
132 >>> t.scale(5, 6)
133 <Transform [5 0 0 6 0 0]>
134 >>>
135 """
jvr9e58c902003-08-22 14:56:48 +0000136 if y is None:
137 y = x
138 return self.transform((x, 0, 0, y, 0, 0))
jvr6385a4e2003-08-24 16:17:11 +0000139
jvr9e58c902003-08-22 14:56:48 +0000140 def rotate(self, angle):
jvrf184f752003-09-16 11:01:40 +0000141 """Return a new transformation, rotated by 'angle' (radians).
142
143 Example:
144 >>> import math
145 >>> t = Transform()
146 >>> t.rotate(math.pi / 2)
147 <Transform [0 1 -1 0 0 0]>
148 >>>
149 """
jvr9e58c902003-08-22 14:56:48 +0000150 import math
151 c = _normSinCos(math.cos(angle))
152 s = _normSinCos(math.sin(angle))
153 return self.transform((c, s, -s, c, 0, 0))
jvr6385a4e2003-08-24 16:17:11 +0000154
jvr9e58c902003-08-22 14:56:48 +0000155 def skew(self, x=0, y=0):
jvrf184f752003-09-16 11:01:40 +0000156 """Return a new transformation, skewed by x and y.
157
158 Example:
159 >>> import math
160 >>> t = Transform()
161 >>> t.skew(math.pi / 4)
162 <Transform [1.0 0.0 1.0 1.0 0 0]>
163 >>>
164 """
jvr9e58c902003-08-22 14:56:48 +0000165 import math
166 return self.transform((1, math.tan(y), math.tan(x), 1, 0, 0))
jvr6385a4e2003-08-24 16:17:11 +0000167
jvr9e58c902003-08-22 14:56:48 +0000168 def transform(self, other):
jvrf184f752003-09-16 11:01:40 +0000169 """Return a new transformation, transformed by another
170 transformation.
171
172 Example:
173 >>> t = Transform(2, 0, 0, 3, 1, 6)
174 >>> t.transform((4, 3, 2, 1, 5, 6))
175 <Transform [8 9 4 3 11 24]>
176 >>>
177 """
jvr9e58c902003-08-22 14:56:48 +0000178 xx1, xy1, yx1, yy1, dx1, dy1 = other
179 xx2, xy2, yx2, yy2, dx2, dy2 = self.__affine
180 return self.__class__(
181 xx1*xx2 + xy1*yx2,
182 xx1*xy2 + xy1*yy2,
183 yx1*xx2 + yy1*yx2,
184 yx1*xy2 + yy1*yy2,
185 xx2*dx1 + yx2*dy1 + dx2,
186 xy2*dx1 + yy2*dy1 + dy2)
jvr6385a4e2003-08-24 16:17:11 +0000187
jvr9e58c902003-08-22 14:56:48 +0000188 def reverseTransform(self, other):
jvrf184f752003-09-16 11:01:40 +0000189 """Return a new transformation, which is the other transformation
190 transformed by self. self.reverseTransform(other) is equivalent to
191 other.transform(self).
192
193 Example:
194 >>> t = Transform(2, 0, 0, 3, 1, 6)
195 >>> t.reverseTransform((4, 3, 2, 1, 5, 6))
196 <Transform [8 6 6 3 21 15]>
197 >>> Transform(4, 3, 2, 1, 5, 6).transform((2, 0, 0, 3, 1, 6))
198 <Transform [8 6 6 3 21 15]>
199 >>>
200 """
jvr9e58c902003-08-22 14:56:48 +0000201 xx1, xy1, yx1, yy1, dx1, dy1 = self.__affine
202 xx2, xy2, yx2, yy2, dx2, dy2 = other
203 return self.__class__(
204 xx1*xx2 + xy1*yx2,
205 xx1*xy2 + xy1*yy2,
206 yx1*xx2 + yy1*yx2,
207 yx1*xy2 + yy1*yy2,
208 xx2*dx1 + yx2*dy1 + dx2,
209 xy2*dx1 + yy2*dy1 + dy2)
jvr6385a4e2003-08-24 16:17:11 +0000210
jvr9e58c902003-08-22 14:56:48 +0000211 def inverse(self):
jvrf184f752003-09-16 11:01:40 +0000212 """Return the inverse transformation.
213
214 Example:
215 >>> t = Transform(2, 0, 0, 3, 1, 6)
216 >>> t.inverse()
217 <Transform [0.5 0.0 0.0 0.333333333333 -0.5 -2.0]>
218 >>>
219 """
jvr9e58c902003-08-22 14:56:48 +0000220 if self.__affine == (1, 0, 0, 1, 0, 0):
221 return self
222 xx, xy, yx, yy, dx, dy = self.__affine
223 det = float(xx*yy - yx*xy)
224 xx, xy, yx, yy = yy/det, -xy/det, -yx/det, xx/det
225 dx, dy = -xx*dx - yx*dy, -xy*dx - yy*dy
226 return self.__class__(xx, xy, yx, yy, dx, dy)
jvr6385a4e2003-08-24 16:17:11 +0000227
jvr9e58c902003-08-22 14:56:48 +0000228 def toPS(self):
229 return "[%s %s %s %s %s %s]" % self.__affine
jvr6385a4e2003-08-24 16:17:11 +0000230
jvr9e58c902003-08-22 14:56:48 +0000231 def __len__(self):
232 return 6
jvr6385a4e2003-08-24 16:17:11 +0000233
jvr9e58c902003-08-22 14:56:48 +0000234 def __getitem__(self, index):
235 return self.__affine[index]
jvr6385a4e2003-08-24 16:17:11 +0000236
jvr9e58c902003-08-22 14:56:48 +0000237 def __getslice__(self, i, j):
238 return self.__affine[i:j]
jvr6385a4e2003-08-24 16:17:11 +0000239
jvr9e58c902003-08-22 14:56:48 +0000240 def __cmp__(self, other):
241 xx1, xy1, yx1, yy1, dx1, dy1 = self.__affine
242 xx2, xy2, yx2, yy2, dx2, dy2 = other
243 return cmp((xx1, xy1, yx1, yy1, dx1, dy1),
244 (xx2, xy2, yx2, yy2, dx2, dy2))
jvr6385a4e2003-08-24 16:17:11 +0000245
jvr9e58c902003-08-22 14:56:48 +0000246 def __hash__(self):
247 return hash(self.__affine)
jvr6385a4e2003-08-24 16:17:11 +0000248
jvr9e58c902003-08-22 14:56:48 +0000249 def __repr__(self):
250 return "<%s [%s %s %s %s %s %s]>" % ((self.__class__.__name__,)
251 + tuple(map(str, self.__affine)))
252
253
254Identity = Transform()
255
256def Offset(x=0, y=0):
jvrf184f752003-09-16 11:01:40 +0000257 """Return the identity transformation offset by x, y.
258
259 Example:
260 >>> Offset(2, 3)
261 <Transform [1 0 0 1 2 3]>
262 >>>
263 """
jvr9e58c902003-08-22 14:56:48 +0000264 return Transform(1, 0, 0, 1, x, y)
265
266def Scale(x, y=None):
jvrf184f752003-09-16 11:01:40 +0000267 """Return the identity transformation scaled by x, y. The 'y' argument
268 may be None, which implies to use the x value for y as well.
269
270 Example:
271 >>> Scale(2, 3)
272 <Transform [2 0 0 3 0 0]>
273 >>>
274 """
jvr9e58c902003-08-22 14:56:48 +0000275 if y is None:
276 y = x
277 return Transform(x, 0, 0, y, 0, 0)
jvrf184f752003-09-16 11:01:40 +0000278
279
280def _test():
281 import doctest, fontTools.misc.transform
282 return doctest.testmod(fontTools.misc.transform)
283
284if __name__ == "__main__":
285 _test()