Merge branch 'atlas_tools' of Aerdan/smeargle into master
commit
9252c663d4
31
readme.txt
31
readme.txt
|
@ -1,4 +1,4 @@
|
||||||
Smeargle 0.4.0 readme
|
Smeargle 0.6.0 readme
|
||||||
---------------------
|
---------------------
|
||||||
Usage: smeargle.py game.json
|
Usage: smeargle.py game.json
|
||||||
|
|
||||||
|
@ -15,6 +15,9 @@ Smeargle outputs three files per script, at present:
|
||||||
* <script>_index.txt provides a mapping of deduplicated tiles to the original
|
* <script>_index.txt provides a mapping of deduplicated tiles to the original
|
||||||
text.
|
text.
|
||||||
|
|
||||||
|
These filenames can be configured on an individual script basis; see game.json
|
||||||
|
documentation below.
|
||||||
|
|
||||||
game.json format
|
game.json format
|
||||||
----------------
|
----------------
|
||||||
The following format MUST be observed, or you will not get the output you want.
|
The following format MUST be observed, or you will not get the output you want.
|
||||||
|
@ -23,13 +26,19 @@ this example for your own use. Do not leave a trailing comma on the final entry
|
||||||
in each object or array.
|
in each object or array.
|
||||||
|
|
||||||
{
|
{
|
||||||
"name": "Example", // The name of the game, for reference.
|
"name": "Example", // The name of the game, for reference.
|
||||||
"fonts": {
|
"fonts": {
|
||||||
"Melissa 8": "melissa8.json" // Font name and its filename.
|
"Melissa 8": "melissa8.json" // Font name and its filename.
|
||||||
}, "scripts": {
|
}, "scripts": {
|
||||||
"test.txt": { // Script filename.
|
"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": "Melissa 8" // Reference to the font table, above.
|
"max_tiles_per_line": 8, // Optional: set to 0 for unlimited tiles.
|
||||||
|
"output_format": "thingy", // Optional: Output format for tilemap. "thingy", "atlas"
|
||||||
|
"leading_zeroes": true, // Optional: Forces 16-bit tilemap output (i.e. 0x0012 instead of 0x12)
|
||||||
|
"tile_offset": 256, // Optional: Constant to add to tile index (first tile: 0x0000 + 256 = 0x0100)
|
||||||
|
"raw_fn": "ex_raw.png", // Optional: Output filename for raw graphic tile data.
|
||||||
|
"deduped_fn": "ex_comp.png", // Optional: Output filename for deduped tile data.
|
||||||
|
"tilemap_fn": "example.tbl" // Optional: Output filename for tilemap text.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -67,6 +76,16 @@ arguments to see what formats are available.
|
||||||
|
|
||||||
Changelog
|
Changelog
|
||||||
---------
|
---------
|
||||||
|
0.6.0
|
||||||
|
* Adds several optional arguments to script json elements:
|
||||||
|
** output_format: determines how the tilemap text file gets rendered. Possible values are "atlas", "thingy", null
|
||||||
|
** leading_zeroes: Forces tilemap output to always be 16-bit.
|
||||||
|
** tile_offset: Adds a constant value to the tile index.
|
||||||
|
Useful if you want the tilemap to start counting somewhere other than zero.
|
||||||
|
** raw_fn: Filename for raw tile graphic png output.
|
||||||
|
** deduped_fn: Filename for compressed tile graphic png output.
|
||||||
|
** tilemap_fn: Filename for text index tilemap file.
|
||||||
|
|
||||||
0.5.0
|
0.5.0
|
||||||
* Introduce a master 'game.json' file in order to enable batch processing, for
|
* Introduce a master 'game.json' file in order to enable batch processing, for
|
||||||
games which use multiple scripts that have different fonts or rendering
|
games which use multiple scripts that have different fonts or rendering
|
||||||
|
|
99
smeargle.py
99
smeargle.py
|
@ -1,13 +1,14 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
import json
|
import json
|
||||||
from math import ceil
|
from math import ceil, floor
|
||||||
|
|
||||||
from PyQt5.QtGui import QGuiApplication, QPixmap, QImage, QColor, QPainter
|
from PyQt5.QtGui import QGuiApplication, QPixmap, QImage, QColor, QPainter
|
||||||
|
|
||||||
|
|
||||||
class Font:
|
class Font:
|
||||||
"""A simple class for managing Smeargle's font data."""
|
"""A simple class for managing Smeargle's font data."""
|
||||||
|
|
||||||
def __init__(self, filename):
|
def __init__(self, filename):
|
||||||
"""Creates the font object.
|
"""Creates the font object.
|
||||||
|
|
||||||
|
@ -75,8 +76,15 @@ class Font:
|
||||||
|
|
||||||
|
|
||||||
class Script:
|
class Script:
|
||||||
def __init__(self, filename, max_tiles=0):
|
def __init__(self, filename, raw_fn=None, deduped_fn=None, tilemap_fn=None,
|
||||||
|
max_tiles=0, output_format=None, tile_offset=0, leading_zeroes=False):
|
||||||
self.max_tiles = max_tiles
|
self.max_tiles = max_tiles
|
||||||
|
self.output_format = output_format
|
||||||
|
self.tile_offset = tile_offset
|
||||||
|
self.leading_zeroes = leading_zeroes
|
||||||
|
self.raw_fn = raw_fn
|
||||||
|
self.deduped_fn = deduped_fn
|
||||||
|
self.tilemap_fn = tilemap_fn
|
||||||
with open(filename, mode='r', encoding='UTF-8') as f:
|
with open(filename, mode='r', encoding='UTF-8') as f:
|
||||||
self._text = f.read().split('\n')
|
self._text = f.read().split('\n')
|
||||||
|
|
||||||
|
@ -117,8 +125,7 @@ class Script:
|
||||||
|
|
||||||
return lines
|
return lines
|
||||||
|
|
||||||
@staticmethod
|
def generate_tilemap(self, font, lines):
|
||||||
def generate_tilemap(font, lines):
|
|
||||||
tilemap = {}
|
tilemap = {}
|
||||||
raw_tiles = []
|
raw_tiles = []
|
||||||
compressed_tiles = []
|
compressed_tiles = []
|
||||||
|
@ -152,7 +159,27 @@ class Script:
|
||||||
if data not in tilemap.keys():
|
if data not in tilemap.keys():
|
||||||
tilemap[data] = tile
|
tilemap[data] = tile
|
||||||
compressed_tiles.append(tile)
|
compressed_tiles.append(tile)
|
||||||
map_idx[data] = '0x{:02x}'.format(unique)
|
if self.output_format == 'atlas':
|
||||||
|
index = unique + self.tile_offset
|
||||||
|
upper_val = int(floor(index / 256))
|
||||||
|
lower_val = int(index % 256)
|
||||||
|
if upper_val > 0 or self.leading_zeroes is True:
|
||||||
|
map_idx[data] = "<${:02x}><${:02x}>".format(upper_val, lower_val)
|
||||||
|
else:
|
||||||
|
map_idx[data] = "<${:02x}>".format(lower_val)
|
||||||
|
elif self.output_format == 'thingy':
|
||||||
|
index = unique + self.tile_offset
|
||||||
|
upper_val = int(floor(index / 256))
|
||||||
|
lower_val = int(index % 256)
|
||||||
|
if upper_val > 0 or self.leading_zeroes is True:
|
||||||
|
map_idx[data] = "{:02x}{:02x}".format(upper_val, lower_val)
|
||||||
|
else:
|
||||||
|
map_idx[data] = "{:02x}".format(lower_val)
|
||||||
|
else:
|
||||||
|
if self.leading_zeroes:
|
||||||
|
map_idx[data] = '0x{:04x}'.format(unique + self.tile_offset)
|
||||||
|
else:
|
||||||
|
map_idx[data] = '0x{:02x}'.format(unique + self.tile_offset)
|
||||||
unique += 1
|
unique += 1
|
||||||
|
|
||||||
raw_tiles.append(tile)
|
raw_tiles.append(tile)
|
||||||
|
@ -161,7 +188,10 @@ class Script:
|
||||||
column += font.width
|
column += font.width
|
||||||
count -= 1
|
count -= 1
|
||||||
|
|
||||||
indexes.append((text, ' '.join(tile_idx)))
|
if self.output_format is None:
|
||||||
|
indexes.append((text, ' '.join(tile_idx)))
|
||||||
|
else:
|
||||||
|
indexes.append((text, ''.join(tile_idx)))
|
||||||
return compressed_tiles, raw_tiles, map_idx, indexes, total, unique
|
return compressed_tiles, raw_tiles, map_idx, indexes, total, unique
|
||||||
|
|
||||||
def render_tiles(self, font, tiles):
|
def render_tiles(self, font, tiles):
|
||||||
|
@ -201,11 +231,36 @@ class Game:
|
||||||
for name, file in self._data['fonts'].items():
|
for name, file in self._data['fonts'].items():
|
||||||
self._fonts[name] = Font(file)
|
self._fonts[name] = Font(file)
|
||||||
|
|
||||||
|
valid_formats = ['thingy', 'atlas', None]
|
||||||
|
defaults = {
|
||||||
|
'max_tiles_per_line': 0,
|
||||||
|
'output_format': None,
|
||||||
|
'tile_offset': 0,
|
||||||
|
'leading_zeroes': False,
|
||||||
|
'raw_fn': None,
|
||||||
|
'deduped_fn': None,
|
||||||
|
'tilemap_fn': None
|
||||||
|
}
|
||||||
|
|
||||||
for script, data in self._data['scripts'].items():
|
for script, data in self._data['scripts'].items():
|
||||||
if 'max_tiles_per_line' not in data:
|
|
||||||
data['max_tiles_per_line'] = 0
|
# Add defaults to script data if not present
|
||||||
|
for k, v in defaults.items():
|
||||||
|
if k not in data:
|
||||||
|
data[k] = v
|
||||||
|
|
||||||
|
if data['output_format'] not in valid_formats:
|
||||||
|
raise ValueError("output_format must be one of {} or omitted entirely".format(valid_formats[:-1]))
|
||||||
|
|
||||||
self._scripts[script] = (
|
self._scripts[script] = (
|
||||||
Script(script, data['max_tiles_per_line']),
|
Script(filename=script,
|
||||||
|
raw_fn=data['raw_fn'],
|
||||||
|
deduped_fn=data['deduped_fn'],
|
||||||
|
tilemap_fn=data['tilemap_fn'],
|
||||||
|
max_tiles=data['max_tiles_per_line'],
|
||||||
|
output_format=data['output_format'],
|
||||||
|
tile_offset=data['tile_offset'],
|
||||||
|
leading_zeroes=data['leading_zeroes']),
|
||||||
self._fonts[data['font']]
|
self._fonts[data['font']]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -224,12 +279,23 @@ class Game:
|
||||||
filebase = os.path.split(script)[-1]
|
filebase = os.path.split(script)[-1]
|
||||||
name, ext = os.path.splitext(filebase)
|
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]
|
script, font = self._scripts[script]
|
||||||
|
|
||||||
|
if script.raw_fn is None:
|
||||||
|
output_raw = os.path.join(render_path, name + '_raw.png')
|
||||||
|
else:
|
||||||
|
output_raw = os.path.join(render_path, script.raw_fn)
|
||||||
|
|
||||||
|
if script.deduped_fn is None:
|
||||||
|
output_comp = os.path.join(render_path, name + '_compressed.png')
|
||||||
|
else:
|
||||||
|
output_comp = os.path.join(render_path, script.deduped_fn)
|
||||||
|
|
||||||
|
if script.tilemap_fn is None:
|
||||||
|
output_map = os.path.join(render_path, name + '_index.txt')
|
||||||
|
else:
|
||||||
|
output_map = os.path.join(render_path, script.tilemap_fn)
|
||||||
|
|
||||||
if output: print('Rendering text...')
|
if output: print('Rendering text...')
|
||||||
lines = script.render_lines(font)
|
lines = script.render_lines(font)
|
||||||
if output: print('Text rendered.')
|
if output: print('Text rendered.')
|
||||||
|
@ -249,7 +315,10 @@ class Game:
|
||||||
if output: print('Writing map index...', end='')
|
if output: print('Writing map index...', end='')
|
||||||
with open(output_map, mode='wt') as f:
|
with open(output_map, mode='wt') as f:
|
||||||
for text, index in indexes:
|
for text, index in indexes:
|
||||||
f.write('{} = {}\n'.format(text, index))
|
if script.output_format == 'thingy':
|
||||||
|
f.write('{}={}\n'.format(index, text))
|
||||||
|
else:
|
||||||
|
f.write('{} = {}\n'.format(text, index))
|
||||||
if output: print('done.')
|
if output: print('done.')
|
||||||
|
|
||||||
if output:
|
if output:
|
||||||
|
@ -262,7 +331,7 @@ class Game:
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
|
|
||||||
if len(sys.argv) < 1:
|
if len(sys.argv) < 1:
|
||||||
print('Usage: smeargle.py game.json [output_directory]')
|
print('Usage: smeargle.py game.json [output_directory]')
|
||||||
print('\nPlease see the included readme.txt for documentation on file formats.')
|
print('\nPlease see the included readme.txt for documentation on file formats.')
|
||||||
|
|
Loading…
Reference in New Issue