Source code for binpack

"""
binpack
=======

Copyright @ 2013 Mathias Westerdahl

Brief
------

The bin packing functions are implemented by Jukka Jylanki:

- `A thousand ways to pack the bin - A practical approach to two-dimensional rectangle bin packing <http://clb.demon.fi/files/RectangleBinPack.pdf>`_
- `RectangleBinPack.zip <http://clb.demon.fi/files/RectangleBinPack/RectangleBinPack.zip>`_ (public domain) 

An example of the Skyline Bottom Left packing algorithm which is default in the font creator
 
.. image:: binpack_skyline_bl.png

Reference
---------


"""

import sys
import ctypes
from ctypes import c_void_p, c_float, c_int32, c_bool


if sys.platform == 'darwin':
    _suffix = '.dylib'
    platformdir = 'darwin64' if sys.maxsize > 2**32 else 'darwin32'
elif sys.platform == 'win32':
    _suffix = '.dll'
    platformdir = 'win64' if sys.maxsize > 2**32 else 'win32'
elif sys.platform == 'linux2':
    _suffix = '.so'
    platformdir = 'linux64' if sys.maxsize > 2**32 else 'linux32'

try:
    path = '_binpack%s' % (_suffix)
    _binpack = ctypes.cdll.LoadLibrary(path)
except (OSError,):
    raise IOError("FAILED TO OPEN " + path)

#: Bottom left
SKYLINE_BL = 0
#: Min Waste
SKYLINE_MW = 1
#: Positions the rectangle against the short side of a free rectangle into which it fits the best.
MAXRECTS_BSSF = 2
#: Positions the rectangle against the long side of a free rectangle into which it fits the best.
MAXRECTS_BLSF = 3
#: Positions the rectangle into the smallest free rect into which it fits.
MAXRECTS_BAF = 4
#: Does the Tetris placement.
MAXRECTS_BL = 5
#: Chooses the placement where the rectangle touches other rects as much as possible.
MAXRECTS_CP = 6


c_packer_p = c_void_p

[docs]class Rect(ctypes.Structure): """ A rectangle struct (x, y, w, h) """ _fields_ = [ ('x', c_int32), ('y', c_int32), ('width', c_int32), ('height', c_int32), ] def __str__(self): return 'Rect(%d, %d, %d, %d)' % (self.x, self.y, self.width, self.height)
_create_packer = _binpack.create_packer _create_packer.restype = c_packer_p _create_packer.argtypes = [c_int32, c_int32, c_int32, c_bool] _destroy_packer = _binpack.destroy_packer _destroy_packer.argtypes = [c_packer_p] _get_occupancy = _binpack.get_occupancy _get_occupancy.restype = c_float _get_occupancy.argtypes = [c_packer_p] _pack_rect = _binpack.pack_rect _pack_rect.restype = Rect _pack_rect.argtypes = [c_packer_p, c_int32, c_int32]
[docs]def create_packer(type, width, height, allow_rotate): """ Creates a packer instance for use with consecutive calls to pack_rect() :param type: The packing algorithm. Must be one of: - SKYLINE_BL - SKYLINE_MW - MAXRECTS_BSSF - MAXRECTS_BLSF - MAXRECTS_BAF - MAXRECTS_BL - MAXRECTS_CP :param width: The width of the packing bin (won't change) :param height: The height of the packing bin (won't change) :param allow_rotate: Tells the packer if it is allowed to rotate the rects. :return: Returns the packer instance. Must be destroyed with destroy_packer() """ return _create_packer(type, width, height, allow_rotate)
[docs]def destroy_packer(packer): """ Destroys the packer instance :param packer: The packer instance """ _destroy_packer(packer)
[docs]def get_occupancy(packer): """ Returns the occupancy as a value between 0.0 and 1.0 :param packer: The packer instance :return: The current occupancy as a unit value """ return _get_occupancy(packer)
[docs]def pack_rect(packer, w, h): """ Packs a rectangle in the bin. :param w: The width of the rect :param h: The height of the rect :return: Returns (0, 0, 0, 0) if the bin is full Returns (x, y, W, H) if the rect was packed Returns (x, y, H, W) if the rect was packed and rotated """ return _pack_rect(packer, w, h)
if __name__ == '__main__': import time import Image, ImageDraw from random import seed, randint data = [] for _ in xrange(700): w = randint(10, 20) h = randint(10, 30) r = randint(100, 200) g = randint(100, 200) b = randint(100, 200) data.append( (w, h, r, g, b) ) def _sort(a, b): return a[1] - b[1] # sort by height data.sort(_sort) for typ, name in ( (SKYLINE_BL, 'skyline_bl'), (SKYLINE_MW, 'skyline_mw'), (MAXRECTS_BSSF, 'maxrects_bssf'), (MAXRECTS_CP, 'maxrects_cp'), (MAXRECTS_BAF, 'maxrects_baf') ): iw = 512 ih = 512 im = Image.new( 'RGB', (iw, ih), (0,0,0) ) draw = ImageDraw.Draw(im) packer = create_packer(typ, iw, ih, False) seed(1234567) timestart = time.time() count = 0 for w, h, r, g, b in data: rect = pack_rect( packer, w, h ) if rect.height == 0: break draw.rectangle( (rect.x, rect.y, rect.x + rect.width, rect.y + rect.height), fill=(r,g,b) ) count += 1 print '%s: Packed %d rects in %f seconds. Filled %d %%' % ( name, count, time.time() - timestart, (get_occupancy(packer) * 100)) destroy_packer(packer) draw.rectangle( (30,30, 50, 60), fill=(230,120,100) ) im.save('build/binpack_%s.png' % name)