transform.py revision 3a9fd301808f5a8991ca9ac44028d1ecb22d307f
1f184f754862637ad9c3c32952a0756414e2b67a6jvr"""Affine 2D transformation matrix class. 2f184f754862637ad9c3c32952a0756414e2b67a6jvr 3f184f754862637ad9c3c32952a0756414e2b67a6jvrThe Transform class implements various transformation matrix operations, 4f184f754862637ad9c3c32952a0756414e2b67a6jvrboth on the matrix itself, as well as on 2D coordinates. 5f184f754862637ad9c3c32952a0756414e2b67a6jvr 6f184f754862637ad9c3c32952a0756414e2b67a6jvrTransform instances are effectively immutable: all methods that operate on the 7f184f754862637ad9c3c32952a0756414e2b67a6jvrtransformation itself always return a new instance. This has as the 8f184f754862637ad9c3c32952a0756414e2b67a6jvrinteresting side effect that Transform instances are hashable, ie. they can be 9f184f754862637ad9c3c32952a0756414e2b67a6jvrused as dictionary keys. 10f184f754862637ad9c3c32952a0756414e2b67a6jvr 11f184f754862637ad9c3c32952a0756414e2b67a6jvrThis module exports the following symbols: 12f184f754862637ad9c3c32952a0756414e2b67a6jvr 13f184f754862637ad9c3c32952a0756414e2b67a6jvr Transform -- this is the main class 14f184f754862637ad9c3c32952a0756414e2b67a6jvr Identity -- Transform instance set to the identity transformation 15f184f754862637ad9c3c32952a0756414e2b67a6jvr Offset -- Convenience function that returns a translating transformation 16f184f754862637ad9c3c32952a0756414e2b67a6jvr Scale -- Convenience function that returns a scaling transformation 17f184f754862637ad9c3c32952a0756414e2b67a6jvr 18f184f754862637ad9c3c32952a0756414e2b67a6jvrExamples: 19f184f754862637ad9c3c32952a0756414e2b67a6jvr 20f184f754862637ad9c3c32952a0756414e2b67a6jvr >>> t = Transform(2, 0, 0, 3, 0, 0) 21f184f754862637ad9c3c32952a0756414e2b67a6jvr >>> t.transformPoint((100, 100)) 22f184f754862637ad9c3c32952a0756414e2b67a6jvr (200, 300) 23f184f754862637ad9c3c32952a0756414e2b67a6jvr >>> t = Scale(2, 3) 24f184f754862637ad9c3c32952a0756414e2b67a6jvr >>> t.transformPoint((100, 100)) 25f184f754862637ad9c3c32952a0756414e2b67a6jvr (200, 300) 26f184f754862637ad9c3c32952a0756414e2b67a6jvr >>> t.transformPoint((0, 0)) 27f184f754862637ad9c3c32952a0756414e2b67a6jvr (0, 0) 28f184f754862637ad9c3c32952a0756414e2b67a6jvr >>> t = Offset(2, 3) 29f184f754862637ad9c3c32952a0756414e2b67a6jvr >>> t.transformPoint((100, 100)) 30f184f754862637ad9c3c32952a0756414e2b67a6jvr (102, 103) 31f184f754862637ad9c3c32952a0756414e2b67a6jvr >>> t.transformPoint((0, 0)) 32f184f754862637ad9c3c32952a0756414e2b67a6jvr (2, 3) 33f184f754862637ad9c3c32952a0756414e2b67a6jvr >>> t2 = t.scale(0.5) 34f184f754862637ad9c3c32952a0756414e2b67a6jvr >>> t2.transformPoint((100, 100)) 35f184f754862637ad9c3c32952a0756414e2b67a6jvr (52.0, 53.0) 36f184f754862637ad9c3c32952a0756414e2b67a6jvr >>> import math 37f184f754862637ad9c3c32952a0756414e2b67a6jvr >>> t3 = t2.rotate(math.pi / 2) 38f184f754862637ad9c3c32952a0756414e2b67a6jvr >>> t3.transformPoint((0, 0)) 39f184f754862637ad9c3c32952a0756414e2b67a6jvr (2.0, 3.0) 40f184f754862637ad9c3c32952a0756414e2b67a6jvr >>> t3.transformPoint((100, 100)) 41f184f754862637ad9c3c32952a0756414e2b67a6jvr (-48.0, 53.0) 42f184f754862637ad9c3c32952a0756414e2b67a6jvr >>> t = Identity.scale(0.5).translate(100, 200).skew(0.1, 0.2) 43f184f754862637ad9c3c32952a0756414e2b67a6jvr >>> t.transformPoints([(0, 0), (1, 1), (100, 100)]) 44f184f754862637ad9c3c32952a0756414e2b67a6jvr [(50.0, 100.0), (50.550167336042726, 100.60135501775433), (105.01673360427253, 160.13550177543362)] 45f184f754862637ad9c3c32952a0756414e2b67a6jvr >>> 46f184f754862637ad9c3c32952a0756414e2b67a6jvr""" 47f184f754862637ad9c3c32952a0756414e2b67a6jvr 48f184f754862637ad9c3c32952a0756414e2b67a6jvr 49f184f754862637ad9c3c32952a0756414e2b67a6jvr__all__ = ["Transform", "Identity", "Offset", "Scale"] 509e58c90401ef3ce7ed4a85fbda14bbbc564ebf0ejvr 519e58c90401ef3ce7ed4a85fbda14bbbc564ebf0ejvr 529e58c90401ef3ce7ed4a85fbda14bbbc564ebf0ejvr_EPSILON = 1e-15 539e58c90401ef3ce7ed4a85fbda14bbbc564ebf0ejvr_ONE_EPSILON = 1 - _EPSILON 549e58c90401ef3ce7ed4a85fbda14bbbc564ebf0ejvr_MINUS_ONE_EPSILON = -1 + _EPSILON 559e58c90401ef3ce7ed4a85fbda14bbbc564ebf0ejvr 569e58c90401ef3ce7ed4a85fbda14bbbc564ebf0ejvr 579e58c90401ef3ce7ed4a85fbda14bbbc564ebf0ejvrdef _normSinCos(v): 589e58c90401ef3ce7ed4a85fbda14bbbc564ebf0ejvr if abs(v) < _EPSILON: 599e58c90401ef3ce7ed4a85fbda14bbbc564ebf0ejvr v = 0 609e58c90401ef3ce7ed4a85fbda14bbbc564ebf0ejvr elif v > _ONE_EPSILON: 619e58c90401ef3ce7ed4a85fbda14bbbc564ebf0ejvr v = 1 629e58c90401ef3ce7ed4a85fbda14bbbc564ebf0ejvr elif v < _MINUS_ONE_EPSILON: 639e58c90401ef3ce7ed4a85fbda14bbbc564ebf0ejvr v = -1 649e58c90401ef3ce7ed4a85fbda14bbbc564ebf0ejvr return v 659e58c90401ef3ce7ed4a85fbda14bbbc564ebf0ejvr 669e58c90401ef3ce7ed4a85fbda14bbbc564ebf0ejvr 679e58c90401ef3ce7ed4a85fbda14bbbc564ebf0ejvrclass Transform: 686385a4e9e16560212857b5eade6d0015f729a0b3jvr 699e58c90401ef3ce7ed4a85fbda14bbbc564ebf0ejvr """2x2 transformation matrix plus offset, a.k.a. Affine transform. 70f184f754862637ad9c3c32952a0756414e2b67a6jvr Transform instances are immutable: all transforming methods, eg. 71f184f754862637ad9c3c32952a0756414e2b67a6jvr rotate(), return a new Transform instance. 72f184f754862637ad9c3c32952a0756414e2b67a6jvr 73f184f754862637ad9c3c32952a0756414e2b67a6jvr Examples: 74f184f754862637ad9c3c32952a0756414e2b67a6jvr >>> t = Transform() 75f184f754862637ad9c3c32952a0756414e2b67a6jvr >>> t 76f184f754862637ad9c3c32952a0756414e2b67a6jvr <Transform [1 0 0 1 0 0]> 77f184f754862637ad9c3c32952a0756414e2b67a6jvr >>> t.scale(2) 78f184f754862637ad9c3c32952a0756414e2b67a6jvr <Transform [2 0 0 2 0 0]> 79f184f754862637ad9c3c32952a0756414e2b67a6jvr >>> t.scale(2.5, 5.5) 80f184f754862637ad9c3c32952a0756414e2b67a6jvr <Transform [2.5 0.0 0.0 5.5 0 0]> 81f184f754862637ad9c3c32952a0756414e2b67a6jvr >>> 82f184f754862637ad9c3c32952a0756414e2b67a6jvr >>> t.scale(2, 3).transformPoint((100, 100)) 83f184f754862637ad9c3c32952a0756414e2b67a6jvr (200, 300) 84f184f754862637ad9c3c32952a0756414e2b67a6jvr """ 856385a4e9e16560212857b5eade6d0015f729a0b3jvr 869e58c90401ef3ce7ed4a85fbda14bbbc564ebf0ejvr def __init__(self, xx=1, xy=0, yx=0, yy=1, dx=0, dy=0): 87deca398915e92f24fe698e2e7d0e13122f61383ejvr """Transform's constructor takes six arguments, all of which are 88deca398915e92f24fe698e2e7d0e13122f61383ejvr optional, and can be used as keyword arguments: 89deca398915e92f24fe698e2e7d0e13122f61383ejvr >>> Transform(12) 90deca398915e92f24fe698e2e7d0e13122f61383ejvr <Transform [12 0 0 1 0 0]> 91deca398915e92f24fe698e2e7d0e13122f61383ejvr >>> Transform(dx=12) 92deca398915e92f24fe698e2e7d0e13122f61383ejvr <Transform [1 0 0 1 12 0]> 93deca398915e92f24fe698e2e7d0e13122f61383ejvr >>> Transform(yx=12) 94deca398915e92f24fe698e2e7d0e13122f61383ejvr <Transform [1 0 12 1 0 0]> 95deca398915e92f24fe698e2e7d0e13122f61383ejvr >>> 96deca398915e92f24fe698e2e7d0e13122f61383ejvr """ 979e58c90401ef3ce7ed4a85fbda14bbbc564ebf0ejvr self.__affine = xx, xy, yx, yy, dx, dy 986385a4e9e16560212857b5eade6d0015f729a0b3jvr 993a9fd301808f5a8991ca9ac44028d1ecb22d307fBehdad Esfahbod def transformPoint(self, p): 100f184f754862637ad9c3c32952a0756414e2b67a6jvr """Transform a point. 101f184f754862637ad9c3c32952a0756414e2b67a6jvr 102f184f754862637ad9c3c32952a0756414e2b67a6jvr Example: 103f184f754862637ad9c3c32952a0756414e2b67a6jvr >>> t = Transform() 104f184f754862637ad9c3c32952a0756414e2b67a6jvr >>> t = t.scale(2.5, 5.5) 105f184f754862637ad9c3c32952a0756414e2b67a6jvr >>> t.transformPoint((100, 100)) 106f184f754862637ad9c3c32952a0756414e2b67a6jvr (250.0, 550.0) 107f184f754862637ad9c3c32952a0756414e2b67a6jvr """ 1083a9fd301808f5a8991ca9ac44028d1ecb22d307fBehdad Esfahbod (x, y) = p 1099e58c90401ef3ce7ed4a85fbda14bbbc564ebf0ejvr xx, xy, yx, yy, dx, dy = self.__affine 1109e58c90401ef3ce7ed4a85fbda14bbbc564ebf0ejvr return (xx*x + yx*y + dx, xy*x + yy*y + dy) 1116385a4e9e16560212857b5eade6d0015f729a0b3jvr 112f184f754862637ad9c3c32952a0756414e2b67a6jvr def transformPoints(self, points): 113f184f754862637ad9c3c32952a0756414e2b67a6jvr """Transform a list of points. 114f184f754862637ad9c3c32952a0756414e2b67a6jvr 115f184f754862637ad9c3c32952a0756414e2b67a6jvr Example: 116f184f754862637ad9c3c32952a0756414e2b67a6jvr >>> t = Scale(2, 3) 117f184f754862637ad9c3c32952a0756414e2b67a6jvr >>> t.transformPoints([(0, 0), (0, 100), (100, 100), (100, 0)]) 118f184f754862637ad9c3c32952a0756414e2b67a6jvr [(0, 0), (0, 300), (200, 300), (200, 0)] 119f184f754862637ad9c3c32952a0756414e2b67a6jvr >>> 120f184f754862637ad9c3c32952a0756414e2b67a6jvr """ 121f184f754862637ad9c3c32952a0756414e2b67a6jvr xx, xy, yx, yy, dx, dy = self.__affine 122f184f754862637ad9c3c32952a0756414e2b67a6jvr return [(xx*x + yx*y + dx, xy*x + yy*y + dy) for x, y in points] 123f184f754862637ad9c3c32952a0756414e2b67a6jvr 1249e58c90401ef3ce7ed4a85fbda14bbbc564ebf0ejvr def translate(self, x=0, y=0): 125f184f754862637ad9c3c32952a0756414e2b67a6jvr """Return a new transformation, translated (offset) by x, y. 126f184f754862637ad9c3c32952a0756414e2b67a6jvr 127f184f754862637ad9c3c32952a0756414e2b67a6jvr Example: 128f184f754862637ad9c3c32952a0756414e2b67a6jvr >>> t = Transform() 129f184f754862637ad9c3c32952a0756414e2b67a6jvr >>> t.translate(20, 30) 130f184f754862637ad9c3c32952a0756414e2b67a6jvr <Transform [1 0 0 1 20 30]> 131f184f754862637ad9c3c32952a0756414e2b67a6jvr >>> 132f184f754862637ad9c3c32952a0756414e2b67a6jvr """ 1339e58c90401ef3ce7ed4a85fbda14bbbc564ebf0ejvr return self.transform((1, 0, 0, 1, x, y)) 1346385a4e9e16560212857b5eade6d0015f729a0b3jvr 1359e58c90401ef3ce7ed4a85fbda14bbbc564ebf0ejvr def scale(self, x=1, y=None): 136f184f754862637ad9c3c32952a0756414e2b67a6jvr """Return a new transformation, scaled by x, y. The 'y' argument 137f184f754862637ad9c3c32952a0756414e2b67a6jvr may be None, which implies to use the x value for y as well. 138f184f754862637ad9c3c32952a0756414e2b67a6jvr 139f184f754862637ad9c3c32952a0756414e2b67a6jvr Example: 140f184f754862637ad9c3c32952a0756414e2b67a6jvr >>> t = Transform() 141f184f754862637ad9c3c32952a0756414e2b67a6jvr >>> t.scale(5) 142f184f754862637ad9c3c32952a0756414e2b67a6jvr <Transform [5 0 0 5 0 0]> 143f184f754862637ad9c3c32952a0756414e2b67a6jvr >>> t.scale(5, 6) 144f184f754862637ad9c3c32952a0756414e2b67a6jvr <Transform [5 0 0 6 0 0]> 145f184f754862637ad9c3c32952a0756414e2b67a6jvr >>> 146f184f754862637ad9c3c32952a0756414e2b67a6jvr """ 1479e58c90401ef3ce7ed4a85fbda14bbbc564ebf0ejvr if y is None: 1489e58c90401ef3ce7ed4a85fbda14bbbc564ebf0ejvr y = x 1499e58c90401ef3ce7ed4a85fbda14bbbc564ebf0ejvr return self.transform((x, 0, 0, y, 0, 0)) 1506385a4e9e16560212857b5eade6d0015f729a0b3jvr 1519e58c90401ef3ce7ed4a85fbda14bbbc564ebf0ejvr def rotate(self, angle): 152f184f754862637ad9c3c32952a0756414e2b67a6jvr """Return a new transformation, rotated by 'angle' (radians). 153f184f754862637ad9c3c32952a0756414e2b67a6jvr 154f184f754862637ad9c3c32952a0756414e2b67a6jvr Example: 155f184f754862637ad9c3c32952a0756414e2b67a6jvr >>> import math 156f184f754862637ad9c3c32952a0756414e2b67a6jvr >>> t = Transform() 157f184f754862637ad9c3c32952a0756414e2b67a6jvr >>> t.rotate(math.pi / 2) 158f184f754862637ad9c3c32952a0756414e2b67a6jvr <Transform [0 1 -1 0 0 0]> 159f184f754862637ad9c3c32952a0756414e2b67a6jvr >>> 160f184f754862637ad9c3c32952a0756414e2b67a6jvr """ 1619e58c90401ef3ce7ed4a85fbda14bbbc564ebf0ejvr import math 1629e58c90401ef3ce7ed4a85fbda14bbbc564ebf0ejvr c = _normSinCos(math.cos(angle)) 1639e58c90401ef3ce7ed4a85fbda14bbbc564ebf0ejvr s = _normSinCos(math.sin(angle)) 1649e58c90401ef3ce7ed4a85fbda14bbbc564ebf0ejvr return self.transform((c, s, -s, c, 0, 0)) 1656385a4e9e16560212857b5eade6d0015f729a0b3jvr 1669e58c90401ef3ce7ed4a85fbda14bbbc564ebf0ejvr def skew(self, x=0, y=0): 167f184f754862637ad9c3c32952a0756414e2b67a6jvr """Return a new transformation, skewed by x and y. 168f184f754862637ad9c3c32952a0756414e2b67a6jvr 169f184f754862637ad9c3c32952a0756414e2b67a6jvr Example: 170f184f754862637ad9c3c32952a0756414e2b67a6jvr >>> import math 171f184f754862637ad9c3c32952a0756414e2b67a6jvr >>> t = Transform() 172f184f754862637ad9c3c32952a0756414e2b67a6jvr >>> t.skew(math.pi / 4) 173f184f754862637ad9c3c32952a0756414e2b67a6jvr <Transform [1.0 0.0 1.0 1.0 0 0]> 174f184f754862637ad9c3c32952a0756414e2b67a6jvr >>> 175f184f754862637ad9c3c32952a0756414e2b67a6jvr """ 1769e58c90401ef3ce7ed4a85fbda14bbbc564ebf0ejvr import math 1779e58c90401ef3ce7ed4a85fbda14bbbc564ebf0ejvr return self.transform((1, math.tan(y), math.tan(x), 1, 0, 0)) 1786385a4e9e16560212857b5eade6d0015f729a0b3jvr 1799e58c90401ef3ce7ed4a85fbda14bbbc564ebf0ejvr def transform(self, other): 180f184f754862637ad9c3c32952a0756414e2b67a6jvr """Return a new transformation, transformed by another 181f184f754862637ad9c3c32952a0756414e2b67a6jvr transformation. 182f184f754862637ad9c3c32952a0756414e2b67a6jvr 183f184f754862637ad9c3c32952a0756414e2b67a6jvr Example: 184f184f754862637ad9c3c32952a0756414e2b67a6jvr >>> t = Transform(2, 0, 0, 3, 1, 6) 185f184f754862637ad9c3c32952a0756414e2b67a6jvr >>> t.transform((4, 3, 2, 1, 5, 6)) 186f184f754862637ad9c3c32952a0756414e2b67a6jvr <Transform [8 9 4 3 11 24]> 187f184f754862637ad9c3c32952a0756414e2b67a6jvr >>> 188f184f754862637ad9c3c32952a0756414e2b67a6jvr """ 1899e58c90401ef3ce7ed4a85fbda14bbbc564ebf0ejvr xx1, xy1, yx1, yy1, dx1, dy1 = other 1909e58c90401ef3ce7ed4a85fbda14bbbc564ebf0ejvr xx2, xy2, yx2, yy2, dx2, dy2 = self.__affine 1919e58c90401ef3ce7ed4a85fbda14bbbc564ebf0ejvr return self.__class__( 1929e58c90401ef3ce7ed4a85fbda14bbbc564ebf0ejvr xx1*xx2 + xy1*yx2, 1939e58c90401ef3ce7ed4a85fbda14bbbc564ebf0ejvr xx1*xy2 + xy1*yy2, 1949e58c90401ef3ce7ed4a85fbda14bbbc564ebf0ejvr yx1*xx2 + yy1*yx2, 1959e58c90401ef3ce7ed4a85fbda14bbbc564ebf0ejvr yx1*xy2 + yy1*yy2, 1969e58c90401ef3ce7ed4a85fbda14bbbc564ebf0ejvr xx2*dx1 + yx2*dy1 + dx2, 1979e58c90401ef3ce7ed4a85fbda14bbbc564ebf0ejvr xy2*dx1 + yy2*dy1 + dy2) 1986385a4e9e16560212857b5eade6d0015f729a0b3jvr 1999e58c90401ef3ce7ed4a85fbda14bbbc564ebf0ejvr def reverseTransform(self, other): 200f184f754862637ad9c3c32952a0756414e2b67a6jvr """Return a new transformation, which is the other transformation 201f184f754862637ad9c3c32952a0756414e2b67a6jvr transformed by self. self.reverseTransform(other) is equivalent to 202f184f754862637ad9c3c32952a0756414e2b67a6jvr other.transform(self). 203f184f754862637ad9c3c32952a0756414e2b67a6jvr 204f184f754862637ad9c3c32952a0756414e2b67a6jvr Example: 205f184f754862637ad9c3c32952a0756414e2b67a6jvr >>> t = Transform(2, 0, 0, 3, 1, 6) 206f184f754862637ad9c3c32952a0756414e2b67a6jvr >>> t.reverseTransform((4, 3, 2, 1, 5, 6)) 207f184f754862637ad9c3c32952a0756414e2b67a6jvr <Transform [8 6 6 3 21 15]> 208f184f754862637ad9c3c32952a0756414e2b67a6jvr >>> Transform(4, 3, 2, 1, 5, 6).transform((2, 0, 0, 3, 1, 6)) 209f184f754862637ad9c3c32952a0756414e2b67a6jvr <Transform [8 6 6 3 21 15]> 210f184f754862637ad9c3c32952a0756414e2b67a6jvr >>> 211f184f754862637ad9c3c32952a0756414e2b67a6jvr """ 2129e58c90401ef3ce7ed4a85fbda14bbbc564ebf0ejvr xx1, xy1, yx1, yy1, dx1, dy1 = self.__affine 2139e58c90401ef3ce7ed4a85fbda14bbbc564ebf0ejvr xx2, xy2, yx2, yy2, dx2, dy2 = other 2149e58c90401ef3ce7ed4a85fbda14bbbc564ebf0ejvr return self.__class__( 2159e58c90401ef3ce7ed4a85fbda14bbbc564ebf0ejvr xx1*xx2 + xy1*yx2, 2169e58c90401ef3ce7ed4a85fbda14bbbc564ebf0ejvr xx1*xy2 + xy1*yy2, 2179e58c90401ef3ce7ed4a85fbda14bbbc564ebf0ejvr yx1*xx2 + yy1*yx2, 2189e58c90401ef3ce7ed4a85fbda14bbbc564ebf0ejvr yx1*xy2 + yy1*yy2, 2199e58c90401ef3ce7ed4a85fbda14bbbc564ebf0ejvr xx2*dx1 + yx2*dy1 + dx2, 2209e58c90401ef3ce7ed4a85fbda14bbbc564ebf0ejvr xy2*dx1 + yy2*dy1 + dy2) 2216385a4e9e16560212857b5eade6d0015f729a0b3jvr 2229e58c90401ef3ce7ed4a85fbda14bbbc564ebf0ejvr def inverse(self): 223f184f754862637ad9c3c32952a0756414e2b67a6jvr """Return the inverse transformation. 224f184f754862637ad9c3c32952a0756414e2b67a6jvr 225f184f754862637ad9c3c32952a0756414e2b67a6jvr Example: 226deca398915e92f24fe698e2e7d0e13122f61383ejvr >>> t = Identity.translate(2, 3).scale(4, 5) 227deca398915e92f24fe698e2e7d0e13122f61383ejvr >>> t.transformPoint((10, 20)) 228deca398915e92f24fe698e2e7d0e13122f61383ejvr (42, 103) 229deca398915e92f24fe698e2e7d0e13122f61383ejvr >>> it = t.inverse() 230deca398915e92f24fe698e2e7d0e13122f61383ejvr >>> it.transformPoint((42, 103)) 231deca398915e92f24fe698e2e7d0e13122f61383ejvr (10.0, 20.0) 232f184f754862637ad9c3c32952a0756414e2b67a6jvr >>> 233f184f754862637ad9c3c32952a0756414e2b67a6jvr """ 2349e58c90401ef3ce7ed4a85fbda14bbbc564ebf0ejvr if self.__affine == (1, 0, 0, 1, 0, 0): 2359e58c90401ef3ce7ed4a85fbda14bbbc564ebf0ejvr return self 2369e58c90401ef3ce7ed4a85fbda14bbbc564ebf0ejvr xx, xy, yx, yy, dx, dy = self.__affine 2379e58c90401ef3ce7ed4a85fbda14bbbc564ebf0ejvr det = float(xx*yy - yx*xy) 2389e58c90401ef3ce7ed4a85fbda14bbbc564ebf0ejvr xx, xy, yx, yy = yy/det, -xy/det, -yx/det, xx/det 2399e58c90401ef3ce7ed4a85fbda14bbbc564ebf0ejvr dx, dy = -xx*dx - yx*dy, -xy*dx - yy*dy 2409e58c90401ef3ce7ed4a85fbda14bbbc564ebf0ejvr return self.__class__(xx, xy, yx, yy, dx, dy) 2416385a4e9e16560212857b5eade6d0015f729a0b3jvr 2429e58c90401ef3ce7ed4a85fbda14bbbc564ebf0ejvr def toPS(self): 243deca398915e92f24fe698e2e7d0e13122f61383ejvr """Return a PostScript representation: 244deca398915e92f24fe698e2e7d0e13122f61383ejvr >>> t = Identity.scale(2, 3).translate(4, 5) 245deca398915e92f24fe698e2e7d0e13122f61383ejvr >>> t.toPS() 246deca398915e92f24fe698e2e7d0e13122f61383ejvr '[2 0 0 3 8 15]' 247deca398915e92f24fe698e2e7d0e13122f61383ejvr >>> 248deca398915e92f24fe698e2e7d0e13122f61383ejvr """ 2499e58c90401ef3ce7ed4a85fbda14bbbc564ebf0ejvr return "[%s %s %s %s %s %s]" % self.__affine 2506385a4e9e16560212857b5eade6d0015f729a0b3jvr 2519e58c90401ef3ce7ed4a85fbda14bbbc564ebf0ejvr def __len__(self): 252deca398915e92f24fe698e2e7d0e13122f61383ejvr """Transform instances also behave like sequences of length 6: 253deca398915e92f24fe698e2e7d0e13122f61383ejvr >>> len(Identity) 254deca398915e92f24fe698e2e7d0e13122f61383ejvr 6 255deca398915e92f24fe698e2e7d0e13122f61383ejvr >>> 256deca398915e92f24fe698e2e7d0e13122f61383ejvr """ 2579e58c90401ef3ce7ed4a85fbda14bbbc564ebf0ejvr return 6 2586385a4e9e16560212857b5eade6d0015f729a0b3jvr 2599e58c90401ef3ce7ed4a85fbda14bbbc564ebf0ejvr def __getitem__(self, index): 260deca398915e92f24fe698e2e7d0e13122f61383ejvr """Transform instances also behave like sequences of length 6: 261deca398915e92f24fe698e2e7d0e13122f61383ejvr >>> list(Identity) 262deca398915e92f24fe698e2e7d0e13122f61383ejvr [1, 0, 0, 1, 0, 0] 263deca398915e92f24fe698e2e7d0e13122f61383ejvr >>> tuple(Identity) 264deca398915e92f24fe698e2e7d0e13122f61383ejvr (1, 0, 0, 1, 0, 0) 265deca398915e92f24fe698e2e7d0e13122f61383ejvr >>> 266deca398915e92f24fe698e2e7d0e13122f61383ejvr """ 2679e58c90401ef3ce7ed4a85fbda14bbbc564ebf0ejvr return self.__affine[index] 2686385a4e9e16560212857b5eade6d0015f729a0b3jvr 2699e58c90401ef3ce7ed4a85fbda14bbbc564ebf0ejvr def __getslice__(self, i, j): 270deca398915e92f24fe698e2e7d0e13122f61383ejvr """Transform instances also behave like sequences and even support 271deca398915e92f24fe698e2e7d0e13122f61383ejvr slicing: 272deca398915e92f24fe698e2e7d0e13122f61383ejvr >>> t = Offset(100, 200) 273deca398915e92f24fe698e2e7d0e13122f61383ejvr >>> t 274deca398915e92f24fe698e2e7d0e13122f61383ejvr <Transform [1 0 0 1 100 200]> 275deca398915e92f24fe698e2e7d0e13122f61383ejvr >>> t[4:] 276deca398915e92f24fe698e2e7d0e13122f61383ejvr (100, 200) 277deca398915e92f24fe698e2e7d0e13122f61383ejvr >>> 278deca398915e92f24fe698e2e7d0e13122f61383ejvr """ 2799e58c90401ef3ce7ed4a85fbda14bbbc564ebf0ejvr return self.__affine[i:j] 2806385a4e9e16560212857b5eade6d0015f729a0b3jvr 2819e58c90401ef3ce7ed4a85fbda14bbbc564ebf0ejvr def __cmp__(self, other): 282deca398915e92f24fe698e2e7d0e13122f61383ejvr """Transform instances are comparable: 283deca398915e92f24fe698e2e7d0e13122f61383ejvr >>> t1 = Identity.scale(2, 3).translate(4, 6) 284deca398915e92f24fe698e2e7d0e13122f61383ejvr >>> t2 = Identity.translate(8, 18).scale(2, 3) 285deca398915e92f24fe698e2e7d0e13122f61383ejvr >>> t1 == t2 286deca398915e92f24fe698e2e7d0e13122f61383ejvr 1 287deca398915e92f24fe698e2e7d0e13122f61383ejvr >>> 288deca398915e92f24fe698e2e7d0e13122f61383ejvr 289deca398915e92f24fe698e2e7d0e13122f61383ejvr But beware of floating point rounding errors: 290deca398915e92f24fe698e2e7d0e13122f61383ejvr >>> t1 = Identity.scale(0.2, 0.3).translate(0.4, 0.6) 291deca398915e92f24fe698e2e7d0e13122f61383ejvr >>> t2 = Identity.translate(0.08, 0.18).scale(0.2, 0.3) 292deca398915e92f24fe698e2e7d0e13122f61383ejvr >>> t1 293deca398915e92f24fe698e2e7d0e13122f61383ejvr <Transform [0.2 0.0 0.0 0.3 0.08 0.18]> 294deca398915e92f24fe698e2e7d0e13122f61383ejvr >>> t2 295deca398915e92f24fe698e2e7d0e13122f61383ejvr <Transform [0.2 0.0 0.0 0.3 0.08 0.18]> 296deca398915e92f24fe698e2e7d0e13122f61383ejvr >>> t1 == t2 297deca398915e92f24fe698e2e7d0e13122f61383ejvr 0 298deca398915e92f24fe698e2e7d0e13122f61383ejvr >>> 299deca398915e92f24fe698e2e7d0e13122f61383ejvr """ 3009e58c90401ef3ce7ed4a85fbda14bbbc564ebf0ejvr xx1, xy1, yx1, yy1, dx1, dy1 = self.__affine 3019e58c90401ef3ce7ed4a85fbda14bbbc564ebf0ejvr xx2, xy2, yx2, yy2, dx2, dy2 = other 3029e58c90401ef3ce7ed4a85fbda14bbbc564ebf0ejvr return cmp((xx1, xy1, yx1, yy1, dx1, dy1), 3039e58c90401ef3ce7ed4a85fbda14bbbc564ebf0ejvr (xx2, xy2, yx2, yy2, dx2, dy2)) 3046385a4e9e16560212857b5eade6d0015f729a0b3jvr 3059e58c90401ef3ce7ed4a85fbda14bbbc564ebf0ejvr def __hash__(self): 306deca398915e92f24fe698e2e7d0e13122f61383ejvr """Transform instances are hashable, meaning you can use them as 307deca398915e92f24fe698e2e7d0e13122f61383ejvr keys in dictionaries: 308deca398915e92f24fe698e2e7d0e13122f61383ejvr >>> d = {Scale(12, 13): None} 309deca398915e92f24fe698e2e7d0e13122f61383ejvr >>> d 310deca398915e92f24fe698e2e7d0e13122f61383ejvr {<Transform [12 0 0 13 0 0]>: None} 311deca398915e92f24fe698e2e7d0e13122f61383ejvr >>> 312deca398915e92f24fe698e2e7d0e13122f61383ejvr 313deca398915e92f24fe698e2e7d0e13122f61383ejvr But again, beware of floating point rounding errors: 314deca398915e92f24fe698e2e7d0e13122f61383ejvr >>> t1 = Identity.scale(0.2, 0.3).translate(0.4, 0.6) 315deca398915e92f24fe698e2e7d0e13122f61383ejvr >>> t2 = Identity.translate(0.08, 0.18).scale(0.2, 0.3) 316deca398915e92f24fe698e2e7d0e13122f61383ejvr >>> t1 317deca398915e92f24fe698e2e7d0e13122f61383ejvr <Transform [0.2 0.0 0.0 0.3 0.08 0.18]> 318deca398915e92f24fe698e2e7d0e13122f61383ejvr >>> t2 319deca398915e92f24fe698e2e7d0e13122f61383ejvr <Transform [0.2 0.0 0.0 0.3 0.08 0.18]> 320deca398915e92f24fe698e2e7d0e13122f61383ejvr >>> d = {t1: None} 321deca398915e92f24fe698e2e7d0e13122f61383ejvr >>> d 322deca398915e92f24fe698e2e7d0e13122f61383ejvr {<Transform [0.2 0.0 0.0 0.3 0.08 0.18]>: None} 323deca398915e92f24fe698e2e7d0e13122f61383ejvr >>> d[t2] 324deca398915e92f24fe698e2e7d0e13122f61383ejvr Traceback (most recent call last): 325deca398915e92f24fe698e2e7d0e13122f61383ejvr File "<stdin>", line 1, in ? 326deca398915e92f24fe698e2e7d0e13122f61383ejvr KeyError: <Transform [0.2 0.0 0.0 0.3 0.08 0.18]> 327deca398915e92f24fe698e2e7d0e13122f61383ejvr >>> 328deca398915e92f24fe698e2e7d0e13122f61383ejvr """ 3299e58c90401ef3ce7ed4a85fbda14bbbc564ebf0ejvr return hash(self.__affine) 3306385a4e9e16560212857b5eade6d0015f729a0b3jvr 3319e58c90401ef3ce7ed4a85fbda14bbbc564ebf0ejvr def __repr__(self): 3329e58c90401ef3ce7ed4a85fbda14bbbc564ebf0ejvr return "<%s [%s %s %s %s %s %s]>" % ((self.__class__.__name__,) 3339e58c90401ef3ce7ed4a85fbda14bbbc564ebf0ejvr + tuple(map(str, self.__affine))) 3349e58c90401ef3ce7ed4a85fbda14bbbc564ebf0ejvr 3359e58c90401ef3ce7ed4a85fbda14bbbc564ebf0ejvr 3369e58c90401ef3ce7ed4a85fbda14bbbc564ebf0ejvrIdentity = Transform() 3379e58c90401ef3ce7ed4a85fbda14bbbc564ebf0ejvr 3389e58c90401ef3ce7ed4a85fbda14bbbc564ebf0ejvrdef Offset(x=0, y=0): 339f184f754862637ad9c3c32952a0756414e2b67a6jvr """Return the identity transformation offset by x, y. 340f184f754862637ad9c3c32952a0756414e2b67a6jvr 341f184f754862637ad9c3c32952a0756414e2b67a6jvr Example: 342f184f754862637ad9c3c32952a0756414e2b67a6jvr >>> Offset(2, 3) 343f184f754862637ad9c3c32952a0756414e2b67a6jvr <Transform [1 0 0 1 2 3]> 344f184f754862637ad9c3c32952a0756414e2b67a6jvr >>> 345f184f754862637ad9c3c32952a0756414e2b67a6jvr """ 3469e58c90401ef3ce7ed4a85fbda14bbbc564ebf0ejvr return Transform(1, 0, 0, 1, x, y) 3479e58c90401ef3ce7ed4a85fbda14bbbc564ebf0ejvr 3489e58c90401ef3ce7ed4a85fbda14bbbc564ebf0ejvrdef Scale(x, y=None): 349f184f754862637ad9c3c32952a0756414e2b67a6jvr """Return the identity transformation scaled by x, y. The 'y' argument 350f184f754862637ad9c3c32952a0756414e2b67a6jvr may be None, which implies to use the x value for y as well. 351f184f754862637ad9c3c32952a0756414e2b67a6jvr 352f184f754862637ad9c3c32952a0756414e2b67a6jvr Example: 353f184f754862637ad9c3c32952a0756414e2b67a6jvr >>> Scale(2, 3) 354f184f754862637ad9c3c32952a0756414e2b67a6jvr <Transform [2 0 0 3 0 0]> 355f184f754862637ad9c3c32952a0756414e2b67a6jvr >>> 356f184f754862637ad9c3c32952a0756414e2b67a6jvr """ 3579e58c90401ef3ce7ed4a85fbda14bbbc564ebf0ejvr if y is None: 3589e58c90401ef3ce7ed4a85fbda14bbbc564ebf0ejvr y = x 3599e58c90401ef3ce7ed4a85fbda14bbbc564ebf0ejvr return Transform(x, 0, 0, y, 0, 0) 360f184f754862637ad9c3c32952a0756414e2b67a6jvr 361f184f754862637ad9c3c32952a0756414e2b67a6jvr 362f184f754862637ad9c3c32952a0756414e2b67a6jvrdef _test(): 363deca398915e92f24fe698e2e7d0e13122f61383ejvr import doctest, transform 364deca398915e92f24fe698e2e7d0e13122f61383ejvr return doctest.testmod(transform) 365f184f754862637ad9c3c32952a0756414e2b67a6jvr 366f184f754862637ad9c3c32952a0756414e2b67a6jvrif __name__ == "__main__": 367f184f754862637ad9c3c32952a0756414e2b67a6jvr _test() 368