Smeargle 0.5.0
parent
46eb139637
commit
49aad5bdbb
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"name": "Example",
|
||||
"fonts": {
|
||||
"Melissa 8": "melissa8.json"
|
||||
}, "scripts": {
|
||||
"test.txt": {
|
||||
"max_tiles_per_line": 8,
|
||||
"font": "Melissa 8"
|
||||
}
|
||||
}
|
||||
}
|
41
readme.txt
41
readme.txt
|
@ -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
|
||||
|
|
128
smeargle.py
128
smeargle.py
|
@ -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,20 +185,36 @@ class Script:
|
|||
def render_tiles_to_file(self, font, tiles, filename):
|
||||
self.render_tiles(font, tiles).save(filename, 'PNG')
|
||||
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
import os.path
|
||||
class Game:
|
||||
def __init__(self, filename):
|
||||
with open(filename, mode='rb') as f:
|
||||
self._data = json.load(f)
|
||||
|
||||
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.')
|
||||
sys.exit(-1)
|
||||
self._fonts = {}
|
||||
self._scripts = {}
|
||||
|
||||
app = QGuiApplication(sys.argv)
|
||||
for name, file in self._data['fonts']:
|
||||
self._fonts[name] = Font(file)
|
||||
|
||||
font = sys.argv[1]
|
||||
script = sys.argv[2]
|
||||
render_path = sys.argv[3] if len(sys.argv) > 3 else 'output'
|
||||
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)
|
||||
|
@ -196,37 +223,58 @@ if __name__ == '__main__':
|
|||
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.")
|
||||
script, font = self._scripts[script]
|
||||
|
||||
print("Loading script...", end='')
|
||||
script = Script(script)
|
||||
print("done.")
|
||||
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 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'
|
||||
|
||||
print('Loading game data from {}...'.format(sys.argv[1]), end='')
|
||||
game = Game(sys.argv[1])
|
||||
print('done.')
|
||||
|
||||
for script in game.scripts():
|
||||
print('Processing {}...'.format(script))
|
||||
game.render_script(script, output=True)
|
||||
print('{} processed.'.format(script))
|
||||
|
||||
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('done.')
|
||||
|
||||
print('Writing raw tiles...', end='')
|
||||
script.render_tiles_to_file(font, raw, output_raw)
|
||||
print('done.')
|
||||
|
||||
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()
|
||||
print('Raw tiles: ', output_raw)
|
||||
print('Compressed: ', output_comp)
|
||||
print('Tile<->text: ', output_map)
|
||||
|
|
Loading…
Reference in New Issue