Skip to content

Commit

Permalink
font overhaul (#56)
Browse files Browse the repository at this point in the history
* font overhaul

* misc changes

* compressed fonts & changed back to consola
  • Loading branch information
kipcode66 authored Oct 19, 2020
1 parent fd17b65 commit 4d79159
Show file tree
Hide file tree
Showing 83 changed files with 789 additions and 2,823 deletions.
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@
*.o
*.a
gz2e01.iso
rzde01.iso
GZ2E01.map
RZDE01.map
build/
.vscode/
!.vscode/c_cpp_properties.json
external/fonts/target
external/fonts/build
Updater.log
Updater.log
__pycache__
5 changes: 5 additions & 0 deletions RomHack.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ libs = ["tpgz.a", "external/libtp_c/libtp_c.a", "external/gcn_c/gcn_c.a"]
"tpgz/tex/tpgz.tex" = "res/tex/tpgz.tex"
"tpgz/tex/flagOn.tex" = "res/tex/flagOn.tex"
"tpgz/tex/flagOff.tex" = "res/tex/flagOff.tex"
"tpgz/fonts/consola.fnt" = "res/fonts/consola.fnt"
"tpgz/fonts/lib-serif.fnt" = "res/fonts/lib-serif.fnt"
"tpgz/fonts/lib-serif-bold.fnt" = "res/fonts/lib-serif-bold.fnt"
"tpgz/fonts/lib-sans.fnt" = "res/fonts/lib-sans.fnt"
"tpgz/fonts/lib-sans-bold.fnt" = "res/fonts/lib-sans-bold.fnt"
"tpgz/save_files/any.bin" = "res/save_files/any.bin"
"tpgz/save_files/any/aeralfos_skip.bin" = "res/save_files/any/aeralfos_skip.bin"
"tpgz/save_files/any/boss_bug.bin" = "res/save_files/any/boss_bug.bin"
Expand Down
Binary file added external/fonts/fonts/LiberationSans-Bold.ttf
Binary file not shown.
Binary file added external/fonts/fonts/LiberationSans-Regular.ttf
Binary file not shown.
Binary file added external/fonts/fonts/LiberationSerif-Bold.ttf
Binary file not shown.
Binary file added external/fonts/fonts/LiberationSerif-Regular.ttf
Binary file not shown.
85 changes: 85 additions & 0 deletions external/misc/font2fnt.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
from PIL import Image, ImageFont, ImageDraw
from textures import Formats, write_tex
import struct
import argparse
import re

def size_type(string):
pattern = re.compile("([0-9]+)[xX]([0-9]+)")
matching = re.fullmatch(pattern, string)
if matching is None:
msg = "%r is not a valid size" % string
raise argparse.ArgumentTypeError(msg)
size = tuple(int(g) for g in matching.groups())
if size[0] % 4 != 0 or size[1] % 4 != 0:
msg = "The new size \"%dx%d\" must be a multiple of 4" % size
raise argparse.ArgumentTypeError(msg)
return size

def write_header(out_buf, font):
out_buf.write(struct.pack(">4s", "FNT0".encode("ascii"))) # channel count
out_buf.write(struct.pack(">f", font['base_size'])) # base_size
out_buf.write(struct.pack(">ff", font['metrics'][0], font['metrics'][1])) # (ascend, descend)

def write_glyphs(out_buf, font):
out_buf.write(len(font['glyphs']).to_bytes(4, 'big', signed=False)) # glyphs count
for i in range(len(font['glyphs'])):
out_buf.write(struct.pack(">ff", font['glyphs'][i]['offset'], font['glyphs'][i]['width']))
out_buf.write(struct.pack(">ffff", *font['glyphs'][i]['tex_coords']))

if __name__ == '__main__':
parser = argparse.ArgumentParser(description='Converts a font into a .fnt file.')
parser.add_argument('-V', '--version', action='version', version='%(prog)s 0.2')
parser.add_argument('font_file', type=argparse.FileType('rb'),
help='Path to the font file')
parser.add_argument('out_fnt', type=argparse.FileType('wb'), nargs='?', default="out.fnt",
help='Name of the output file')
parser.add_argument('-s', '--size', type=size_type, metavar='WxH', default="256x256",
help='Set the resolution of the font to a width/height of W/H')
parser.add_argument('-F', '--font-size', type=int, default=18,
help='Font size (in points)')
parser.add_argument('-f', '--format', type=str, choices=[name for name, _ in Formats.__members__.items()],
default='I8', help='Format to save the font in')
params = parser.parse_args()

if params.format in ["RGBA8"] and (params.size[0] % 4 != 0 or params.size[1] % 4 != 0):
raise RuntimeError("Size has to be a multiple of 4 when using RGBA8")
if params.format in ["CMPR", "I8"] and (params.size[0] % 8 != 0 and params.size[1] % 8 != 0):
raise RuntimeError("Size has to be a multiple of 8 when using CMPR or I8 formats")

image = Image.new('RGBA', params.size, (0,0,0,0))
draw = ImageDraw.Draw(image)

ftf = ImageFont.FreeTypeFont(params.font_file, params.font_size)
asc, dsc = ftf.getmetrics()
pos = (0, asc)
default_glyph = {
'offset': 0,
'width': 0,
'tex_coords' : (0, 0, 0, 0),
}
glyphs = [{**default_glyph} for i in range(256)]
for i in range(256):
bbox = ftf.getbbox(chr(i), anchor="ls")
glyph_width = bbox[2] - bbox[0]
glyph_offset = (bbox[0], bbox[1])
if pos[0] + glyph_width > params.size[0]:
pos = (0, pos[1] + asc + dsc + 1)
if pos[1] + asc + dsc > params.size[1]:
raise RuntimeError("The resolution is too small to fit all the glyphs! Try to reduce the font size")

draw.text((pos[0] - bbox[0], pos[1]), chr(i), font=ftf, anchor="ls")
glyphs[i]['offset'] = bbox[0]
glyphs[i]['width'] = glyph_width
glyphs[i]['tex_coords'] = ((pos[0]) / params.size[0], (pos[1] - asc) / params.size[1], (pos[0] + glyph_width) / params.size[0], (pos[1] + dsc) / params.size[1])

pos = (pos[0] + glyph_width, pos[1])
font = {
'metrics': (asc, dsc),
'base_size': params.font_size,
'glyphs': [*glyphs],
}

write_header(params.out_fnt, font)
write_glyphs(params.out_fnt, font)
write_tex(params.out_fnt, image, Formats.RGBA8)
93 changes: 8 additions & 85 deletions external/misc/img2tex.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,7 @@
from PIL import Image
import struct
from textures import Formats, write_tex
import argparse
import re
import functools
import io
#import syaz0

formats = {
'RGBA8': 0,
'CMPR': 1
}

def size_type(string):
pattern = re.compile("([0-9]+)[xX]([0-9]+)")
Expand All @@ -23,67 +15,6 @@ def size_type(string):
raise argparse.ArgumentTypeError(msg)
return size

def write_header(out_file, image, format):
out_file.write(struct.pack(">4s", "TEX0".encode("ascii"))) # channel count
out_file.write(formats[format].to_bytes(4, 'big', signed=False))
out_file.write(image.width.to_bytes(4, 'big', signed=False))
out_file.write(image.height.to_bytes(4, 'big', signed=False))

def write_tile(out_file, chn1, chn2):
for j in range(4):
for i in range(4):
pos = (i, j)
val1 = chn1.getpixel(pos)
val2 = chn2.getpixel(pos)
out_file.write(struct.pack(">BB", val1, val2))

def write_raw(out_file, image):
# We save each 4x4 tile one at a time.
for j in range(image.height // 4):
for i in range(image.width // 4):
tile = image.crop((i * 4, j * 4, (i + 1) * 4, (j + 1) * 4))
chnR, chnG, chnB, chnA = tile.split()
write_tile(out_file, chnA, chnR)
write_tile(out_file, chnG, chnB)

def toRGB565(r, g, b):
return (int(r * 31 / 255) << 11) | (int(g * 63 / 255) << 5) | (int(b * 31 / 255))

def quantize_tile(out_file, img):
has_transparency = any(pixel[3] == 0 for pixel in (img.getdata()))
lst = img.quantize(2).getpalette()[:2*3]
palette = [tuple(lst[i:i + 3]) for i in range(0, len(lst), 3)]
if not ((has_transparency and toRGB565(*palette[0]) <= toRGB565(*palette[1])) or ((not has_transparency) and toRGB565(*palette[0]) > toRGB565(*palette[1]))):
tmp = palette[0]
palette[0] = palette[1]
palette[1] = tmp
out_file.write(toRGB565(*palette[0]).to_bytes(2, 'big', signed=False))
out_file.write(toRGB565(*palette[1]).to_bytes(2, 'big', signed=False))
if (has_transparency):
palette.append(tuple((palette[0][i] + palette[1][i]) // 2 for i in range(3)))
else:
palette.append(tuple((palette[0][i] * 2 + palette[1][i]) // 3 for i in range(3)))
palette.append(tuple((palette[1][i] * 2 + palette[0][i]) // 3 for i in range(3)))
for j in range(4):
row = list()
for i in range(4):
pixel = img.getpixel((i, j))
distances = [sum(abs(pixel[a] - palette[k][a]) for a in range(len(palette[k]))) for k in range(len(palette))]
chnA = pixel[3]
min_palette = distances.index(min(distances))
if chnA == 0:
min_palette = 3
row.append(min_palette)
out_file.write(functools.reduce(lambda x, y: x | y, ((int(row[i]) & 0x3) << ((4 - (i + 1)) * 2) for i in range(4))).to_bytes(1, 'big', signed=False))

def write_cmpr(out_file, image):
for v in range(image.height // 8):
for u in range(image.width // 8):
for j in range(2):
for i in range(2):
tile = image.crop((u * 8 + i * 4, v * 8 + j * 4, u * 8 + (i + 1) * 4, v * 8 + (j + 1) * 4))
quantize_tile(out_file, tile)

if __name__ == "__main__":
parser = argparse.ArgumentParser(description='Converts an image into a texture file.')
parser.add_argument('-V', '--version', action='version', version='%(prog)s 0.2')
Expand All @@ -93,25 +24,17 @@ def write_cmpr(out_file, image):
help='Name of the output file')
parser.add_argument('-r', '--resize', type=size_type, metavar='WxH',
help='Resizes the image to a width/height of W/H')
parser.add_argument('-f', '--format', choices=formats.keys(), default='RGBA8',
help='Format to save the picture in')
parser.add_argument('-f', '--format', type=str, choices=[name for name, _ in Formats.__members__.items()],
default='RGBA8', help='Format to save the picture in')
params = parser.parse_args()

# We fetch the picture, and make sure that it has a valid size
image = Image.open(params.image_file).convert('RGBA')
if not params.resize is None:
image = image.resize(params.resize)
if image.width % 4 != 0 or image.height % 4 != 0:
raise RuntimeError("image size has to be a multiple of 4")
if params.format == "CMPR" and (image.width % 8 != 0 and image.height % 8 != 0):
raise RuntimeError("image size has to be a multiple of 8 when using CMPR")
buffer = io.BytesIO()

write_header(buffer, image, params.format)
if params.format in ["RGBA8"] and (image.width % 4 != 0 or image.height % 4 != 0):
raise RuntimeError("image size has to be a multiple of 4 when using RGBA8")
if params.format in ["CMPR", "I8"] and (image.width % 8 != 0 and image.height % 8 != 0):
raise RuntimeError("image size has to be a multiple of 8 when using CMPR or I8 formats")

if params.format == "RGBA8":
write_raw(buffer, image)
elif params.format == "CMPR":
write_cmpr(buffer, image)
#params.out_tex.write(syaz0.compress(buffer.getvalue()))
params.out_tex.write(buffer.getvalue())
write_tex(params.out_tex, image, Formats[params.format])
1 change: 1 addition & 0 deletions external/misc/textures/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .textures import Formats, write_tex
107 changes: 107 additions & 0 deletions external/misc/textures/textures.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
from enum import Enum, unique
from PIL import Image
import struct
import re
import functools
import io
#import syaz0


@unique
class Formats(Enum):
RGBA8 = 0
CMPR = 1
I8 = 2


def write_header(out_file, image: Image, format: Formats):
out_file.write(struct.pack(">4s", "TEX0".encode("ascii"))) # channel count
out_file.write(format.value.to_bytes(4, 'big', signed=False))
out_file.write(image.width.to_bytes(4, 'big', signed=False))
out_file.write(image.height.to_bytes(4, 'big', signed=False))

def write_tile(out_file, chn):
for j in range(8):
for i in range(8):
pos = (i, j)
val = chn.getpixel(pos)
out_file.write(struct.pack(">B", val))

def write_tile2(out_file, chn1, chn2):
for j in range(4):
for i in range(4):
pos = (i, j)
val1 = chn1.getpixel(pos)
val2 = chn2.getpixel(pos)
out_file.write(struct.pack(">BB", val1, val2))

def write_raw(out_file, image):
image = image.convert('RGBA')
# We save each 4x4 tile one at a time.
for j in range(image.height // 4):
for i in range(image.width // 4):
tile = image.crop((i * 4, j * 4, (i + 1) * 4, (j + 1) * 4))
chnR, chnG, chnB, chnA = tile.split()
write_tile2(out_file, chnA, chnR)
write_tile2(out_file, chnG, chnB)

def write_i8(out_file, image):
image = image.convert(mode="L", dither=None)
# We save each 4x4 tile one at a time.
for j in range(image.height // 8):
for i in range(image.width // 8):
tile = image.crop((i * 8, j * 8, (i + 1) * 8, (j + 1) * 8))
write_tile(out_file, tile)

def toRGB565(r, g, b):
return (int(r * 31 / 255) << 11) | (int(g * 63 / 255) << 5) | (int(b * 31 / 255))

def quantize_tile(out_file, img):
img = img.convert('RGBA')
has_transparency = any(pixel[3] == 0 for pixel in (img.getdata()))
lst = img.quantize(2).getpalette()[:2*3]
palette = [tuple(lst[i:i + 3]) for i in range(0, len(lst), 3)]
if not ((has_transparency and toRGB565(*palette[0]) <= toRGB565(*palette[1])) or ((not has_transparency) and toRGB565(*palette[0]) > toRGB565(*palette[1]))):
tmp = palette[0]
palette[0] = palette[1]
palette[1] = tmp
out_file.write(toRGB565(*palette[0]).to_bytes(2, 'big', signed=False))
out_file.write(toRGB565(*palette[1]).to_bytes(2, 'big', signed=False))
if (has_transparency):
palette.append(tuple((palette[0][i] + palette[1][i]) // 2 for i in range(3)))
else:
palette.append(tuple((palette[0][i] * 2 + palette[1][i]) // 3 for i in range(3)))
palette.append(tuple((palette[1][i] * 2 + palette[0][i]) // 3 for i in range(3)))
for j in range(4):
row = list()
for i in range(4):
pixel = img.getpixel((i, j))
distances = [sum(abs(pixel[a] - palette[k][a]) for a in range(len(palette[k]))) for k in range(len(palette))]
chnA = pixel[3]
min_palette = distances.index(min(distances))
if chnA == 0:
min_palette = 3
row.append(min_palette)
out_file.write(functools.reduce(lambda x, y: x | y, ((int(row[i]) & 0x3) << ((4 - (i + 1)) * 2) for i in range(4))).to_bytes(1, 'big', signed=False))

def write_cmpr(out_file, image):
for v in range(image.height // 8):
for u in range(image.width // 8):
for j in range(2):
for i in range(2):
tile = image.crop((u * 8 + i * 4, v * 8 + j * 4, u * 8 + (i + 1) * 4, v * 8 + (j + 1) * 4))
quantize_tile(out_file, tile)

def write_tex(out_buf, img: Image, fmt: Formats):
buffer = io.BytesIO()

write_header(out_buf, img, fmt)

if fmt == Formats.RGBA8:
write_raw(buffer, img)
elif fmt == Formats.CMPR:
write_cmpr(buffer, img)
elif fmt == Formats.I8:
write_i8(buffer, img)
#out_buf.write(syaz0.compress(buffer.getvalue()))
out_buf.write(buffer.getvalue())
2 changes: 1 addition & 1 deletion include/fifo_queue.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ class FIFOQueue {

public:
FIFOQueue();
static void renderItems(_FIFOQueue& Queue, Font& font);
static void renderItems(_FIFOQueue& Queue);
static void push(const char *msg, _FIFOQueue& Queue);
static void push(const char *msg, _FIFOQueue& Queue, int RGBA);
};
Expand Down
Loading

0 comments on commit 4d79159

Please sign in to comment.