Smeargle 0.5.0

current
Kiyoshi Aman 2018-03-14 10:53:03 -05:00
parent 46eb139637
commit 49aad5bdbb
3 changed files with 135 additions and 53 deletions

11
example.json Normal file
View File

@ -0,0 +1,11 @@
{
"name": "Example",
"fonts": {
"Melissa 8": "melissa8.json"
}, "scripts": {
"test.txt": {
"max_tiles_per_line": 8,
"font": "Melissa 8"
}
}
}

View File

@ -1,16 +1,12 @@
Smeargle 0.4.0 readme
---------------------
Usage: smeargle.py font.json script.txt
Usage: smeargle.py game.json
font.json is a JSON document which describes the font and provides a mapping
of characters to font indexes and font widths.
script.txt is a plaintext document without formatting which is rendered as an
image in PNG with a limited palette.
game.json is a file which follows the Game JSON format outlined below.
Output
------
Smeargle outputs three files, at present:
Smeargle outputs three files per script, at present:
* <script>_raw.png is the undeduplicated rendering of the script with the
specified font.
@ -19,12 +15,31 @@ Smeargle outputs three files, at present:
* <script>_index.txt provides a mapping of deduplicated tiles to the original
text.
game.json format
----------------
The following format MUST be observed, or you will not get the output you want.
Remove '//' and everything following it in each line if you plan to cop/paste
this example for your own use. Do not leave a trailing comma on the final entry
in each object or array.
{
"name": "Example", // The name of the game, for reference.
"fonts": {
"Melissa 8": "melissa8.json" // Font name and its filename.
}, "scripts": {
"test.txt": { // Script filename.
"max_tiles_per_line": 8, // Omit or set to 0 for unlimited tiles.
"font": "Melissa 8" // Reference to the font table, above.
}
}
}
font.json format
----------------
The following format MUST be observed, or you will not get the output you want.
Remove '//' and everything following it in each line if you plan to copy/paste
this example for your own use. Do not leave a trailing comma on the final entry
in the map.
in each object or array.
{
"font_name": "Example", // Human-readable, not currently used
@ -37,10 +52,12 @@ in the map.
[0, 0, 0] // A color in R,G,B format.
],
"map": { // character -> index & width
" ": {"index": 115, "width": 4}, // Must be a blank tile somewher
" ": {"index": 115, "width": 4}, // Must be a blank tile somewhere
}
}
The first color in the palette is assumed to be the background color.
porygon.py
----------
Usage: porygon.py image format
@ -50,6 +67,12 @@ arguments to see what formats are available.
Changelog
---------
0.5.0
* Introduce a master 'game.json' file in order to enable batch processing, for
games which use multiple scripts that have different fonts or rendering
requirements.
* Emit an error if no arguments are given.
0.4.0
* A complete rewrite of Smeargle to make it more modular.
* Implemented a palette feature in order to ensure strict palette ordering in

View File

@ -72,7 +72,8 @@ class Font:
return sum(self.table[x]['width'] for x in text)
class Script:
def __init__(self, filename):
def __init__(self, filename, max_tiles=0):
self.max_tiles = max_tiles
with open(filename, mode='r', encoding='UTF-8') as f:
self._text = f.read().split('\n')
@ -81,12 +82,20 @@ class Script:
def render_lines(self, font):
table = font.table
lines = []
max_tiles = self.max_tiles * font.width
for line in self._text:
if len(line) < 1:
continue
length = font.length(line)
length = ceil(length / font.width) * font.width
if max_tiles > 0 and length > max_tiles:
print('WARNING: "{}" exceeds {} tiles by {}px; truncating.'.format(
line,
max_tiles,
length - max_tiles
))
length = max_tiles
image = QImage(length, font.height, QImage.Format_RGB32)
image.fill(font.palette[0])
pos = 0
@ -94,6 +103,8 @@ class Script:
self._painter.begin(image)
for glyph in line:
width = font.table[glyph]['width']
if pos + width >= max_tiles:
break
self._painter.drawImage(pos, 0, font.index(font.table[glyph]['index'] - 1))
pos += width
@ -174,59 +185,96 @@ class Script:
def render_tiles_to_file(self, font, tiles, filename):
self.render_tiles(font, tiles).save(filename, 'PNG')
class Game:
def __init__(self, filename):
with open(filename, mode='rb') as f:
self._data = json.load(f)
self._fonts = {}
self._scripts = {}
for name, file in self._data['fonts']:
self._fonts[name] = Font(file)
for script, data in self._data['scripts']:
if 'max_tiles_per_line' not in data:
data['max_tiles_per_line'] = 0
self._scripts[script] = (
Script(script, data['max_tiles_per_line']),
self._fonts[data['font']]
)
@property
def fonts(self):
return tuple(self._fonts.keys())
@property
def scripts(self):
return tuple(self._scripts.keys())
def render_script(self, script, output=False):
if script not in self._scripts.keys():
raise KeyError('unknown script')
filebase = os.path.split(script)[-1]
name, ext = os.path.splitext(filebase)
output_raw = os.path.join(render_path, name + '_raw.png')
output_comp = os.path.join(render_path, name + '_compressed.png')
output_map = os.path.join(render_path, name + '_index.txt')
script, font = self._scripts[script]
if output: print('Rendering text...', end='')
lines = script.render_lines(font)
if output: print('done.')
if output: print("Generating tilemap...", end='')
(compressed, raw, map_index, indexes, total, unique) = script.generate_tilemap(font, lines)
if output: print("{} tiles generated, {} unique.".format(total, unique))
if output: print('Writing compressed tiles...', end='')
script.render_tiles_to_file(font, compressed, output_comp)
if output: print('done.')
if output: print('Writing raw tiles...', end='')
script.render_tiles_to_file(font, raw, output_raw)
if output: print('done.')
if output: print('Writing map index...', end='')
with open(output_map, mode='wt') as f:
for text, index in indexes:
f.write('{} = {}\n'.format(text, index))
if output: print('done.')
if output:
print()
print('Raw tiles: ', output_raw)
print('Compressed: ', output_comp)
print('Tile<->text: ', output_map)
if __name__ == '__main__':
import sys
import os.path
if len(sys.argv) < 1:
print('Usage: smeargle.py font.json script.txt')
print('\nPlease see the included readme.txt for documentation on the font metadata.')
print('Usage: smeargle.py game.json [output_directory]')
print('\nPlease see the included readme.txt for documentation on file formats.')
sys.exit(-1)
app = QGuiApplication(sys.argv)
render_path = sys.argv[2] if len(sys.argv) > 2 else 'output'
font = sys.argv[1]
script = sys.argv[2]
render_path = sys.argv[3] if len(sys.argv) > 3 else 'output'
filebase = os.path.split(script)[-1]
name, ext = os.path.splitext(filebase)
output_raw = os.path.join(render_path, name + '_raw.png')
output_comp = os.path.join(render_path, name + '_compressed.png')
output_map = os.path.join(render_path, name + '_index.txt')
print("Loading font...", end='')
font = Font(font)
print("done.")
print("Loading script...", end='')
script = Script(script)
print("done.")
print("Rendering text...", end='')
lines = script.render_lines(font)
print("done.")
print("Generating tilemap...", end='')
(compressed, raw, map_index, indexes, total, unique) = script.generate_tilemap(font, lines)
print("{} tiles generated, {} unique.".format(total, unique))
print('Writing compressed tiles...', end='')
script.render_tiles_to_file(font, compressed, output_comp)
print('Loading game data from {}...'.format(sys.argv[1]), end='')
game = Game(sys.argv[1])
print('done.')
print('Writing raw tiles...', end='')
script.render_tiles_to_file(font, raw, output_raw)
print('done.')
for script in game.scripts():
print('Processing {}...'.format(script))
game.render_script(script, output=True)
print('{} processed.'.format(script))
print('Writing map index...', end='')
with open(output_map, mode='wt') as f:
for text, index in indexes:
f.write('{} = {}\n'.format(text, index))
print('done.')
print("Rendering text...", end='')
lines = script.render_lines(font)
print("done.")
print()
print('Raw tiles: ', output_raw)
print('Compressed: ', output_comp)
print('Tile<->text: ', output_map)