Home | History | Annotate | Download | only in misc
      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