PSCmd and PSOp are now joined in a new class canvasitem
[PyX/mjg.git] / pyx / trafo.py
blobfa965fcf8b36a05d23e1e34fdbc61404c4e1327f
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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
24 import math
25 import attr, base, 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(base.canvasitem, 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_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):
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):
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 inverse(self):
136 det = _det(self.matrix) # shouldn't be zero, but
137 try:
138 matrix = ( ( self.matrix[1][1]/det, -self.matrix[0][1]/det),
139 (-self.matrix[1][0]/det, self.matrix[0][0]/det)
141 except ZeroDivisionError:
142 raise UndefinedResultError, "transformation matrix must not be singular"
143 return trafo_pt(matrix=matrix) * \
144 trafo_pt(vector=(-self.vector[0], -self.vector[1]))
146 def mirrored(self, angle):
147 return mirror(angle)*self
149 def rotated_pt(self, angle, x=None, y=None):
150 return rotate_pt(angle, x, y)*self
152 def rotated(self, angle, x=None, y=None):
153 return rotate(angle, x, y)*self
155 def scaled_pt(self, sx, sy=None, x=None, y=None):
156 return scale_pt(sx, sy, x, y)*self
158 def scaled(self, sx, sy=None, x=None, y=None):
159 return scale(sx, sy, x, y)*self
161 def slanted_pt(self, a, angle=0, x=None, y=None):
162 return slant_pt(a, angle, x, y)*self
164 def slanted(self, a, angle=0, x=None, y=None):
165 return slant(a, angle, x, y)*self
167 def translated_pt(self, x, y):
168 return translate_pt(x,y)*self
170 def translated(self, x, y):
171 return translate(x, y)*self
174 class trafo(trafo_pt):
176 """affine transformation"""
178 def __init__(self, matrix=((1,0),(0,1)), vector=(0,0)):
179 trafo_pt.__init__(self,
180 matrix,
181 (unit.topt(vector[0]), unit.topt(vector[1])))
185 # some standard transformations
188 class mirror(trafo):
189 def __init__(self,angle=0):
190 trafo.__init__(self, matrix=_mmatrix(angle))
193 class rotate_pt(trafo_pt):
194 def __init__(self, angle, x=None, y=None):
195 vector = 0, 0
196 if x is not None or y is not None:
197 if x is None or y is None:
198 raise (UndefinedResultError,
199 "either specify both x and y or none of them")
200 vector=_rvector(angle, x, y)
202 trafo_pt.__init__(self,
203 matrix=_rmatrix(angle),
204 vector=vector)
207 class rotate(trafo_pt):
208 def __init__(self, angle, x=None, y=None):
209 vector = 0, 0
210 if x is not None or y is not None:
211 if x is None or y is None:
212 raise (UndefinedResultError,
213 "either specify both x and y or none of them")
214 vector=_rvector(angle, unit.topt(x), unit.topt(y))
216 trafo_pt.__init__(self,
217 matrix=_rmatrix(angle),
218 vector=vector)
221 class scale_pt(trafo_pt):
222 def __init__(self, sx, sy=None, x=None, y=None):
223 sy = sy or sx
224 if not sx or not sy:
225 raise (UndefinedResultError,
226 "one scaling factor is 0")
227 vector = 0, 0
228 if x is not None or y is not None:
229 if x is None or y is None:
230 raise (UndefinedResultError,
231 "either specify both x and y or none of them")
232 vector = (1-sx)*x, (1-sy)*y
234 trafo_pt.__init__(self, matrix=((sx,0),(0,sy)), vector=vector)
237 class scale(trafo):
238 def __init__(self, sx, sy=None, x=None, y=None):
239 sy = sy or sx
240 if not sx or not sy:
241 raise (UndefinedResultError,
242 "one scaling factor is 0")
243 vector = 0, 0
244 if x is not None or y is not None:
245 if x is None or y is None:
246 raise (UndefinedResultError,
247 "either specify both x and y or none of them")
248 vector = (1-sx)*x, (1-sy)*y
250 trafo.__init__(self, matrix=((sx,0),(0,sy)), vector=vector)
253 class slant_pt(trafo_pt):
254 def __init__(self, a, angle=0, x=None, y=None):
255 t = ( rotate_pt(-angle, x, y)*
256 trafo(matrix=((1, a), (0, 1)))*
257 rotate_pt(angle, x, y) )
258 trafo_pt.__init__(self, t.matrix, t.vector)
261 class slant(trafo):
262 def __init__(self, a, angle=0, x=None, y=None):
263 t = ( rotate(-angle, x, y)*
264 trafo(matrix=((1, a), (0, 1)))*
265 rotate(angle, x, y) )
266 trafo.__init__(self, t.matrix, t.vector)
269 class translate_pt(trafo_pt):
270 def __init__(self, x, y):
271 trafo_pt.__init__(self, vector=(x, y))
274 class translate(trafo):
275 def __init__(self, x, y):
276 trafo.__init__(self, vector=(x, y))