Generating a tile sheet from many images

Using isometric image assets that correlate to 3D models to create a 3D environment with. The assets used are from Kenney.

Cropping and adding multiple images into a tileset is inefficient and time-consuming. I decided to write a Python script that automates this.

The isometric assets as part of Kenney’s asset pack
The generated tile-sheet
Using the generated tile-sheet in Tiled, a flexible level editor

The script imports Image for writing, cropping and creating images and ElementTree to read, edit and create XML files that create a file describing every tile and their names in the Tiled format. 

from PIL import Image
import xml.etree.ElementTree as ET
import xml.dom.minidom
import os.path, sys
import math

path = "C:\\Users\\Dion-\\Documents\\Development\\Tiled\\TestAssets"
newpath = path + "\\exported\\"

dirs = os.listdir(path)

# Setting the points for cropped image
left = 191
top = 181
right = 130
bottom = 168

#image related
imgWidth, imgHeight = Image.open(
    os.path.join( path, dirs[0] )
).crop((left, top, right + left, bottom + top)).size

print( "Image size: " + str(imgWidth) + ", " + str(imgHeight) )

#tileset related
columns = 24
rows = math.floor( len(dirs) / columns ) + 1

spacing = 5

tilesetWidth = ( imgWidth + spacing ) * columns
tilesetHeight = ( imgHeight + spacing ) * rows
tilesetWidth = round(tilesetWidth)
tilesetHeight = round(tilesetHeight)

imgTileset = Image.new('RGBA', (tilesetWidth, tilesetHeight))

def cropAllToTileset():
    itemCount = 0
    curCol = 0
    curRow = 0
    dom_tileset = ET.Element('tileset')
    for item in dirs:

        fullpath = os.path.join(path, item)
        if os.path.isfile(fullpath):

            im = Image.open(fullpath)
            f, e = os.path.splitext(fullpath)

            imCrop = im.crop((left, top, right + left, bottom + top))

            imgTileset.paste( imCrop.copy(), ((imgWidth + spacing) * curCol, (imgHeight + spacing) * curRow) )

            #construct dom
            dom_tile = ET.SubElement(dom_tileset, 'tile')
            dom_tile.set('id', str(itemCount))
            dom_properties = ET.SubElement(dom_tile, 'properties')
            dom_property = ET.SubElement(dom_properties, 'property')
            dom_property.set('name', 'obj_name')
            dom_property.set('value', os.path.basename(f))

            itemCount += 1

            # count correct rows for tileset
            if (itemCount % columns) == 0:
                curRow += 1
                curCol = 0
            else:
                curCol += 1

    if not os.path.exists(newpath + "\\tileset\\"):
        os.makedirs(newpath + "\\tileset\\")

    #write image
    imgTileset.save( newpath + "\\tileset\\" + "tileset_" + str(imgWidth) + "x" + str(imgHeight) + ".png" , "png", quality=100 )

    #write dom xml
    mydata = '<root>' + str(ET.tostring(dom_tileset, encoding='utf-8', method='xml')) + '</root>'
    myfile = open(newpath + "\\tileset\\" + "items2.xml", "w")
    dom_final = xml.dom.minidom.parseString(mydata).toprettyxml()
    myfile.write(dom_final)

    print("Done")


# https://stackoverflow.com/questions/47785918/python-pil-crop-all-images-in-a-folder
def crop():
    for item in dirs:
        fullpath = os.path.join(path, item)
        if os.path.isfile(fullpath):
            im = Image.open(fullpath)
            f, e = os.path.splitext(fullpath)

            imCrop = im.crop((left, top, right + left, bottom + top))

            if not os.path.exists(newpath):
                os.makedirs(newpath)

            imCrop.save( newpath + os.path.basename(f) + ".png", "png", quality=100 )

#crop()
cropAllToTileset()