Created
May 9, 2025 20:33
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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