238 lines
6.7 KiB
Python
238 lines
6.7 KiB
Python
# -*- coding: utf-8 -*-
|
|
|
|
"""
|
|
transforms
|
|
"""
|
|
|
|
# imports ####################################################################
|
|
from math import radians, cos, sin, hypot, degrees, atan2, tan
|
|
|
|
from ._common import _Base
|
|
|
|
|
|
# transforms #################################################################
|
|
|
|
class _Transform(_Base):
|
|
"""represents an affine transformation matrix
|
|
|
|
The affine transform that transforms coordinates (x_1, y_1) expressed in space 1 to corrdinates (x2, y2) expressed in space 2, is expressed with 6 floating point values (a, b, c, d, e, f) such that :
|
|
x_2 = a * x_1 + c * y_1 + e
|
|
y_2 = b * x_1 + d * y_1 + f
|
|
|
|
"""
|
|
_state_attributes = ["tag"]
|
|
attributes = []
|
|
|
|
@property
|
|
def tag(self):
|
|
"""the svg tag that represents this transform
|
|
"""
|
|
raise NotImplementedError # this tag is suppsed to be defined in derived classes
|
|
|
|
def project(self, x=0, y=0):
|
|
"""transforms a point using this transform
|
|
|
|
:returns: the transformed point
|
|
"""
|
|
a, b, c, d, e, f = self.abcdef
|
|
return a * x + c * y + e, b * x + d * y + f
|
|
|
|
@property
|
|
def abcdef(self):
|
|
"""
|
|
return the 6 elements a,b,c,d,e,f defining this transformation
|
|
"""
|
|
return
|
|
|
|
def __mul__(self, other):
|
|
sa, sb, sc, sd, se, sf = self.abcdef
|
|
oa, ob, oc, od, oe, of = other.abcdef
|
|
a, c, e = sa * oa + sc * ob, sa * oc + sc * od, sa * oe + sc * of + se
|
|
b, d, f = sb * oa + sd * ob, sb * oc + sd * od, sb * oe + sd * of + sf
|
|
return Matrix(a, b, c, d, e, f)
|
|
|
|
def params(self, error=1e-6):
|
|
"""separate translation, rotation, shear and scale"""
|
|
a, b, c, d, e, f = self.abcdef
|
|
tx, ty = e, f
|
|
|
|
if abs(b * c) < error:
|
|
cosa, sina = 1., 0.
|
|
sx, hy = a, b
|
|
hx, sy = c, d
|
|
else:
|
|
sign = 1. if a * d >= b * c else -1.
|
|
cosa, sina = a + sign * d, b - sign * c
|
|
h = hypot(cosa, sina)
|
|
cosa, sina = cosa / h, sina / h
|
|
sx, hy = a * cosa + b * sina, b * cosa - a * sina
|
|
hx, sy = c * cosa + d * sina, d * cosa - c * sina
|
|
sx -= hx * hy / sy
|
|
return (tx, ty), (cosa, sina), (hx, hy), (sx, sy)
|
|
|
|
def uniform(self):
|
|
"""returns this matrix in the form of a 3x3 matrix for homogen coordinates
|
|
"""
|
|
a, b, c, d, e, f = self.abcdef
|
|
return [(a, b, 0., c, d, 0., e, f, 1.)]
|
|
|
|
def __str__(self):
|
|
return "%s(" % self.tag + \
|
|
",".join(str(getattr(self, a)) for a in self.attributes) + \
|
|
")"
|
|
|
|
|
|
class Translate(_Transform):
|
|
tag = "translate"
|
|
attributes = ["tx", "ty"]
|
|
_state_attributes = _Transform._state_attributes + attributes
|
|
|
|
def __init__(self, tx=0, ty=0):
|
|
self.tx, self.ty = tx, ty
|
|
|
|
def inverse(self):
|
|
return Translate(-self.tx, -self.ty)
|
|
|
|
@property
|
|
def abcdef(self):
|
|
return 1., 0., 0., 1., self.tx, self.ty
|
|
|
|
|
|
class Scale(_Transform):
|
|
tag = "scale"
|
|
attributes = ["sx", "sy"]
|
|
_state_attributes = _Transform._state_attributes + attributes
|
|
|
|
def __init__(self, sx=1., sy=None):
|
|
self.sx = sx
|
|
self.sy = sy or sx
|
|
|
|
def inverse(self):
|
|
return Scale(1. / self.sx, 1. / self.sy)
|
|
|
|
@property
|
|
def abcdef(self):
|
|
return self.sx, 0., 0., self.sy, 0., 0.
|
|
|
|
|
|
class Rotate(_Transform):
|
|
tag = "rotate"
|
|
attributes = ["a", "cx", "cy"]
|
|
_state_attributes = _Transform._state_attributes + attributes
|
|
|
|
def __init__(self, a=0, cx=0, cy=0):
|
|
self.a = a
|
|
self.cx, self.cy = cx, cy
|
|
|
|
def inverse(self):
|
|
return Rotate(-self.a, self.cx, self.cy)
|
|
|
|
@property
|
|
def abcdef(self):
|
|
a, cx, cy = radians(self.a), self.cx, self.cy
|
|
c, s = cos(a), sin(a)
|
|
return c, s, -s, c, cx * (1. - c) + cy * s, cy * (1. - c) - cx * s
|
|
|
|
|
|
class SkewX(_Transform):
|
|
tag = "skewX"
|
|
attributes = ["ax"]
|
|
_state_attributes = _Transform._state_attributes + attributes
|
|
|
|
def __init__(self, ax=0.):
|
|
self.ax = ax
|
|
|
|
def inverse(self):
|
|
return SkewX(-self.ax)
|
|
|
|
@property
|
|
def abcdef(self):
|
|
t = tan(radians(self.ax))
|
|
return 1., 0., t, 1., 0., 0.
|
|
|
|
|
|
class SkewY(_Transform):
|
|
tag = "skewY"
|
|
attributes = ["ay"]
|
|
_state_attributes = _Transform._state_attributes + attributes
|
|
|
|
def __init__(self, ay=0.):
|
|
self.ay = ay
|
|
|
|
def inverse(self):
|
|
return SkewY(-self.ay)
|
|
|
|
@property
|
|
def abcdef(self):
|
|
t = tan(radians(self.ay))
|
|
return 1., t, 0., 1., 0., 0.
|
|
|
|
|
|
class Matrix(_Transform):
|
|
tag = "matrix"
|
|
attributes = ["a", "b", "c", "d", "e", "f"]
|
|
_state_attributes = _Transform._state_attributes + attributes
|
|
|
|
def __init__(self, a=1., b=0., c=0., d=1., e=0., f=0.):
|
|
self.a, self.c, self.e = a, c, e
|
|
self.b, self.d, self.f = b, d, f
|
|
|
|
def get_abcdef(self):
|
|
return self.a, self.b, self.c, self.d, self.e, self.f
|
|
|
|
def set_abcdef(self, abcdef):
|
|
self.a, self.b, self.c, self.d, self.e, self.f = abcdef
|
|
abcdef = property(get_abcdef, set_abcdef)
|
|
|
|
def inverse(self):
|
|
a, b, c, d, e, f = self.abcdef
|
|
det = a * d - b * c
|
|
return Matrix(*(u / det for u in (d, -b, -c, a, c * f - e * d, b * e - a * f)))
|
|
|
|
|
|
def ortho(left, right, bottom, top):
|
|
width, height = right - left, top - bottom
|
|
a, c, e = 2. / width, 0., -(left + right) / width
|
|
b, d, f = 0., 2. / height, -(bottom + top) / height
|
|
return Matrix(a, b, c, d, e, f)
|
|
|
|
|
|
def stretch(x, y, width, height):
|
|
a, c, e = width, 0., x
|
|
b, d, f = 0., height, y
|
|
return Matrix(a, b, c, d, e, f)
|
|
|
|
|
|
def shrink(x, y, width, height):
|
|
a, c, e = 1. / width, 0., -x / width
|
|
b, d, f = 0., 1. / height, -y / height
|
|
return Matrix(a, b, c, d, e, f)
|
|
|
|
|
|
# helpers ####################################################################
|
|
|
|
def product(p=Matrix(), *qs):
|
|
for q in qs:
|
|
p = p * q
|
|
return p
|
|
|
|
|
|
def _list_from_params(t, r, sk, s, error=1e-6):
|
|
(tx, ty), (cosa, sina), (hx, hy), (sx, sy) = t, r, sk, s
|
|
transforms = []
|
|
if (tx, ty) != (0., 0.):
|
|
transforms.append(Translate(tx, ty))
|
|
if abs(sina) > abs(cosa) * error:
|
|
transforms.append(Rotate(degrees(atan2(sina, cosa))))
|
|
if abs(hx) > abs(sy) * error:
|
|
transforms.append(SkewX(degrees(atan2(hx, sy))))
|
|
if abs(hy) > abs(sx) * error:
|
|
transforms.append(SkewY(degrees(atan2(hy, sx))))
|
|
if any(abs(1. - s) > error for s in (sx, sy)):
|
|
transforms.append(Scale(sx, sy))
|
|
return transforms
|
|
|
|
|
|
def normalized(transform):
|
|
return _list_from_params(*product(*transform).params())
|