arrayTools.py revision 4f033683ca79093de30b62f5a3b87c2c466496f0
1#
2# Various array and rectangle tools, but mostly rectangles, hence the
3# name of this module (not).
4#
5
6
7import math
8
9def matMult(a,b):
10    """Returns the matrix multiplication of two matrices. Matrices
11    are represented as lists of lists, row-major."""
12    r1 = range(len(a[0]))
13    r2 = range(len(b[0]))
14    return [[sum(r[i]*b[i][j] for i in r1) for j in r2] for r in a]
15
16def calcBounds(array):
17    """Return the bounding rectangle of a 2D points array as a tuple:
18    (xMin, yMin, xMax, yMax)
19    """
20    if len(array) == 0:
21        return 0, 0, 0, 0
22    xs = [x for x, y in array]
23    ys = [y for x, y in array]
24    return min(xs), min(ys), max(xs), max(ys)
25
26def updateBounds(bounds, (x, y), min=min, max=max):
27    """Return the bounding recangle of rectangle bounds and point (x, y)."""
28    xMin, yMin, xMax, yMax = bounds
29    return min(xMin, x), min(yMin, y), max(xMax, x), max(yMax, y)
30
31def pointInRect((x, y), rect):
32    """Return True when point (x, y) is inside rect."""
33    xMin, yMin, xMax, yMax = rect
34    return (xMin <= x <= xMax) and (yMin <= y <= yMax)
35
36def pointsInRect(array, rect):
37    """Find out which points or array are inside rect.
38    Returns an array with a boolean for each point.
39    """
40    if len(array) < 1:
41        return []
42    xMin, yMin, xMax, yMax = rect
43    return [(xMin <= x <= xMax) and (yMin <= y <= yMax) for x, y in array]
44
45def vectorLength(vector):
46    """Return the length of the given vector."""
47    x, y = vector
48    return math.sqrt(x**2 + y**2)
49
50def asInt16(array):
51    """Round and cast to 16 bit integer."""
52    return [int(math.floor(i+0.5)) for i in array]
53
54
55def normRect((xMin, yMin, xMax, yMax)):
56    """Normalize the rectangle so that the following holds:
57        xMin <= xMax and yMin <= yMax
58    """
59    return min(xMin, xMax), min(yMin, yMax), max(xMin, xMax), max(yMin, yMax)
60
61def scaleRect((xMin, yMin, xMax, yMax), x, y):
62    """Scale the rectangle by x, y."""
63    return xMin * x, yMin * y, xMax * x, yMax * y
64
65def offsetRect((xMin, yMin, xMax, yMax), dx, dy):
66    """Offset the rectangle by dx, dy."""
67    return xMin+dx, yMin+dy, xMax+dx, yMax+dy
68
69def insetRect((xMin, yMin, xMax, yMax), dx, dy):
70    """Inset the rectangle by dx, dy on all sides."""
71    return xMin+dx, yMin+dy, xMax-dx, yMax-dy
72
73def sectRect((xMin1, yMin1, xMax1, yMax1), (xMin2, yMin2, xMax2, yMax2)):
74    """Return a boolean and a rectangle. If the input rectangles intersect, return
75    True and the intersecting rectangle. Return False and (0, 0, 0, 0) if the input
76    rectangles don't intersect.
77    """
78    xMin, yMin, xMax, yMax = (max(xMin1, xMin2), max(yMin1, yMin2),
79                              min(xMax1, xMax2), min(yMax1, yMax2))
80    if xMin >= xMax or yMin >= yMax:
81        return 0, (0, 0, 0, 0)
82    return 1, (xMin, yMin, xMax, yMax)
83
84def unionRect((xMin1, yMin1, xMax1, yMax1), (xMin2, yMin2, xMax2, yMax2)):
85    """Return the smallest rectangle in which both input rectangles are fully
86    enclosed. In other words, return the total bounding rectangle of both input
87    rectangles.
88    """
89    xMin, yMin, xMax, yMax = (min(xMin1, xMin2), min(yMin1, yMin2),
90                              max(xMax1, xMax2), max(yMax1, yMax2))
91    return (xMin, yMin, xMax, yMax)
92
93def rectCenter((xMin, yMin, xMax, yMax)):
94    """Return the center of the rectangle as an (x, y) coordinate."""
95    return (xMin+xMax)/2, (yMin+yMax)/2
96
97def intRect((xMin, yMin, xMax, yMax)):
98    """Return the rectangle, rounded off to integer values, but guaranteeing that
99    the resulting rectangle is NOT smaller than the original.
100    """
101    import math
102    xMin = int(math.floor(xMin))
103    yMin = int(math.floor(yMin))
104    xMax = int(math.ceil(xMax))
105    yMax = int(math.ceil(yMax))
106    return (xMin, yMin, xMax, yMax)
107
108
109def _test():
110    """
111    >>> import math
112    >>> calcBounds([])
113    (0, 0, 0, 0)
114    >>> calcBounds([(0, 40), (0, 100), (50, 50), (80, 10)])
115    (0, 10, 80, 100)
116    >>> updateBounds((0, 0, 0, 0), (100, 100))
117    (0, 0, 100, 100)
118    >>> pointInRect((50, 50), (0, 0, 100, 100))
119    True
120    >>> pointInRect((0, 0), (0, 0, 100, 100))
121    True
122    >>> pointInRect((100, 100), (0, 0, 100, 100))
123    True
124    >>> not pointInRect((101, 100), (0, 0, 100, 100))
125    True
126    >>> list(pointsInRect([(50, 50), (0, 0), (100, 100), (101, 100)], (0, 0, 100, 100)))
127    [True, True, True, False]
128    >>> vectorLength((3, 4))
129    5.0
130    >>> vectorLength((1, 1)) == math.sqrt(2)
131    True
132    >>> list(asInt16([0, 0.1, 0.5, 0.9]))
133    [0, 0, 1, 1]
134    >>> normRect((0, 10, 100, 200))
135    (0, 10, 100, 200)
136    >>> normRect((100, 200, 0, 10))
137    (0, 10, 100, 200)
138    >>> scaleRect((10, 20, 50, 150), 1.5, 2)
139    (15.0, 40, 75.0, 300)
140    >>> offsetRect((10, 20, 30, 40), 5, 6)
141    (15, 26, 35, 46)
142    >>> insetRect((10, 20, 50, 60), 5, 10)
143    (15, 30, 45, 50)
144    >>> insetRect((10, 20, 50, 60), -5, -10)
145    (5, 10, 55, 70)
146    >>> intersects, rect = sectRect((0, 10, 20, 30), (0, 40, 20, 50))
147    >>> not intersects
148    True
149    >>> intersects, rect = sectRect((0, 10, 20, 30), (5, 20, 35, 50))
150    >>> intersects
151    1
152    >>> rect
153    (5, 20, 20, 30)
154    >>> unionRect((0, 10, 20, 30), (0, 40, 20, 50))
155    (0, 10, 20, 50)
156    >>> rectCenter((0, 0, 100, 200))
157    (50, 100)
158    >>> rectCenter((0, 0, 100, 199.0))
159    (50, 99.5)
160    >>> intRect((0.9, 2.9, 3.1, 4.1))
161    (0, 2, 4, 5)
162    """
163
164if __name__ == "__main__":
165    import doctest
166    doctest.testmod()
167