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 Usage: smeargle.py game.json
@ -33,6 +33,7 @@ in each object or array.
"test.txt": { // Script filename. "test.txt": { // Script filename.
"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. "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" "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) "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) "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 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 0.6.0
* Adds several optional arguments to script json elements: * 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. ** leading_zeroes: Forces tilemap output to always be 16-bit.
** tile_offset: Adds a constant value to the tile index. ** tile_offset: Adds a constant value to the tile index. Useful if you want the
Useful if you want the tilemap to start counting somewhere other than zero. tilemap to start counting somewhere other than zero.
** raw_fn: Filename for raw tile graphic png output. ** raw_fn: Filename for raw tile graphic png output.
** deduped_fn: Filename for compressed tile graphic png output. ** deduped_fn: Filename for compressed tile graphic png output.
** tilemap_fn: Filename for text index tilemap file. ** tilemap_fn: Filename for text index tilemap file.

View File

@ -1,337 +1,25 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# 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 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 sys
import os import os
from PyQt5.QtGui import QGuiApplication
from smeargle.game import Game
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.')

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']