small updates to some unit tests
[PyX.git] / pyx / trafo.py
blobb9509502d6af5d13eba992eb5ed2f9da612c3f31
1 # -*- coding: ISO-8859-1 -*-
4 # Copyright (C) 2002-2006 Jörg Lehmann <joergl@users.sourceforge.net>
5 # Copyright (C) 2002-2004 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
23 import math
24 import attr, canvas, deformer, unit
25 import bbox as bboxmodule
27 # global epsilon (used to judge whether a matrix is singular)
28 _epsilon = (1e-5)**2
30 def set(epsilon=None):
31 global _epsilon
32 if epsilon is not None:
33 _epsilon = epsilon
36 # some helper routines
38 def _rmatrix(angle):
39 phi = math.pi*angle/180.0
41 return ((math.cos(phi), -math.sin(phi)),
42 (math.sin(phi), math.cos(phi)))
44 def _rvector(angle, x, y):
45 phi = math.pi*angle/180.0
47 return ((1-math.cos(phi))*x + math.sin(phi) *y,
48 -math.sin(phi) *x + (1-math.cos(phi))*y)
51 def _mmatrix(angle):
52 phi = math.pi*angle/180.0
54 return ( (math.cos(phi)*math.cos(phi)-math.sin(phi)*math.sin(phi),
55 -2*math.sin(phi)*math.cos(phi) ),
56 (-2*math.sin(phi)*math.cos(phi),
57 math.sin(phi)*math.sin(phi)-math.cos(phi)*math.cos(phi) ) )
59 class _marker: pass
61 # Exception
63 class TrafoException(Exception):
64 pass
66 # trafo: affine transformations
68 class trafo_pt(canvas.canvasitem, deformer.deformer):
70 """affine transformation (coordinates in constructor in pts)
72 Note that though the coordinates in the constructor are in
73 pts (which is useful for internal purposes), all other
74 methods only accept units in the standard user notation.
76 """
78 def __init__(self, matrix=((1, 0), (0, 1)), vector=(0, 0), epsilon=_marker):
79 """Return trafo with given transformation matrix and vector. If epsilon
80 is passed it is used instead of the global epsilon defined in the module to
81 check whether the matrix is singular or not. Use epsilon=None to turn of this
82 checking.
83 """
84 if epsilon is _marker:
85 epsilon = _epsilon
86 self.epsilon = epsilon
87 if epsilon is not None and abs(matrix[0][0]*matrix[1][1] - matrix[0][1]*matrix[1][0]) < epsilon:
88 raise TrafoException("transformation matrix must not be singular")
89 else:
90 self.matrix = matrix
91 self.vector = vector
93 def __mul__(self, other):
94 if isinstance(other, trafo_pt):
95 if self.epsilon is None or other.epsilon is None:
96 epsilon = None
97 elif self.epsilon <= other.epsilon:
98 epsilon = self.epsilon
99 else:
100 epsilon = other.epsilon
101 matrix = ( ( self.matrix[0][0]*other.matrix[0][0] +
102 self.matrix[0][1]*other.matrix[1][0],
103 self.matrix[0][0]*other.matrix[0][1] +
104 self.matrix[0][1]*other.matrix[1][1] ),
105 ( self.matrix[1][0]*other.matrix[0][0] +
106 self.matrix[1][1]*other.matrix[1][0],
107 self.matrix[1][0]*other.matrix[0][1] +
108 self.matrix[1][1]*other.matrix[1][1] )
111 vector = ( self.matrix[0][0]*other.vector[0] +
112 self.matrix[0][1]*other.vector[1] +
113 self.vector[0],
114 self.matrix[1][0]*other.vector[0] +
115 self.matrix[1][1]*other.vector[1] +
116 self.vector[1] )
118 return trafo_pt(matrix=matrix, vector=vector, epsilon=epsilon)
119 else:
120 raise NotImplementedError("can only multiply two transformations")
122 def __str__(self):
123 return "[%f %f %f %f %f %f]" % \
124 ( self.matrix[0][0], self.matrix[1][0],
125 self.matrix[0][1], self.matrix[1][1],
126 self.vector[0], self.vector[1] )
128 def processPS(self, file, writer, context, registry, bbox):
129 file.write("[%f %f %f %f %f %f] concat\n" % \
130 ( self.matrix[0][0], self.matrix[1][0],
131 self.matrix[0][1], self.matrix[1][1],
132 self.vector[0], self.vector[1] ) )
134 def processPDF(self, file, writer, context, registry, bbox):
135 file.write("%f %f %f %f %f %f cm\n" % \
136 ( self.matrix[0][0], self.matrix[1][0],
137 self.matrix[0][1], self.matrix[1][1],
138 self.vector[0], self.vector[1] ) )
140 def bbox(self):
141 return bboxmodule.empty()
143 def apply_pt(self, x_pt, y_pt):
144 """apply transformation to point (x_pt, y_pt) in pts"""
145 return ( self.matrix[0][0]*x_pt + self.matrix[0][1]*y_pt + self.vector[0],
146 self.matrix[1][0]*x_pt + self.matrix[1][1]*y_pt + self.vector[1] )
148 def apply(self, x, y):
149 # for the transformation we have to convert to points
150 tx, ty = self.apply_pt(unit.topt(x), unit.topt(y))
151 return tx * unit.t_pt, ty * unit.t_pt
153 def deform(self, path):
154 return path.transformed(self)
156 def inverse(self):
157 det = 1.0*(self.matrix[0][0]*self.matrix[1][1] - self.matrix[0][1]*self.matrix[1][0])
158 matrix = ( ( self.matrix[1][1]/det, -self.matrix[0][1]/det),
159 (-self.matrix[1][0]/det, self.matrix[0][0]/det) )
160 return ( trafo_pt(matrix=matrix, epsilon=self.epsilon) *
161 trafo_pt(vector=(-self.vector[0], -self.vector[1]), epsilon=self.epsilon) )
163 def mirrored(self, angle):
164 return mirror(angle, epsilon=self.epsilon) * self
166 def rotated_pt(self, angle, x=None, y=None):
167 return rotate_pt(angle, x, y, epsilon=self.epsilon) * self
169 def rotated(self, angle, x=None, y=None):
170 return rotate(angle, x, y, epsilon=self.epsilon) * self
172 def scaled_pt(self, sx, sy=None, x=None, y=None):
173 return scale_pt(sx, sy, x, y, epsilon=self.epsilon) * self
175 def scaled(self, sx, sy=None, x=None, y=None):
176 return scale(sx, sy, x, y, epsilon=self.epsilon) * self
178 def slanted_pt(self, a, angle=0, x=None, y=None):
179 return slant_pt(a, angle, x, y, epsilon=self.epsilon) * self
181 def slanted(self, a, angle=0, x=None, y=None):
182 return slant(a, angle, x, y, epsilon=self.epsilon) * self
184 def translated_pt(self, x, y):
185 return translate_pt(x, y, epsilon=self.epsilon) * self
187 def translated(self, x, y):
188 return translate(x, y, epsilon=self.epsilon) * self
191 class trafo(trafo_pt):
193 """affine transformation"""
195 def __init__(self, matrix=((1,0), (0,1)), vector=(0, 0), epsilon=_marker):
196 trafo_pt.__init__(self,
197 matrix, (unit.topt(vector[0]), unit.topt(vector[1])),
198 epsilon=epsilon)
201 # some standard transformations
204 class mirror(trafo):
205 def __init__(self, angle=0, epsilon=_marker):
206 trafo.__init__(self, matrix=_mmatrix(angle), epsilon=epsilon)
209 class rotate_pt(trafo_pt):
210 def __init__(self, angle, x=None, y=None, epsilon=_marker):
211 vector = 0, 0
212 if x is not None or y is not None:
213 if x is None or y is None:
214 raise TrafoException("either specify both x and y or none of them")
215 vector=_rvector(angle, x, y)
217 trafo_pt.__init__(self, matrix=_rmatrix(angle), vector=vector, epsilon=epsilon)
220 class rotate(trafo_pt):
221 def __init__(self, angle, x=None, y=None, epsilon=_marker):
222 vector = 0, 0
223 if x is not None or y is not None:
224 if x is None or y is None:
225 raise TrafoException("either specify both x and y or none of them")
226 vector=_rvector(angle, unit.topt(x), unit.topt(y))
228 trafo_pt.__init__(self, matrix=_rmatrix(angle), vector=vector, epsilon=epsilon)
231 class scale_pt(trafo_pt):
232 def __init__(self, sx, sy=None, x=None, y=None, epsilon=_marker):
233 if sy is None:
234 sy = sx
235 vector = 0, 0
236 if x is not None or y is not None:
237 if x is None or y is None:
238 raise TrafoException("either specify both x and y or none of them")
239 vector = (1-sx)*x, (1-sy)*y
240 trafo_pt.__init__(self, matrix=((sx, 0), (0, sy)), vector=vector, epsilon=epsilon)
243 class scale(trafo):
244 def __init__(self, sx, sy=None, x=None, y=None, epsilon=_marker):
245 if sy is None:
246 sy = sx
247 vector = 0, 0
248 if x is not None or y is not None:
249 if x is None or y is None:
250 raise TrafoException("either specify both x and y or none of them")
251 vector = (1-sx)*x, (1-sy)*y
252 trafo.__init__(self, matrix=((sx, 0), (0, sy)), vector=vector, epsilon=epsilon)
255 class slant_pt(trafo_pt):
256 def __init__(self, a, angle=0, x=None, y=None, epsilon=_marker):
257 t = ( rotate_pt(-angle, x, y, epsilon=epsilon) *
258 trafo(matrix=((1, a), (0, 1)), epsilon=epsilon) *
259 rotate_pt(angle, x, y, epsilon=epsilon) )
260 trafo_pt.__init__(self, t.matrix, t.vector, epsilon=epsilon)
263 class slant(trafo):
264 def __init__(self, a, angle=0, x=None, y=None, epsilon=_marker):
265 t = ( rotate(-angle, x, y, epsilon=epsilon) *
266 trafo(matrix=((1, a), (0, 1)), epsilon=epsilon) *
267 rotate(angle, x, y, epsilon=epsilon) )
268 trafo.__init__(self, t.matrix, t.vector, epsilon=epsilon)
271 class translate_pt(trafo_pt):
272 def __init__(self, x, y, epsilon=_marker):
273 trafo_pt.__init__(self, vector=(x, y), epsilon=epsilon)
276 class translate(trafo):
277 def __init__(self, x, y, epsilon=_marker):
278 trafo.__init__(self, vector=(x, y), epsilon=epsilon)