# -*- 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())