2003-08-22 14:56:48 +00:00
|
|
|
"""Affine 2D transformation class."""
|
|
|
|
|
|
|
|
|
|
|
|
_EPSILON = 1e-15
|
|
|
|
_ONE_EPSILON = 1 - _EPSILON
|
|
|
|
_MINUS_ONE_EPSILON = -1 + _EPSILON
|
|
|
|
|
|
|
|
|
|
|
|
def _normSinCos(v):
|
|
|
|
if abs(v) < _EPSILON:
|
|
|
|
v = 0
|
|
|
|
elif v > _ONE_EPSILON:
|
|
|
|
v = 1
|
|
|
|
elif v < _MINUS_ONE_EPSILON:
|
|
|
|
v = -1
|
|
|
|
return v
|
|
|
|
|
|
|
|
|
|
|
|
class Transform:
|
2003-08-24 16:17:11 +00:00
|
|
|
|
2003-08-22 14:56:48 +00:00
|
|
|
"""2x2 transformation matrix plus offset, a.k.a. Affine transform.
|
|
|
|
Transform instances are "immutable": all transforming methods, eg.
|
|
|
|
rotate(), return a new Transform instance."""
|
2003-08-24 16:17:11 +00:00
|
|
|
|
2003-08-22 14:56:48 +00:00
|
|
|
def __init__(self, xx=1, xy=0, yx=0, yy=1, dx=0, dy=0):
|
|
|
|
self.__affine = xx, xy, yx, yy, dx, dy
|
2003-08-24 16:17:11 +00:00
|
|
|
|
2003-08-22 14:56:48 +00:00
|
|
|
def transformPoint(self, (x, y)):
|
|
|
|
"""Transform a point."""
|
|
|
|
xx, xy, yx, yy, dx, dy = self.__affine
|
|
|
|
return (xx*x + yx*y + dx, xy*x + yy*y + dy)
|
2003-08-24 16:17:11 +00:00
|
|
|
|
2003-08-22 14:56:48 +00:00
|
|
|
def translate(self, x=0, y=0):
|
|
|
|
return self.transform((1, 0, 0, 1, x, y))
|
2003-08-24 16:17:11 +00:00
|
|
|
|
2003-08-22 14:56:48 +00:00
|
|
|
def scale(self, x=1, y=None):
|
|
|
|
if y is None:
|
|
|
|
y = x
|
|
|
|
return self.transform((x, 0, 0, y, 0, 0))
|
2003-08-24 16:17:11 +00:00
|
|
|
|
2003-08-22 14:56:48 +00:00
|
|
|
def rotate(self, angle):
|
|
|
|
import math
|
|
|
|
c = _normSinCos(math.cos(angle))
|
|
|
|
s = _normSinCos(math.sin(angle))
|
|
|
|
return self.transform((c, s, -s, c, 0, 0))
|
2003-08-24 16:17:11 +00:00
|
|
|
|
2003-08-22 14:56:48 +00:00
|
|
|
def skew(self, x=0, y=0):
|
|
|
|
import math
|
|
|
|
return self.transform((1, math.tan(y), math.tan(x), 1, 0, 0))
|
2003-08-24 16:17:11 +00:00
|
|
|
|
2003-08-22 14:56:48 +00:00
|
|
|
def transform(self, other):
|
|
|
|
xx1, xy1, yx1, yy1, dx1, dy1 = other
|
|
|
|
xx2, xy2, yx2, yy2, dx2, dy2 = self.__affine
|
|
|
|
return self.__class__(
|
|
|
|
xx1*xx2 + xy1*yx2,
|
|
|
|
xx1*xy2 + xy1*yy2,
|
|
|
|
yx1*xx2 + yy1*yx2,
|
|
|
|
yx1*xy2 + yy1*yy2,
|
|
|
|
xx2*dx1 + yx2*dy1 + dx2,
|
|
|
|
xy2*dx1 + yy2*dy1 + dy2)
|
2003-08-24 16:17:11 +00:00
|
|
|
|
2003-08-22 14:56:48 +00:00
|
|
|
def reverseTransform(self, other):
|
|
|
|
xx1, xy1, yx1, yy1, dx1, dy1 = self.__affine
|
|
|
|
xx2, xy2, yx2, yy2, dx2, dy2 = other
|
|
|
|
return self.__class__(
|
|
|
|
xx1*xx2 + xy1*yx2,
|
|
|
|
xx1*xy2 + xy1*yy2,
|
|
|
|
yx1*xx2 + yy1*yx2,
|
|
|
|
yx1*xy2 + yy1*yy2,
|
|
|
|
xx2*dx1 + yx2*dy1 + dx2,
|
|
|
|
xy2*dx1 + yy2*dy1 + dy2)
|
2003-08-24 16:17:11 +00:00
|
|
|
|
2003-08-22 14:56:48 +00:00
|
|
|
def inverse(self):
|
|
|
|
"Return the inverse transform."
|
|
|
|
if self.__affine == (1, 0, 0, 1, 0, 0):
|
|
|
|
return self
|
|
|
|
xx, xy, yx, yy, dx, dy = self.__affine
|
|
|
|
det = float(xx*yy - yx*xy)
|
|
|
|
xx, xy, yx, yy = yy/det, -xy/det, -yx/det, xx/det
|
|
|
|
dx, dy = -xx*dx - yx*dy, -xy*dx - yy*dy
|
|
|
|
return self.__class__(xx, xy, yx, yy, dx, dy)
|
2003-08-24 16:17:11 +00:00
|
|
|
|
2003-08-22 14:56:48 +00:00
|
|
|
def toPS(self):
|
|
|
|
return "[%s %s %s %s %s %s]" % self.__affine
|
2003-08-24 16:17:11 +00:00
|
|
|
|
2003-08-22 14:56:48 +00:00
|
|
|
def __len__(self):
|
|
|
|
return 6
|
2003-08-24 16:17:11 +00:00
|
|
|
|
2003-08-22 14:56:48 +00:00
|
|
|
def __getitem__(self, index):
|
|
|
|
return self.__affine[index]
|
2003-08-24 16:17:11 +00:00
|
|
|
|
2003-08-22 14:56:48 +00:00
|
|
|
def __getslice__(self, i, j):
|
|
|
|
return self.__affine[i:j]
|
2003-08-24 16:17:11 +00:00
|
|
|
|
2003-08-22 14:56:48 +00:00
|
|
|
def __cmp__(self, other):
|
|
|
|
xx1, xy1, yx1, yy1, dx1, dy1 = self.__affine
|
|
|
|
xx2, xy2, yx2, yy2, dx2, dy2 = other
|
|
|
|
return cmp((xx1, xy1, yx1, yy1, dx1, dy1),
|
|
|
|
(xx2, xy2, yx2, yy2, dx2, dy2))
|
2003-08-24 16:17:11 +00:00
|
|
|
|
2003-08-22 14:56:48 +00:00
|
|
|
def __hash__(self):
|
|
|
|
return hash(self.__affine)
|
2003-08-24 16:17:11 +00:00
|
|
|
|
2003-08-22 14:56:48 +00:00
|
|
|
def __repr__(self):
|
|
|
|
return "<%s [%s %s %s %s %s %s]>" % ((self.__class__.__name__,)
|
|
|
|
+ tuple(map(str, self.__affine)))
|
|
|
|
|
|
|
|
|
|
|
|
Identity = Transform()
|
|
|
|
|
|
|
|
def Offset(x=0, y=0):
|
|
|
|
return Transform(1, 0, 0, 1, x, y)
|
|
|
|
|
|
|
|
def Scale(x, y=None):
|
|
|
|
if y is None:
|
|
|
|
y = x
|
|
|
|
return Transform(x, 0, 0, y, 0, 0)
|