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