make all parts of the manual compile again; parts of the manual are still out of...
[PyX/mjg.git] / pyx / trafo.py
blobcb7a0fab99434520d98db228586324ddb229f804
1 #!/usr/bin/env python
2 # -*- coding: ISO-8859-1 -*-
5 # Copyright (C) 2002 Jörg Lehmann <joergl@users.sourceforge.net>
6 # Copyright (C) 2002 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
24 import math
25 import attr, base, bbox, 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(base.PSOp, attr.attr):
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):
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(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 write(self, file):
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 bbox(self):
113 return bbox._bbox()
115 def _apply(self, x, y):
116 """apply transformation to point (x,y) (coordinates in pts)"""
117 return (self.matrix[0][0]*x +
118 self.matrix[0][1]*y +
119 self.vector[0],
120 self.matrix[1][0]*x +
121 self.matrix[1][1]*y +
122 self.vector[1])
124 def apply(self, x, y):
125 # before the transformation, we first have to convert to
126 # our internal unit (i.e. pts)
127 tx, ty = self._apply(unit.topt(x), unit.topt(y))
129 # the end result can be converted back to general lengths
130 return (unit.t_pt(tx), unit.t_pt(ty))
132 def inverse(self):
133 det = _det(self.matrix) # shouldn't be zero, but
134 try:
135 matrix = ( ( self.matrix[1][1]/det, -self.matrix[0][1]/det),
136 (-self.matrix[1][0]/det, self.matrix[0][0]/det)
138 except ZeroDivisionError:
139 raise UndefinedResultError, "transformation matrix must not be singular"
140 return _trafo(matrix=matrix) * \
141 _trafo(vector=(-self.vector[0], -self.vector[1]))
143 def mirrored(self, angle):
144 return mirror(angle)*self
146 def _rotated(self, angle, x=None, y=None):
147 return _rotate(angle, x, y)*self
149 def rotated(self, angle, x=None, y=None):
150 return rotate(angle, x, y)*self
152 def _scaled(self, sx, sy=None, x=None, y=None):
153 return _scale(sx, sy, x, y)*self
155 def scaled(self, sx, sy=None, x=None, y=None):
156 return scale(sx, sy, x, y)*self
158 def _slanted(self, a, angle=0, x=None, y=None):
159 return _slant(a, angle, x, y)*self
161 def slanted(self, a, angle=0, x=None, y=None):
162 return slant(a, angle, x, y)*self
164 def _translated(self, x, y):
165 return _translate(x,y)*self
167 def translated(self, x, y):
168 return translate(x, y)*self
171 class trafo(_trafo):
173 """affine transformation"""
175 def __init__(self, matrix=((1,0),(0,1)), vector=(0,0)):
176 _trafo.__init__(self,
177 matrix,
178 (unit.topt(vector[0]), unit.topt(vector[1])))
182 # some standard transformations
185 class mirror(trafo):
186 def __init__(self,angle=0):
187 trafo.__init__(self, matrix=_mmatrix(angle))
190 class _rotate(_trafo):
191 def __init__(self, angle, x=None, y=None):
192 vector = 0, 0
193 if x is not None or y is not None:
194 if x is None or y is None:
195 raise (UndefinedResultError,
196 "either specify both x and y or none of them")
197 vector=_rvector(angle, x, y)
199 _trafo.__init__(self,
200 matrix=_rmatrix(angle),
201 vector=vector)
204 class rotate(_trafo):
205 def __init__(self, angle, x=None, y=None):
206 vector = 0, 0
207 if x is not None or y is not None:
208 if x is None or y is None:
209 raise (UndefinedResultError,
210 "either specify both x and y or none of them")
211 vector=_rvector(angle, unit.topt(x), unit.topt(y))
213 _trafo.__init__(self,
214 matrix=_rmatrix(angle),
215 vector=vector)
218 class _scale(_trafo):
219 def __init__(self, sx, sy=None, x=None, y=None):
220 sy = sy or sx
221 if not sx or not sy:
222 raise (UndefinedResultError,
223 "one scaling factor is 0")
224 vector = 0, 0
225 if x is not None or y is not None:
226 if x is None or y is None:
227 raise (UndefinedResultError,
228 "either specify both x and y or none of them")
229 vector = (1-sx)*x, (1-sy)*y
231 _trafo.__init__(self, matrix=((sx,0),(0,sy)), vector=vector)
234 class scale(trafo):
235 def __init__(self, sx, sy=None, x=None, y=None):
236 sy = sy or sx
237 if not sx or not sy:
238 raise (UndefinedResultError,
239 "one scaling factor is 0")
240 vector = 0, 0
241 if x is not None or y is not None:
242 if x is None or y is None:
243 raise (UndefinedResultError,
244 "either specify both x and y or none of them")
245 vector = (1-sx)*x, (1-sy)*y
247 trafo.__init__(self, matrix=((sx,0),(0,sy)), vector=vector)
250 class _slant(_trafo):
251 def __init__(self, a, angle=0, x=None, y=None):
252 t = ( _rotate(-angle, x, y)*
253 trafo(matrix=((1, a), (0, 1)))*
254 _rotate(angle, x, y) )
255 _trafo.__init__(self, t.matrix, t.vector)
258 class slant(trafo):
259 def __init__(self, a, angle=0, x=None, y=None):
260 t = ( rotate(-angle, x, y)*
261 trafo(matrix=((1, a), (0, 1)))*
262 rotate(angle, x, y) )
263 trafo.__init__(self, t.matrix, t.vector)
266 class _translate(_trafo):
267 def __init__(self, x, y):
268 _trafo.__init__(self, vector=(x, y))
271 class translate(trafo):
272 def __init__(self, x, y):
273 trafo.__init__(self, vector=(x, y))