basically finished all documentation; work on examples
[PyX/mjg.git] / pyx / trafo.py
blobd32cd8233f4fb5b015d30aa1e44c90f26ddcaf2a
1 #!/usr/bin/env python
4 # Copyright (C) 2002 Jörg Lehmann <joergl@users.sourceforge.net>
5 # Copyright (C) 2002 André Wobst <wobsta@users.sourceforge.net>
7 # This file is part of PyX (http://pyx.sourceforge.net/).
9 # PyX is free software; you can redistribute it and/or modify
10 # it under the terms of the GNU General Public License as published by
11 # the Free Software Foundation; either version 2 of the License, or
12 # (at your option) any later version.
14 # PyX is distributed in the hope that it will be useful,
15 # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 # GNU General Public License for more details.
19 # You should have received a copy of the GNU General Public License
20 # along with PyX; if not, write to the Free Software
21 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
23 import math
24 import base, bbox, unit
26 # some helper routines
28 def _rmatrix(angle):
29 phi = math.pi*angle/180.0
31 return ((math.cos(phi), -math.sin(phi)),
32 (math.sin(phi), math.cos(phi)))
34 def _rvector(angle, x, y):
35 phi = math.pi*angle/180.0
37 return ((1-math.cos(phi))*x + math.sin(phi) *y,
38 -math.sin(phi) *x + (1-math.cos(phi))*y)
41 def _mmatrix(angle):
42 phi = math.pi*angle/180.0
44 return ( (math.cos(phi)*math.cos(phi)-math.sin(phi)*math.sin(phi),
45 -2*math.sin(phi)*math.cos(phi) ),
46 (-2*math.sin(phi)*math.cos(phi),
47 math.sin(phi)*math.sin(phi)-math.cos(phi)*math.cos(phi) ) )
49 def _det(matrix):
50 return matrix[0][0]*matrix[1][1] - matrix[0][1]*matrix[1][0]
52 # Exception
54 class UndefinedResultError(ArithmeticError):
55 pass
57 # trafo: affine transformations
59 class _trafo(base.PSOp):
61 """affine transformation (coordinates in constructor in pts)
63 Note that though the coordinates in the constructor are in
64 pts (which is useful for internal purposes), all other
65 methods only accept units in the standard user notation.
67 """
69 def __init__(self, matrix=((1,0),(0,1)), vector=(0,0)):
70 if _det(matrix)==0:
71 raise UndefinedResultError, "transformation matrix must not be singular"
72 else:
73 self.matrix=matrix
74 self.vector = vector
76 def __mul__(self, other):
77 if isinstance(other, _trafo):
78 matrix = ( ( self.matrix[0][0]*other.matrix[0][0] +
79 self.matrix[0][1]*other.matrix[1][0],
80 self.matrix[0][0]*other.matrix[0][1] +
81 self.matrix[0][1]*other.matrix[1][1] ),
82 ( self.matrix[1][0]*other.matrix[0][0] +
83 self.matrix[1][1]*other.matrix[1][0],
84 self.matrix[1][0]*other.matrix[0][1] +
85 self.matrix[1][1]*other.matrix[1][1] )
88 vector = ( self.matrix[0][0]*other.vector[0] +
89 self.matrix[0][1]*other.vector[1] +
90 self.vector[0],
91 self.matrix[1][0]*other.vector[0] +
92 self.matrix[1][1]*other.vector[1] +
93 self.vector[1] )
95 return _trafo(matrix=matrix, vector=vector)
96 else:
97 raise NotImplementedError, "can only multiply two transformations"
99 def __str__(self):
100 return "[%f %f %f %f %f %f]" % \
101 ( self.matrix[0][0], self.matrix[1][0],
102 self.matrix[0][1], self.matrix[1][1],
103 self.vector[0], self.vector[1] )
105 def write(self, file):
106 file.write("[%f %f %f %f %f %f] concat\n" % \
107 ( self.matrix[0][0], self.matrix[1][0],
108 self.matrix[0][1], self.matrix[1][1],
109 self.vector[0], self.vector[1] ) )
111 def bbox(self):
112 return bbox.bbox()
114 def _apply(self, x, y):
115 """apply transformation to point (x,y) (coordinates in pts)"""
116 return (self.matrix[0][0]*x +
117 self.matrix[0][1]*y +
118 self.vector[0],
119 self.matrix[1][0]*x +
120 self.matrix[1][1]*y +
121 self.vector[1])
123 def apply(self, x, y):
124 # before the transformation, we first have to convert to
125 # our internal unit (i.e. pts)
126 tx, ty = self._apply(unit.topt(x), unit.topt(y))
128 # the end result can be converted back to general lengths
129 return (unit.t_pt(tx), unit.t_pt(ty))
131 def inverse(self):
132 det = _det(self.matrix) # shouldn't be zero, but
133 try:
134 matrix = ( ( self.matrix[1][1]/det, -self.matrix[0][1]/det),
135 (-self.matrix[1][0]/det, self.matrix[0][0]/det)
137 except ZeroDivisionError:
138 raise UndefinedResultError, "transformation matrix must not be singular"
139 return _trafo(matrix=matrix) * \
140 _trafo(vector=(-self.vector[0], -self.vector[1]))
142 def mirrored(self, angle):
143 return mirror(angle)*self
145 def _rotated(self, angle, x=None, y=None):
146 return _rotate(angle, x, y)*self
148 def rotated(self, angle, x=None, y=None):
149 return rotate(angle, x, y)*self
151 def _scaled(self, sx, sy=None, x=None, y=None):
152 return _scale(sx, sy, x, y)*self
154 def scaled(self, sx, sy=None, x=None, y=None):
155 return scale(sx, sy, x, y)*self
157 def _slanted(self, a, angle=0, x=None, y=None):
158 return _slanted(a, angle, x, y)*self
160 def slanted(self, a, angle=0, x=None, y=None):
161 return slanted(a, angle, x, y)*self
163 def _translated(self, x, y):
164 return _translate(x,y)*self
166 def translated(self, x, y):
167 return translate(x, y)*self
170 class trafo(_trafo):
172 """affine transformation"""
174 def __init__(self, matrix=((1,0),(0,1)), vector=(0,0)):
175 _trafo.__init__(self,
176 matrix,
177 (unit.topt(vector[0]), unit.topt(vector[1])))
181 # some standard transformations
184 class mirror(trafo):
185 def __init__(self,angle=0):
186 trafo.__init__(self, matrix=_mmatrix(angle))
189 class _rotate(_trafo):
190 def __init__(self, angle, x=None, y=None):
191 vector = 0, 0
192 if x is not None or y is not None:
193 if x is None or y is None:
194 raise (UndefinedResultError,
195 "either specify both x and y or none of them")
196 vector=_rvector(angle, x, y)
198 _trafo.__init__(self,
199 matrix=_rmatrix(angle),
200 vector=vector)
203 class rotate(_trafo):
204 def __init__(self, angle, x=None, y=None):
205 vector = 0, 0
206 if x is not None or y is not None:
207 if x is None or y is None:
208 raise (UndefinedResultError,
209 "either specify both x and y or none of them")
210 vector=_rvector(angle, unit.topt(x), unit.topt(y))
212 _trafo.__init__(self,
213 matrix=_rmatrix(angle),
214 vector=vector)
217 class _scale(_trafo):
218 def __init__(self, sx, sy=None, x=None, y=None):
219 sy = sy or sx
220 if not sx or not sy:
221 raise (UndefinedResultError,
222 "one scaling factor is 0")
223 vector = 0, 0
224 if x is not None or y is not None:
225 if x is None or y is None:
226 raise (UndefinedResultError,
227 "either specify both x and y or none of them")
228 vector = (1-sx)*x, (1-sy)*y
230 _trafo.__init__(self, matrix=((sx,0),(0,sy)), vector=vector)
233 class scale(trafo):
234 def __init__(self, sx, sy=None, x=None, y=None):
235 sy = sy or sx
236 if not sx or not sy:
237 raise (UndefinedResultError,
238 "one scaling factor is 0")
239 vector = 0, 0
240 if x is not None or y is not None:
241 if x is None or y is None:
242 raise (UndefinedResultError,
243 "either specify both x and y or none of them")
244 vector = (1-sx)*x, (1-sy)*y
246 trafo.__init__(self, matrix=((sx,0),(0,sy)), vector=vector)
249 class _slant(_trafo):
250 def __init__(self, a, angle=0, x=None, y=None):
251 t = ( _rotate(-angle, x, y)*
252 trafo(matrix=((1, a), (0, 1)))*
253 _rotate(angle, x, y) )
254 _trafo.__init__(self, t.matrix, t.vector)
257 class slant(trafo):
258 def __init__(self, a, angle=0, x=None, y=None):
259 t = ( rotate(-angle, x, y)*
260 trafo(matrix=((1, a), (0, 1)))*
261 rotate(angle, x, y) )
262 trafo.__init__(self, t.matrix, t.vector)
265 class _translate(_trafo):
266 def __init__(self, x, y):
267 _trafo.__init__(self, vector=(x, y))
270 class translate(trafo):
271 def __init__(self, x, y):
272 trafo.__init__(self, vector=(x, y))