Skip to main content

ImagePacker — wiki

These two classes can pack surfaces of different sizes into one big surface.

import pygame

class PackNode(object):
    """
    Creates an area which can recursively pack images into itself.
    """
    def __init__(self, area):
        # if tuple contains two elements, assume they are width and height,
        # and origin is (0,0)
        if len(area) == 2:
            area = (0,0,area[0],area[1])
        self.area = area

    def get_width(self):
        return self.area[2] - self.area[0]
    width = property(fget=get_width)

    def get_height(self):
        return self.area[3] - self.area[1]
    height = property(fget=get_height)

    def insert(self, area):
        if hasattr(self, 'child'):
            a = self.child[0].insert(area)
            if a is None: return self.child[1].insert(area)
            return a

        area = PackNode(area)
        if area.width <= self.width and area.height <= self.height:
            self.child = [None,None]
            self.child[0] = PackNode((self.area[0]+area.width, self.area[1],
                self.area[2], self.area[1] + area.height))
            self.child[1] = PackNode((self.area[0], self.area[1]+area.height,
                self.area[2], self.area[3]))
            return PackNode((self.area[0], self.area[1],
                self.area[0]+area.width, self.area[1]+area.height))


class Pack(object):
    def __init__(self, size=(512,512)):
        self.tree = PackNode(size)
        self.image = pygame.Surface(size, pygame.SRCALPHA, 32)
        self.wr = 1.0 / float(size[0])
        self.hr = 1.0 / float(size[1])
        
    def pack(self, img):
        wr = self.wr
        hr = self.hr
        if not isinstance(img, pygame.Surface):
            img = pygame.image.load(img).convert(32, pygame.SRCALPHA)
            img = img.convert_alpha(img)
        uv = self.tree.insert(img.get_size())
        if uv is None: raise ValueError('Pack size too small.')
        area = tuple(uv.area)
        self.image.blit(img, area)
        uv = area[0] * wr, area[1] * hr, area[2] * wr, area[3] * hr
        return (uv[0],uv[1]),(uv[0], uv[3]), (uv[2],uv[3]), (uv[2], uv[1])


if __name__ == "__main__":
    import time
    import glob

    pygame.init()
    s = pygame.display.set_mode((512,512))
    p = Pack()
    for filename in glob.glob("*.png"):
        p.pack(filename)
    pygame.image.save(p.image, 'out.png')
    s.blit(p.image, (0,0))
    pygame.display.flip()
    time.sleep(1)