Smeargle 0.7.0

current
Kiyoshi Aman 2018-10-11 11:11:57 -05:00
parent 78f679966d
commit e97b943689
7 changed files with 511 additions and 355 deletions

42
girafarig.py Normal file
View File

@ -0,0 +1,42 @@
import sys
"""
Super basic simple stupid script to interpolate 1bpp font graphics from smeargle/porygon
"""
def main():
if len(sys.argv) != 3:
raise ValueError("syntax is girafarig.py infile.bin outfile.bin")
in_filename = sys.argv[1]
outfile = sys.argv[2]
with open(in_filename, "rb") as binary_file:
# Read the whole file at once
data = bytearray(binary_file.read())
output = bytearray(len(data))
read_base = 0
while read_base < len(data):
# process row
for i in range(0x10):
top_half_origin = read_base + (i*0x08)
bot_half_origin = read_base + (i*0x08) + 0x80
top_half_dest = read_base + (i*0x10)
bot_half_dest = read_base + (i*0x10) + 0x08
# process character
q = 1
for j in range(0x08):
top_half = data[top_half_origin+j]
bot_half = data[bot_half_origin+j]
output[top_half_dest+j] = top_half
output[bot_half_dest+j] = bot_half
read_base += 0x100
with open(outfile, "wb") as out:
out.write(output)
print ("Done")
if __name__ == '__main__':
main()

View File

@ -1,4 +1,4 @@
Smeargle 0.6.0 readme
Smeargle 0.7.0 readme
---------------------
Usage: smeargle.py game.json
@ -33,6 +33,7 @@ in each object or array.
"test.txt": { // Script filename.
"font": "Melissa 8", // Reference to the font table, above.
"max_tiles_per_line": 8, // Optional: set to 0 for unlimited tiles.
"min_tiles_per_line": 0, // Optional: Non-zero enforces a minimum tile-wise width.
"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)
@ -76,12 +77,19 @@ arguments to see what formats are available.
Changelog
---------
0.7.0
* Add an optional argument to script JSON:
** min_tiles_per_line: enforce a minimum tile count per line.
* Split classes out to separate files.
* Add girafarig, a simple script for interpolating 1bpp graphics.
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
** 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.
** 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.

View File

@ -1,352 +1,40 @@
#!/usr/bin/env python3
import json
from math import ceil, floor
from PyQt5.QtGui import QGuiApplication, QPixmap, QImage, QColor, QPainter
class Font:
"""A simple class for managing Smeargle's font data."""
def __init__(self, filename):
"""Creates the font object.
Takes a filename pointing at the JSON metadata for a font.
"""
with open(filename, mode='rb') as f:
self._json = json.load(f)
self._image = QPixmap(self._json['filename'])
self._colors = []
if 'palette' in self._json:
for color in self._json['palette']:
if isinstance(color, (list, tuple)):
self._colors.append(QColor(*color))
elif isinstance(color, str):
red = int(color[0:2], 16)
green = int(color[2:4], 16)
blue = int(color[4:6], 16)
self._colors.append(QColor(red, green, blue).rgb())
else:
raise ValueError('unsupported color format: {}'.format(color))
else:
print("WARNING: No palette was provided with this font. Output palette order cannot be guaranteed.")
tile = self.index(self.table[' ']['index'])
self._colors = [tile.toImage().pixel(0, 0).rgb()]
def index(self, idx):
"""Given an index, returns the character at that location in the font.
Please note that this function assumes that even variable-width fonts
are stored in a fixed-width grid.
"""
tpr = int(self._image.width() / self.width)
row = int(idx / tpr)
column = idx % tpr
x = column * self.width
y = row * self.height
if (x > self._image.width()) or (y > self._image.height()):
raise ValueError('out of bounds: {}'.format(idx))
return self._image.copy(x, y, self.width, self.height).toImage()
@property
def palette(self):
return self._colors
@property
def width(self):
return self._json['width']
@property
def height(self):
return self._json['height']
@property
def table(self):
return self._json['map']
def length(self, text):
"""Calculate the pixel-wise length of the given string."""
return sum(self.table[x]['width'] for x in text)
class Script:
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.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:
self._text = f.read().split('\n')
self._painter = QPainter()
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 0 < max_tiles < length:
print('WARNING: "{}" exceeds {} tiles by {}px; truncating.'.format(
line,
int(max_tiles / font.width),
length - max_tiles
))
length = max_tiles
image = QImage(length, font.height, QImage.Format_RGB32)
image.fill(font.palette[0])
pos = 0
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
self._painter.end()
lines.append((line, image, length, len(lines)))
return lines
def generate_tilemap(self, font, lines):
tilemap = {}
raw_tiles = []
compressed_tiles = []
map_idx = {}
unique = total = 0
indexes = []
for line in lines:
(text, image, length, lineno) = line
tile_idx = []
# number of tiles in this line
count = int(length / font.width)
column = 0
while count > 0:
tile = image.copy(column, 0, font.width, font.height)
if len(font.palette) > 1:
tile = tile.convertToFormat(QImage.Format_Indexed8, font.palette)
else:
tile = tile.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
compressed_tiles.append(tile)
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
raw_tiles.append(tile)
tile_idx.append(map_idx[data])
total += 1
column += font.width
count -= 1
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
def render_tiles(self, font, tiles):
image = QImage(font.width * 16, ceil(len(tiles) / 16) * font.height, QImage.Format_RGB32)
image.fill(font.palette[0])
(row, column) = (0, 0)
self._painter.begin(image)
for tile in tiles:
self._painter.drawImage(column, row, tile)
if column < (font.width * 15):
column += font.width
else:
column = 0
row += font.height
self._painter.end()
if len(font.palette) > 1:
return image.convertToFormat(QImage.Format_Indexed8, font.palette)
else:
return image.convertToFormat(QImage.Format_Indexed8)
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'].items():
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():
# 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] = (
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']]
)
@property
def fonts(self):
return tuple(self._fonts.keys())
@property
def scripts(self):
return tuple(self._scripts.keys())
def render_script(self, script, render_path, 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)
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...')
lines = script.render_lines(font)
if output: print('Text rendered.')
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:
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()
print('Raw tiles: ', output_raw)
print('Compressed: ', output_comp)
print('Tile<->text: ', output_map)
if __name__ == '__main__':
import sys
import os
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'
if not os.path.exists(render_path):
os.mkdir(render_path, mode=0o644)
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, render_path, output=True)
print('{} processed.'.format(script))
# 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 os
from PyQt5.QtGui import QGuiApplication
from smeargle.game import Game
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'
if not os.path.exists(render_path):
os.mkdir(render_path, mode=0o644)
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, render_path, output=True)
print('{} processed.'.format(script))

0
smeargle/__init__.py Normal file
View File

85
smeargle/font.py Normal file
View File

@ -0,0 +1,85 @@
# 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 json
from PyQt5.QtGui import QPixmap, QColor
class Font:
"""A simple class for managing Smeargle's font data."""
def __init__(self, filename):
"""Creates the font object.
Takes a filename pointing at the JSON metadata for a font.
"""
with open(filename, mode='rb') as f:
self._json = json.load(f)
self._image = QPixmap(self._json['filename'])
self._colors = []
if 'palette' in self._json:
for color in self._json['palette']:
if isinstance(color, (list, tuple)):
self._colors.append(QColor(*color))
elif isinstance(color, str):
red = int(color[0:2], 16)
green = int(color[2:4], 16)
blue = int(color[4:6], 16)
self._colors.append(QColor(red, green, blue).rgb())
else:
raise ValueError('unsupported color format: {}'.format(color))
else:
print("WARNING: No palette was provided with this font. Output palette order cannot be guaranteed.")
tile = self.index(self.table[' ']['index'])
self._colors = [tile.toImage().pixel(0, 0).rgb()]
def index(self, idx):
"""Given an index, returns the character at that location in the font.
Please note that this function assumes that even variable-width fonts
are stored in a fixed-width grid.
"""
tpr = int(self._image.width() / self.width)
row = int(idx / tpr)
column = idx % tpr
x = column * self.width
y = row * self.height
if (x > self._image.width()) or (y > self._image.height()):
raise ValueError('out of bounds: {}'.format(idx))
return self._image.copy(x, y, self.width, self.height).toImage()
@property
def palette(self):
return self._colors
@property
def width(self):
return self._json['width']
@property
def height(self):
return self._json['height']
@property
def table(self):
return self._json['map']
def length(self, text):
"""Calculate the pixel-wise length of the given string."""
return sum(self.table[x]['width'] for x in text)

120
smeargle/game.py Normal file
View File

@ -0,0 +1,120 @@
# 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 json
import os.path
from smeargle.font import Font
from smeargle.script import Script
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'].items():
self._fonts[name] = Font(file)
valid_formats = ['thingy', 'atlas', None]
defaults = {
'max_tiles_per_line': 0,
'min_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():
# 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] = (
Script(filename=script, **data),
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, render_path, 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)
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...')
lines = script.render_lines(font)
if output: print('Text rendered.')
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:
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()
print('Raw tiles: ', output_raw)
print('Compressed: ', output_comp)
print('Tile<->text: ', output_map)

213
smeargle/script.py Normal file
View File

@ -0,0 +1,213 @@
# 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.
from math import floor, ceil
from PyQt5.QtGui import QPainter, QImage
from smeargle.font import Font
def get_or_default(d, key, default):
if key not in d:
return default
return d[key]
class Script:
def __init__(self, filename, **kwargs):
self._cfg = {
'max_tiles': get_or_default(kwargs, 'max_tiles_per_line', 0),
'min_tiles': get_or_default(kwargs, 'min_tiles_per_line', 0),
'output_format': get_or_default(kwargs, 'output_format', None),
'tile_offset': get_or_default(kwargs, 'tile_offset', 0),
'leading_zeroes': get_or_default(kwargs, 'leading_zeroes', False),
'raw_fn': get_or_default(kwargs, 'raw_fn', None),
'deduped_fn': get_or_default(kwargs, 'deduped_fn', None),
'tilemap_fn': get_or_default(kwargs, 'tilemap_fn', None)
}
mint = self._cfg['min_tiles']
maxt = self._cfg['max_tiles']
if mint > maxt and maxt != 0:
raise ValueError('minimum tiles per line higher than maximum')
with open(filename, mode='r', encoding='UTF-8') as f:
self._text = f.read().split('\n')
self._painter = QPainter()
@property
def raw_fn(self):
return self._cfg['raw_fn']
@property
def deduped_fn(self):
return self._cfg['deduped_fn']
@property
def tilemap_fn(self):
return self._cfg['tilemap_fn']
@property
def output_format(self):
return self._cfg['output_format']
@property
def leading_zeroes(self):
return self._cfg['leading_zeroes']
@property
def tile_offset(self):
return self._cfg['tile_offset']
def render_lines(self, font):
table = font.table
lines = []
max_tiles = self._cfg['max_tiles'] * font.width
min_tiles = self._cfg['min_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:
if 0 < max_tiles < length:
print('WARNING: "{}" exceeds {} tiles by {}px; truncating.'.format(
line,
int(max_tiles / font.width),
length - max_tiles
))
length = max_tiles
elif min_tiles > 0:
if 0 < length < min_tiles:
print('INFO: "{}" is shorter than {} tiles by {}px'.format(
line,
int(min_tiles / font.width),
min_tiles - length
))
length = min_tiles
image = QImage(length, font.height, QImage.Format_RGB32)
image.fill(font.palette[0])
pos = 0
self._painter.begin(image)
for glyph in line:
width = font.table[glyph]['width']
if pos + width >= max_tiles and max_tiles > 0:
break
self._painter.drawImage(pos, 0, font.index(font.table[glyph]['index'] - 1))
pos += width
self._painter.end()
lines.append((line, image, length, len(lines)))
return lines
def generate_tilemap(self, font, lines):
tilemap = {}
raw_tiles = []
compressed_tiles = []
map_idx = {}
unique = total = 0
indexes = []
for line in lines:
(text, image, length, lineno) = line
tile_idx = []
# number of tiles in this line
count = int(length / font.width)
column = 0
while count > 0:
tile = image.copy(column, 0, font.width, font.height)
if len(font.palette) > 1:
tile = tile.convertToFormat(QImage.Format_Indexed8, font.palette)
else:
tile = tile.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
compressed_tiles.append(tile)
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
raw_tiles.append(tile)
tile_idx.append(map_idx[data])
total += 1
column += font.width
count -= 1
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
def render_tiles(self, font, tiles):
image = QImage(font.width * 16, ceil(len(tiles) / 16) * font.height, QImage.Format_RGB32)
image.fill(font.palette[0])
(row, column) = (0, 0)
self._painter.begin(image)
for tile in tiles:
self._painter.drawImage(column, row, tile)
if column < (font.width * 15):
column += font.width
else:
column = 0
row += font.height
self._painter.end()
if len(font.palette) > 1:
return image.convertToFormat(QImage.Format_Indexed8, font.palette)
else:
return image.convertToFormat(QImage.Format_Indexed8)
def render_tiles_to_file(self, font, tiles, filename):
self.render_tiles(font, tiles).save(filename, 'PNG')
__all__ = ['Script']