one more change to the multipage question
[PyX/mjg.git] / pyx / trafo.py
blobfce2b5eaa2f9ef1a73adb15d92308297749cc39f
1 #!/usr/bin/env python
2 # -*- coding: ISO-8859-1 -*-
5 # Copyright (C) 2002-2004 Jörg Lehmann <joergl@users.sourceforge.net>
6 # Copyright (C) 2002-2004 André Wobst <wobsta@users.sourceforge.net>
8 # This file is part of PyX (http://pyx.sourceforge.net/).
10 # PyX is free software; you can redistribute it and/or modify
11 # it under the terms of the GNU General Public License as published by
12 # the Free Software Foundation; either version 2 of the License, or
13 # (at your option) any later version.
15 # PyX is distributed in the hope that it will be useful,
16 # but WITHOUT ANY WARRANTY; without even the implied warranty of
17 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 # GNU General Public License for more details.
20 # You should have received a copy of the GNU General Public License
21 # along with PyX; if not, write to the Free Software
22 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
24 import math
25 import attr, canvas, deformer, unit
27 # some helper routines
29 def _rmatrix(angle):
30 phi = math.pi*angle/180.0
32 return ((math.cos(phi), -math.sin(phi)),
33 (math.sin(phi), math.cos(phi)))
35 def _rvector(angle, x, y):
36 phi = math.pi*angle/180.0
38 return ((1-math.cos(phi))*x + math.sin(phi) *y,
39 -math.sin(phi) *x + (1-math.cos(phi))*y)
42 def _mmatrix(angle):
43 phi = math.pi*angle/180.0
45 return ( (math.cos(phi)*math.cos(phi)-math.sin(phi)*math.sin(phi),
46 -2*math.sin(phi)*math.cos(phi) ),
47 (-2*math.sin(phi)*math.cos(phi),
48 math.sin(phi)*math.sin(phi)-math.cos(phi)*math.cos(phi) ) )
50 def _det(matrix):
51 return matrix[0][0]*matrix[1][1] - matrix[0][1]*matrix[1][0]
53 # Exception
55 class UndefinedResultError(ArithmeticError):
56 pass
58 # trafo: affine transformations
60 class trafo_pt(canvas.canvasitem, deformer.deformer):
62 """affine transformation (coordinates in constructor in pts)
64 Note that though the coordinates in the constructor are in
65 pts (which is useful for internal purposes), all other
66 methods only accept units in the standard user notation.
68 """
70 def __init__(self, matrix=((1,0),(0,1)), vector=(0,0)):
71 if _det(matrix)==0:
72 raise UndefinedResultError, "transformation matrix must not be singular"
73 else:
74 self.matrix=matrix
75 self.vector = vector
77 def __mul__(self, other):
78 if isinstance(other, trafo_pt):
79 matrix = ( ( self.matrix[0][0]*other.matrix[0][0] +
80 self.matrix[0][1]*other.matrix[1][0],
81 self.matrix[0][0]*other.matrix[0][1] +
82 self.matrix[0][1]*other.matrix[1][1] ),
83 ( self.matrix[1][0]*other.matrix[0][0] +
84 self.matrix[1][1]*other.matrix[1][0],
85 self.matrix[1][0]*other.matrix[0][1] +
86 self.matrix[1][1]*other.matrix[1][1] )
89 vector = ( self.matrix[0][0]*other.vector[0] +
90 self.matrix[0][1]*other.vector[1] +
91 self.vector[0],
92 self.matrix[1][0]*other.vector[0] +
93 self.matrix[1][1]*other.vector[1] +
94 self.vector[1] )
96 return trafo_pt(matrix=matrix, vector=vector)
97 else:
98 raise NotImplementedError, "can only multiply two transformations"
100 def __str__(self):
101 return "[%f %f %f %f %f %f]" % \
102 ( self.matrix[0][0], self.matrix[1][0],
103 self.matrix[0][1], self.matrix[1][1],
104 self.vector[0], self.vector[1] )
106 def outputPS(self, file, writer, context):
107 file.write("[%f %f %f %f %f %f] concat\n" % \
108 ( self.matrix[0][0], self.matrix[1][0],
109 self.matrix[0][1], self.matrix[1][1],
110 self.vector[0], self.vector[1] ) )
112 def outputPDF(self, file, writer, context):
113 file.write("%f %f %f %f %f %f cm\n" % \
114 ( self.matrix[0][0], self.matrix[1][0],
115 self.matrix[0][1], self.matrix[1][1],
116 self.vector[0], self.vector[1] ) )
118 def bbox(self):
119 return None
121 def _apply(self, x, y):
122 """apply transformation to point (x,y) (coordinates in pts)"""
123 return (self.matrix[0][0]*x +
124 self.matrix[0][1]*y +
125 self.vector[0],
126 self.matrix[1][0]*x +
127 self.matrix[1][1]*y +
128 self.vector[1])
130 def apply(self, x, y):
131 # for the transformation we have to convert to points
132 tx, ty = self._apply(unit.topt(x), unit.topt(y))
133 return tx * unit.t_pt, ty * unit.t_pt
135 def deform(self, path):
136 return path.transformed(self)
138 def inverse(self):
139 det = _det(self.matrix) # shouldn't be zero, but
140 try:
141 matrix = ( ( self.matrix[1][1]/det, -self.matrix[0][1]/det),
142 (-self.matrix[1][0]/det, self.matrix[0][0]/det)
144 except ZeroDivisionError:
145 raise UndefinedResultError, "transformation matrix must not be singular"
146 return trafo_pt(matrix=matrix) * \
147 trafo_pt(vector=(-self.vector[0], -self.vector[1]))
149 def mirrored(self, angle):
150 return mirror(angle)*self
152 def rotated_pt(self, angle, x=None, y=None):
153 return rotate_pt(angle, x, y)*self
155 def rotated(self, angle, x=None, y=None):
156 return rotate(angle, x, y)*self
158 def scaled_pt(self, sx, sy=None, x=None, y=None):
159 return scale_pt(sx, sy, x, y)*self
161 def scaled(self, sx, sy=None, x=None, y=None):
162 return scale(sx, sy, x, y)*self
164 def slanted_pt(self, a, angle=0, x=None, y=None):
165 return slant_pt(a, angle, x, y)*self
167 def slanted(self, a, angle=0, x=None, y=None):
168 return slant(a, angle, x, y)*self
170 def translated_pt(self, x, y):
171 return translate_pt(x,y)*self
173 def translated(self, x, y):
174 return translate(x, y)*self
177 class trafo(trafo_pt):
179 """affine transformation"""
181 def __init__(self, matrix=((1,0),(0,1)), vector=(0,0)):
182 trafo_pt.__init__(self,
183 matrix,
184 (unit.topt(vector[0]), unit.topt(vector[1])))
188 # some standard transformations
191 class mirror(trafo):
192 def __init__(self,angle=0):
193 trafo.__init__(self, matrix=_mmatrix(angle))
196 class rotate_pt(trafo_pt):
197 def __init__(self, angle, x=None, y=None):
198 vector = 0, 0
199 if x is not None or y is not None:
200 if x is None or y is None:
201 raise (UndefinedResultError,
202 "either specify both x and y or none of them")
203 vector=_rvector(angle, x, y)
205 trafo_pt.__init__(self,
206 matrix=_rmatrix(angle),
207 vector=vector)
210 class rotate(trafo_pt):
211 def __init__(self, angle, x=None, y=None):
212 vector = 0, 0
213 if x is not None or y is not None:
214 if x is None or y is None:
215 raise (UndefinedResultError,
216 "either specify both x and y or none of them")
217 vector=_rvector(angle, unit.topt(x), unit.topt(y))
219 trafo_pt.__init__(self,
220 matrix=_rmatrix(angle),
221 vector=vector)
224 class scale_pt(trafo_pt):
225 def __init__(self, sx, sy=None, x=None, y=None):
226 sy = sy or sx
227 if not sx or not sy:
228 raise (UndefinedResultError,
229 "one scaling factor is 0")
230 vector = 0, 0
231 if x is not None or y is not None:
232 if x is None or y is None:
233 raise (UndefinedResultError,
234 "either specify both x and y or none of them")
235 vector = (1-sx)*x, (1-sy)*y
237 trafo_pt.__init__(self, matrix=((sx,0),(0,sy)), vector=vector)
240 class scale(trafo):
241 def __init__(self, sx, sy=None, x=None, y=None):
242 sy = sy or sx
243 if not sx or not sy:
244 raise (UndefinedResultError,
245 "one scaling factor is 0")
246 vector = 0, 0
247 if x is not None or y is not None:
248 if x is None or y is None:
249 raise (UndefinedResultError,
250 "either specify both x and y or none of them")
251 vector = (1-sx)*x, (1-sy)*y
253 trafo.__init__(self, matrix=((sx,0),(0,sy)), vector=vector)
256 class slant_pt(trafo_pt):
257 def __init__(self, a, angle=0, x=None, y=None):
258 t = ( rotate_pt(-angle, x, y)*
259 trafo(matrix=((1, a), (0, 1)))*
260 rotate_pt(angle, x, y) )
261 trafo_pt.__init__(self, t.matrix, t.vector)
264 class slant(trafo):
265 def __init__(self, a, angle=0, x=None, y=None):
266 t = ( rotate(-angle, x, y)*
267 trafo(matrix=((1, a), (0, 1)))*
268 rotate(angle, x, y) )
269 trafo.__init__(self, t.matrix, t.vector)
272 class translate_pt(trafo_pt):
273 def __init__(self, x, y):
274 trafo_pt.__init__(self, vector=(x, y))
277 class translate(trafo):
278 def __init__(self, x, y):
279 trafo.__init__(self, vector=(x, y))