Generating an Atlas of Wooden Letters from a Font File using ChatGPT and Stable Diffusion

Creating an Atlas of Wooden Tile Images from a TTF with ChatGPT

    Last Saturday, I created an Atlas of wooden tile images from just a TTF font file using ChatGPT and batch processing with Stable Diffusion's ControlNet. The entire process took me only two hours, thanks to ChatGPT's assistance. Here's a breakdown of what I did:

  1. First, ChatGPT helped me write a Python script to extract images of capital letters from a TTF file. It created fairly straightforward 512x512 tile images. I did need to modify their vertical offset by hand so they would be optimally positioned for Stable Diffusion img2img. I had to ask for it to ask the user to specify details about what it would be generating. By default, you specify these things inside the script, and this can be a bit tedious. Fortunately, ChatGPT is great at adding simple UIs to Python scripts.
  2. Then, I batch generated wooden tiles from those images using Stable Diffusion batch img2img, using ControlNet to set the lines to match. This created an alphabet of tiles that all matched the description given at the top (beautiful wooden tile) Note that I created these before ControlNet created their color ControlNet, so some of them may be a little dark.
  3. Next, I used ChatGPT to write a Python tool for bulk masking images with the same name, which allowed me to cut out the letters in their original shapes.
  4. I created another Python script with ChatGPT that combined all of the images into one, making it easy to showcase the output.
  5. I ran all of these on a font called Macondo.


    Without ChatGPT, this process would've taken days. It's also worth mentioning that these files sometimes require small edits or fixes. If you encounter any errors, you can supply the code and the error message to ChatGPT and it will usually give you a detailed explanation of the problem and a potential fix for it.

Generated Outputs

All of the letters, masked:


Some of the letters without the mask:



















The Scripts

1. extract_letters.py

    This script extracts images of capital letters (A-Z) from a TTF (TrueType Font) file. It creates an image for each letter and saves it in a folder. The user can customize the image width, image height, font size, and offset height.
import easygui, os
from fontTools.ttLib import TTFont
from PIL import Image, ImageDraw, ImageFont

# Prompt the user to select a TTF file using a file dialog
ttf_path = easygui.fileopenbox(title='Select a TrueType font file', filetypes=[('*.ttf')])

# Prompt the user to enter the image width, image height, font size, and offset height
image_width = easygui.integerbox('Enter the image width:', 'Image size', 512, 1, 2048)
image_height = easygui.integerbox('Enter the image height:', 'Image size', 512, 1, 2048)
font_size = easygui.integerbox('Enter the font size:', 'Font size', 400, 1, 2048)
offset_height = easygui.integerbox('Enter the offset height:', 'Offset height', 128, -2048, 2048)

# Create the "letters" folder if it doesn't exist
font_name = os.path.basename(ttf_path).split('.')[0]
letters_font_path = 'letters_' + font_name
if not os.path.exists(letters_font_path):
    os.makedirs(letters_font_path)

# Load the TTF file
orig_font = TTFont(ttf_path)

# Loop over each capital letter in the alphabet
for letter in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ':  # ASCII code for A-Z
    # Get the glyph for the current letter
    glyph = orig_font['glyf'][letter]
    print(letter, glyph)
    # Create a new image and draw the glyph onto it
    img = Image.new('RGBA', (image_width, image_height), (255, 255, 255, 0))
    draw = ImageDraw.Draw(img)
    font = ImageFont.truetype(ttf_path, font_size)
    w, h = draw.textsize(str(letter), font=font)
    draw.text(((image_width - w) / 2, (image_height - h - offset_height) / 2), str(letter), fill=(0, 0, 0), font=font)

    # Save the image as a PNG file with the letter as the filename
    img.save(letters_font_path+'/{}.png'.format(str(letter)))

    

2. image_mask_batch.py

    This script takes an input folder containing images, a mask folder containing masks for each image, and an output folder to save the processed images. It applies the masks from the mask folder to the images in the input folder, preserving the original shapes of the letters. The resulting masked images are saved in the output folder.
import easygui
import os
from PIL import Image

# Choose the input, mask, and output folders using EasyGUI
input_folder = easygui.diropenbox(title='Select the input folder')
mask_folder = easygui.diropenbox(title='Select the mask folder')
output_folder = easygui.diropenbox(title='Select the output folder')

if not all([input_folder, mask_folder, output_folder]):
    # If any of the folders were not chosen, exit the script
    easygui.msgbox('You must choose all three folders.', title='Error')
    exit()

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

for filename in os.listdir(input_folder):
    if filename.endswith('.png'):
        input_path = os.path.join(input_folder, filename)
        mask_path = os.path.join(mask_folder, filename)
        output_path = os.path.join(output_folder, filename)

        input_image = Image.open(input_path).convert('RGB')
        mask_image = Image.open(mask_path).convert('RGBA')

        mask_alpha = mask_image.split()[3]

        new_image = input_image.copy()
        new_image.putalpha(mask_alpha)

        new_image.save(output_path)
    

3. generate_atlas.py

    This script combines all the images from the input folder into a single image, creating a sprite atlas. It resizes each image to a predefined size and arranges them in a grid pattern. The generated atlas is saved as a PNG file.
import easygui
import os
from PIL import Image

# Define the image size and the number of images per row
image_size = (85, 85)
num_images_per_row = 13

# Choose the input folder using EasyGUI
input_folder = easygui.diropenbox(title='Select the input folder')

if not input_folder:
    # If the folder was not chosen, exit the script
    easygui.msgbox('You must choose an input folder.', title='Error')
    exit()

# Create a new image to serve as the sprite atlas
atlas_size = (image_size[0] * num_images_per_row, image_size[1] * 2)
atlas = Image.new('RGBA', atlas_size, (255, 255, 255, 0))

# Loop over each capital letter in the alphabet
for i, letter in enumerate('ABCDEFGHIJKLMNOPQRSTUVWXYZ'):
    # Load the image for the current letter and resize it to the desired size
    image_path = os.path.join(input_folder, letter + '.png')
    image = Image.open(image_path).resize(image_size, resample=Image.LANCZOS)

    # Calculate the position to paste the image onto the atlas
    x = (i % num_images_per_row) * image_size[0]
    y = (i // num_images_per_row) * image_size[1]

    # Paste the image onto the atlas
    atlas.paste(image, (x, y, x + image_size[0], y + image_size[1]))

# Save the sprite atlas as a PNG file
atlas.save('sprite_atlas.png')

Comments

Popular posts from this blog

Using Kanban Boards to Stay Organized and to Stay Motivated

Low Level Design Part 1: Reading and Gathering Information

Importance over Immediacy