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