Skip to content

Instantly share code, notes, and snippets.

@illnyang
Created May 9, 2025 20:33
Show Gist options
  • Save illnyang/66e8c3284173ba86db826e3ac3c76881 to your computer and use it in GitHub Desktop.
Save illnyang/66e8c3284173ba86db826e3ac3c76881 to your computer and use it in GitHub Desktop.
convert font atlas extracted with allegro4's `dat` into BMFont format, single row texture
#!/usr/bin/env python
import math
import sys
from pathlib import Path
import unicodedata
from PIL import Image
def extract_characters(
input_path: Path,
output_extension: str,
bg = (0, 170, 28),
bg_glyph = (154, 20, 146)
):
with Image.open(input_path) as img:
img = img.convert("RGBA")
img_w, img_h = img.size
rects = []
for y in range(img_h):
for x in range(img_w):
pixel = img.getpixel((x, y))
if pixel[:3] != bg:
added = False
for ax, ay, bx, by in rects:
if ax <= x <= bx and ay <= y <= by:
added = True
break
if not added:
# Expand right
r = x
while r + 1 < img_w and img.getpixel((r + 1, y))[:3] != bg:
r += 1
# Expand down
b = y
while b + 1 < img_h:
all_match = True
for test_x in range(x, r + 1):
if img.getpixel((test_x, b + 1))[:3] == bg:
all_match = False
break
if all_match:
b += 1
else:
break
rects.append((x, y, r, b))
# Determine cell size based on size of first glyph
ax, ay, bx, by = rects[0]
glyph_w = bx - ax + 1
glyph_h = by - ay + 1
cell_w = 16 * math.ceil(glyph_w / 16)
cell_h = 16 * math.ceil(glyph_h / 16)
cell_cols = (img_w - 1) // cell_w
cell_rows = (img_h - 1) // cell_h
assert ((img_w - 1) % cell_w) == 0
assert ((img_h - 1) % cell_h) == 0
assert len(rects) == cell_rows * cell_cols
glyphs = []
# formant = f"{{:0{int(math.log10(len(rects))) + 1}d}}_{{}}.{output_extension}"
for i, (ax, ay, bx, by) in enumerate(rects):
w = bx - ax + 1
h = by - ay + 1
glyph_img = Image.new("RGBA", (w, h), (0, 0, 0, 0))
for x in range(w):
for y in range(h):
img_x = ax + x
img_y = ay + y
pixel = img.getpixel((img_x, img_y))
if pixel[:3] == bg_glyph:
glyph_img.putpixel((x, y), (0, 0, 0, 0))
else:
glyph_img.putpixel((x, y), pixel)
glyphs.append(glyph_img)
# try:
# glyph_name = unicodedata.name(chr(i + ord(' '))).replace(' ', '_')
# except ValueError:
# glyph_name = "UNKNOWN"
# output_path = os.path.join(output_directory, formant.format(i, glyph_name))
# glyph_img.save(output_path)
out_w = sum(glyph_img.size[0] + 1 for glyph_img in glyphs)
out_h = glyph_h
out_img = Image.new("RGBA", (out_w, out_h), (0, 0, 0, 0))
out_x = 0
for glyph_img in glyphs:
out_img.paste(glyph_img, (out_x, 0))
out_x += glyph_img.size[0] + 1
_, ay, _, by = out_img.getbbox(alpha_only=True)
out_img = out_img.crop((0, ay, out_w, by))
out_path = f"{input_path.stem}.fnt.{output_extension}"
out_img.save(out_path)
h = out_img.size[1]
# https://www.angelcode.com/products/bmfont/doc/file_format.html
bmfont = [
{
"info": {
"face": input_path.stem,
"size": h,
"bold": 0,
"italic": 0,
"charset": "",
"unicode": 1,
"stretchH": 100,
"smooth": 0,
"aa": 1,
"padding": [0, 0, 0, 0],
"spacing": [0, 0], # [1,1] ?
"outline": 0
}
},
{
"common": {
"lineHeight": h,
"base": h,
"scaleW": out_w,
"scaleH": h,
"pages": 1,
"packed": 0,
"alphaChnl": 0,
"redChnl": 0,
"greenChnl": 0,
"blueChnl": 0,
},
},
{
"page": {
"id": 0,
"file": out_path
},
},
{
"chars": {
"count": len(glyphs)
}
},
]
out_x = 0
for i, glyph_img in enumerate(glyphs):
bmfont.append({"char": {
"id": i + ord(' '),
"x": out_x,
"y": 0,
"width": glyph_img.size[0],
"height": h,
"xoffset": 0,
"yoffset": 0,
"xadvance": glyph_img.size[0],
"page": 0,
"chnl": 15
}})
out_x += glyph_img.size[0] + 1
with open(f"{input_path.stem}.fnt", 'w') as fnt_file:
for i in range(len(bmfont)):
for tag, attributes in bmfont[i].items():
parts = [tag]
for key, value in attributes.items():
if isinstance(value, list):
parts.append(f"{key}={','.join(map(str, value))}")
elif isinstance(value, str):
parts.append(f'{key}="{value}"')
else:
parts.append(f"{key}={value}")
fnt_file.write(' '.join(parts) + '\n')
if __name__ == "__main__":
if len(sys.argv) <= 1:
print("Usage: python allegro4fnt.py <input> [png|bmp|...]")
sys.exit(1)
input_image = Path(sys.argv[1])
output_ext = sys.argv[2] if len(sys.argv) >= 3 else "png"
extract_characters(input_image, output_ext)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment