add a share dir todo
[PyX.git] / pyx / trafo.py
blob990114165896a534bf640bbb3c97f0c35a177a92
1 #!/usr/bin/env python
2 # -*- coding: ISO-8859-1 -*-
5 # Copyright (C) 2002-2006 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
26 import bbox as bboxmodule
28 # global epsilon (used to judge whether a matrix is singular)
29 _epsilon = (1e-5)**2
31 def set(epsilon=None):
32 global _epsilon
33 if epsilon is not None:
34 _epsilon = epsilon
37 # some helper routines
39 def _rmatrix(angle):
40 phi = math.pi*angle/180.0
42 return ((math.cos(phi), -math.sin(phi)),
43 (math.sin(phi), math.cos(phi)))
45 def _rvector(angle, x, y):
46 phi = math.pi*angle/180.0
48 return ((1-math.cos(phi))*x + math.sin(phi) *y,
49 -math.sin(phi) *x + (1-math.cos(phi))*y)
52 def _mmatrix(angle):
53 phi = math.pi*angle/180.0
55 return ( (math.cos(phi)*math.cos(phi)-math.sin(phi)*math.sin(phi),
56 -2*math.sin(phi)*math.cos(phi) ),
57 (-2*math.sin(phi)*math.cos(phi),
58 math.sin(phi)*math.sin(phi)-math.cos(phi)*math.cos(phi) ) )
60 class _marker: pass
62 # Exception
64 class TrafoException(Exception):
65 pass
67 # trafo: affine transformations
69 class trafo_pt(canvas.canvasitem, deformer.deformer):
71 """affine transformation (coordinates in constructor in pts)
73 Note that though the coordinates in the constructor are in
74 pts (which is useful for internal purposes), all other
75 methods only accept units in the standard user notation.
77 """
79 def __init__(self, matrix=((1, 0), (0, 1)), vector=(0, 0), epsilon=_marker):
80 """Return trafo with given transformation matrix and vector. If epsilon
81 is passed it is used instead of the global epsilon defined in the module to
82 check whether the matrix is singular or not. Use epsilon=None to turn of this
83 checking.
84 """
85 if epsilon is _marker:
86 epsilon = _epsilon
87 self.epsilon = epsilon
88 if epsilon is not None and abs(matrix[0][0]*matrix[1][1] - matrix[0][1]*matrix[1][0]) < epsilon:
89 raise TrafoException("transformation matrix must not be singular")
90 else:
91 self.matrix = matrix
92 self.vector = vector
94 def __mul__(self, other):
95 if isinstance(other, trafo_pt):
96 if self.epsilon is None or other.epsilon is None:
97 epsilon = None
98 elif self.epsilon <= other.epsilon:
99 epsilon = self.epsilon
100 else:
101 epsilon = other.epsilon
102 matrix = ( ( self.matrix[0][0]*other.matrix[0][0] +
103 self.matrix[0][1]*other.matrix[1][0],
104 self.matrix[0][0]*other.matrix[0][1] +
105 self.matrix[0][1]*other.matrix[1][1] ),
106 ( self.matrix[1][0]*other.matrix[0][0] +
107 self.matrix[1][1]*other.matrix[1][0],
108 self.matrix[1][0]*other.matrix[0][1] +
109 self.matrix[1][1]*other.matrix[1][1] )
112 vector = ( self.matrix[0][0]*other.vector[0] +
113 self.matrix[0][1]*other.vector[1] +
114 self.vector[0],
115 self.matrix[1][0]*other.vector[0] +
116 self.matrix[1][1]*other.vector[1] +
117 self.vector[1] )
119 return trafo_pt(matrix=matrix, vector=vector, epsilon=epsilon)
120 else:
121 raise NotImplementedError("can only multiply two transformations")
123 def __str__(self):
124 return "[%f %f %f %f %f %f]" % \
125 ( self.matrix[0][0], self.matrix[1][0],
126 self.matrix[0][1], self.matrix[1][1],
127 self.vector[0], self.vector[1] )
129 def processPS(self, file, writer, context, registry, bbox):
130 file.write("[%f %f %f %f %f %f] concat\n" % \
131 ( self.matrix[0][0], self.matrix[1][0],
132 self.matrix[0][1], self.matrix[1][1],
133 self.vector[0], self.vector[1] ) )
135 def processPDF(self, file, writer, context, registry, bbox):
136 file.write("%f %f %f %f %f %f cm\n" % \
137 ( self.matrix[0][0], self.matrix[1][0],
138 self.matrix[0][1], self.matrix[1][1],
139 self.vector[0], self.vector[1] ) )
141 def bbox(self):
142 return bboxmodule.empty()
144 def apply_pt(self, x_pt, y_pt):
145 """apply transformation to point (x_pt, y_pt) in pts"""
146 return ( self.matrix[0][0]*x_pt + self.matrix[0][1]*y_pt + self.vector[0],
147 self.matrix[1][0]*x_pt + self.matrix[1][1]*y_pt + self.vector[1] )
149 def apply(self, x, y):
150 # for the transformation we have to convert to points
151 tx, ty = self.apply_pt(unit.topt(x), unit.topt(y))
152 return tx * unit.t_pt, ty * unit.t_pt
154 def deform(self, path):
155 return path.transformed(self)
157 def inverse(self):
158 det = 1.0*(self.matrix[0][0]*self.matrix[1][1] - self.matrix[0][1]*self.matrix[1][0])
159 matrix = ( ( self.matrix[1][1]/det, -self.matrix[0][1]/det),
160 (-self.matrix[1][0]/det, self.matrix[0][0]/det) )
161 return ( trafo_pt(matrix=matrix, epsilon=self.epsilon) *
162 trafo_pt(vector=(-self.vector[0], -self.vector[1]), epsilon=self.epsilon) )
164 def mirrored(self, angle):
165 return mirror(angle, epsilon=self.epsilon) * self
167 def rotated_pt(self, angle, x=None, y=None):
168 return rotate_pt(angle, x, y, epsilon=self.epsilon) * self
170 def rotated(self, angle, x=None, y=None):
171 return rotate(angle, x, y, epsilon=self.epsilon) * self
173 def scaled_pt(self, sx, sy=None, x=None, y=None):
174 return scale_pt(sx, sy, x, y, epsilon=self.epsilon) * self
176 def scaled(self, sx, sy=None, x=None, y=None):
177 return scale(sx, sy, x, y, epsilon=self.epsilon) * self
179 def slanted_pt(self, a, angle=0, x=None, y=None):
180 return slant_pt(a, angle, x, y, epsilon=self.epsilon) * self
182 def slanted(self, a, angle=0, x=None, y=None):
183 return slant(a, angle, x, y, epsilon=self.epsilon) * self
185 def translated_pt(self, x, y):
186 return translate_pt(x, y, epsilon=self.epsilon) * self
188 def translated(self, x, y):
189 return translate(x, y, epsilon=self.epsilon) * self
192 class trafo(trafo_pt):
194 """affine transformation"""
196 def __init__(self, matrix=((1,0), (0,1)), vector=(0, 0), epsilon=_marker):
197 trafo_pt.__init__(self,
198 matrix, (unit.topt(vector[0]), unit.topt(vector[1])),
199 epsilon=epsilon)
202 # some standard transformations
205 class mirror(trafo):
206 def __init__(self, angle=0, epsilon=_marker):
207 trafo.__init__(self, matrix=_mmatrix(angle), epsilon=epsilon)
210 class rotate_pt(trafo_pt):
211 def __init__(self, angle, x=None, y=None, epsilon=_marker):
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 TrafoException("either specify both x and y or none of them")
216 vector=_rvector(angle, x, y)
218 trafo_pt.__init__(self, matrix=_rmatrix(angle), vector=vector, epsilon=epsilon)
221 class rotate(trafo_pt):
222 def __init__(self, angle, x=None, y=None, epsilon=_marker):
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 TrafoException("either specify both x and y or none of them")
227 vector=_rvector(angle, unit.topt(x), unit.topt(y))
229 trafo_pt.__init__(self, matrix=_rmatrix(angle), vector=vector, epsilon=epsilon)
232 class scale_pt(trafo_pt):
233 def __init__(self, sx, sy=None, x=None, y=None, epsilon=_marker):
234 if sy is None:
235 sy = sx
236 vector = 0, 0
237 if x is not None or y is not None:
238 if x is None or y is None:
239 raise TrafoException("either specify both x and y or none of them")
240 vector = (1-sx)*x, (1-sy)*y
241 trafo_pt.__init__(self, matrix=((sx, 0), (0, sy)), vector=vector, epsilon=epsilon)
244 class scale(trafo):
245 def __init__(self, sx, sy=None, x=None, y=None, epsilon=_marker):
246 if sy is None:
247 sy = sx
248 vector = 0, 0
249 if x is not None or y is not None:
250 if x is None or y is None:
251 raise TrafoException("either specify both x and y or none of them")
252 vector = (1-sx)*x, (1-sy)*y
253 trafo.__init__(self, matrix=((sx, 0), (0, sy)), vector=vector, epsilon=epsilon)
256 class slant_pt(trafo_pt):
257 def __init__(self, a, angle=0, x=None, y=None, epsilon=_marker):
258 t = ( rotate_pt(-angle, x, y, epsilon=epsilon) *
259 trafo(matrix=((1, a), (0, 1)), epsilon=epsilon) *
260 rotate_pt(angle, x, y, epsilon=epsilon) )
261 trafo_pt.__init__(self, t.matrix, t.vector, epsilon=epsilon)
264 class slant(trafo):
265 def __init__(self, a, angle=0, x=None, y=None, epsilon=_marker):
266 t = ( rotate(-angle, x, y, epsilon=epsilon) *
267 trafo(matrix=((1, a), (0, 1)), epsilon=epsilon) *
268 rotate(angle, x, y, epsilon=epsilon) )
269 trafo.__init__(self, t.matrix, t.vector, epsilon=epsilon)
272 class translate_pt(trafo_pt):
273 def __init__(self, x, y, epsilon=_marker):
274 trafo_pt.__init__(self, vector=(x, y), epsilon=epsilon)
277 class translate(trafo):
278 def __init__(self, x, y, epsilon=_marker):
279 trafo.__init__(self, vector=(x, y), epsilon=epsilon)