2017-12-07 14:42:22 +00:00
|
|
|
#!/usr/bin/env python3
|
2017-12-07 14:38:28 +00:00
|
|
|
# Copyright 2018 Kiyoshi Aman
|
|
|
|
#
|
|
|
|
# Permission to use, copy, modify, and/or distribute this software for any
|
|
|
|
# purpose with or without fee is hereby granted, provided that the above
|
|
|
|
# copyright notice and this permission notice appear in all copies.
|
|
|
|
#
|
|
|
|
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
|
|
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
|
|
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
|
|
|
|
# SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
|
|
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
|
|
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
|
|
|
|
# IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
|
|
|
|
|
|
import sys
|
|
|
|
import json
|
|
|
|
import os.path as op
|
|
|
|
from math import ceil
|
|
|
|
|
|
|
|
from PyQt5.QtGui import QImage, QPixmap, QPainter, QGuiApplication
|
|
|
|
|
|
|
|
class Font(QPixmap):
|
|
|
|
"""A simple class for basic bitmap fonts."""
|
|
|
|
def __init__(self, filename, width=8, height=8):
|
|
|
|
super().__init__(filename)
|
|
|
|
self._width = width
|
|
|
|
self._height = height
|
|
|
|
|
|
|
|
self._tpr = int(self.width() / self._width)
|
|
|
|
|
|
|
|
def index(self, tile):
|
|
|
|
row = int(tile / self._tpr)
|
|
|
|
column = tile % self._tpr
|
|
|
|
|
|
|
|
x = column * self._width
|
|
|
|
y = row * self._height
|
|
|
|
if (x > self.width()) or (y > self.height()):
|
|
|
|
raise KeyError('index')
|
|
|
|
|
|
|
|
return self.copy(x, y, self._width, self._height)
|
|
|
|
|
|
|
|
def main():
|
|
|
|
if len(sys.argv) < 2:
|
|
|
|
print(
|
|
|
|
"""Usage: smeargle.py font.json script
|
|
|
|
|
|
|
|
font.json is the metadata file associated with the font.
|
|
|
|
|
|
|
|
script is the text to be typeset.
|
|
|
|
|
|
|
|
The PNG and raw binary outputs will be exported using the script filename
|
|
|
|
as a base. (ex: test.txt -> test.png & test.bin)
|
|
|
|
"""
|
|
|
|
)
|
|
|
|
sys.exit(1)
|
|
|
|
|
|
|
|
app = QGuiApplication(sys.argv)
|
|
|
|
|
|
|
|
(font, script) = sys.argv[1:3]
|
|
|
|
font_info = None
|
|
|
|
script_base, ext = op.splitext(script)
|
2017-12-07 14:46:56 +00:00
|
|
|
if not op.exists('output/'):
|
|
|
|
import os
|
|
|
|
os.mkdir('output')
|
|
|
|
script_base = 'output/' + script_base
|
2017-12-07 14:38:28 +00:00
|
|
|
output = script_base + '.png'
|
2017-12-17 00:44:50 +00:00
|
|
|
output_full = script_base + '_full.png'
|
2017-12-07 14:38:28 +00:00
|
|
|
output_raw = script_base + '.bin'
|
|
|
|
|
|
|
|
print('Loading font information...')
|
|
|
|
with open(font, mode='rb') as f:
|
|
|
|
font_info = json.load(f)
|
|
|
|
|
|
|
|
print('Loading font data...')
|
|
|
|
font_data = Font(font_info['filename'], font_info['width'], font_info['height'])
|
|
|
|
|
|
|
|
print('Loading text...')
|
|
|
|
with open(script, mode='r', encoding='utf-8', errors='replace') as f:
|
|
|
|
text = f.read()
|
|
|
|
|
|
|
|
images = []
|
|
|
|
fmap = font_info['map']
|
|
|
|
width = font_info['width']
|
|
|
|
height = font_info['height']
|
|
|
|
line = 0
|
|
|
|
lines = text.split('\n')
|
|
|
|
painter = QPainter()
|
|
|
|
filler = font_data.index(fmap[' ']['index']).toImage().pixel(0, 0)
|
|
|
|
|
|
|
|
print('Rendering text...')
|
|
|
|
scriptmap = {}
|
|
|
|
while len(images) < text.count('\n'):
|
|
|
|
position = 0
|
|
|
|
|
|
|
|
temp = QImage(width * len(lines[line]), height, QImage.Format_RGB32)
|
|
|
|
temp.fill(filler)
|
|
|
|
|
|
|
|
painter.begin(temp)
|
|
|
|
for char in lines[line]:
|
|
|
|
if char in fmap.keys():
|
|
|
|
glyph = font_data.index(fmap[char]['index'] - 1)
|
|
|
|
painter.drawImage(position, 0, glyph.toImage())
|
|
|
|
painter.save()
|
|
|
|
position += fmap[char]['width']
|
|
|
|
else:
|
|
|
|
print('WARNING: unknown glyph "{}"'.format(char))
|
|
|
|
painter.end()
|
|
|
|
temp = temp.copy(0, 0, ceil(position / width) * width, height)
|
|
|
|
images.append(temp)
|
|
|
|
scriptmap[lines[line]] = temp
|
|
|
|
line += 1
|
|
|
|
|
|
|
|
print('Text rendered. Generating tilemap...')
|
|
|
|
tilemap = {}
|
2017-12-17 00:44:50 +00:00
|
|
|
tileset = []
|
2017-12-07 14:38:28 +00:00
|
|
|
tilemap_index = {}
|
|
|
|
counter_unique = 0
|
|
|
|
counter_total = 0
|
|
|
|
for line, image in scriptmap.items():
|
|
|
|
iwidth = image.width()
|
|
|
|
tiles = int(iwidth / width)
|
|
|
|
column = 0
|
|
|
|
text_tiles = []
|
|
|
|
|
|
|
|
while tiles > 0:
|
|
|
|
tile = image.copy(column, 0, width, height).convertToFormat(QImage.Format_Indexed8)
|
|
|
|
data = bytearray()
|
|
|
|
|
|
|
|
for y in range(tile.height()):
|
|
|
|
for x in range(tile.width()):
|
|
|
|
data.append(tile.pixelIndex(x, y))
|
|
|
|
|
|
|
|
data = bytes(data)
|
|
|
|
|
|
|
|
if data not in tilemap.keys():
|
|
|
|
tilemap[data] = tile
|
2017-12-08 06:32:48 +00:00
|
|
|
tilemap_index[data] = '0x{:02x}'.format(counter_unique)
|
2017-12-07 14:38:28 +00:00
|
|
|
counter_unique += 1
|
|
|
|
|
2017-12-17 00:44:50 +00:00
|
|
|
tileset.append(tile)
|
|
|
|
|
2017-12-07 14:38:28 +00:00
|
|
|
text_tiles.append(tilemap_index[data])
|
|
|
|
|
|
|
|
counter_total += 1
|
|
|
|
|
|
|
|
column += width
|
|
|
|
tiles -= 1
|
|
|
|
|
|
|
|
scriptmap[line] = text_tiles
|
|
|
|
|
|
|
|
print('Tilemap generated, with {} tiles mapped ({} unique). Rendering tilemap...'.format(counter_total, counter_unique))
|
|
|
|
image = QImage(width * 16, ceil(len(tilemap.keys()) / 16) * height, QImage.Format_RGB32)
|
|
|
|
image.fill(filler)
|
|
|
|
painter.begin(image)
|
|
|
|
(row, column) = (0, 0)
|
|
|
|
for data, tile in tilemap.items():
|
|
|
|
painter.drawImage(column, row, tile)
|
|
|
|
if column < (width * 16 - width):
|
|
|
|
column += width
|
|
|
|
else:
|
|
|
|
column = 0
|
|
|
|
row += height
|
|
|
|
painter.end()
|
2017-12-08 23:01:02 +00:00
|
|
|
image = image.convertToFormat(QImage.Format_Indexed8)
|
2017-12-07 14:38:28 +00:00
|
|
|
|
|
|
|
print('Writing line <-> tilemap indexing...')
|
|
|
|
with open(script_base + '_index.txt', mode='w') as f:
|
|
|
|
for line, index in scriptmap.items():
|
|
|
|
f.write('{} -> {}\n'.format(line, ', '.join(index)))
|
|
|
|
|
|
|
|
print('Saving rendered tilemap...')
|
|
|
|
file = QPixmap.fromImage(image)
|
|
|
|
file.save(output, 'PNG')
|
|
|
|
|
2017-12-17 00:44:50 +00:00
|
|
|
print('Rendering undeduplicated tiles...')
|
|
|
|
image = QImage(width * 16, ceil(len(tileset) / 16) * height, QImage.Format_RGB32)
|
|
|
|
image.fill(filler)
|
|
|
|
painter.begin(image)
|
|
|
|
(row, column) = (0, 0)
|
|
|
|
for tile in tileset:
|
|
|
|
painter.drawImage(column, row, tile)
|
|
|
|
if column < (width * 15):
|
|
|
|
column += width
|
|
|
|
else:
|
|
|
|
column = 0
|
|
|
|
row += height
|
|
|
|
painter.end()
|
|
|
|
image = image.convertToFormat(QImage.Format_Indexed8)
|
|
|
|
file = QPixmap.fromImage(image)
|
|
|
|
file.save(output_full)
|
|
|
|
|
2017-12-07 14:38:28 +00:00
|
|
|
if __name__ == '__main__':
|
|
|
|
main()
|