summaryrefslogtreecommitdiff
path: root/tools/gfx.py
diff options
context:
space:
mode:
Diffstat (limited to 'tools/gfx.py')
-rw-r--r--tools/gfx.py328
1 files changed, 328 insertions, 0 deletions
diff --git a/tools/gfx.py b/tools/gfx.py
new file mode 100644
index 00000000..79dd6f61
--- /dev/null
+++ b/tools/gfx.py
@@ -0,0 +1,328 @@
+"""Supplementary scripts for graphics conversion."""
+
+import os, re
+import argparse
+
+from pokemontools import gfx, lz
+
+
+# Graphics with inverted tilemaps that aren't covered by filepath_rules.
+pics = [
+ 'gfx/shrink1',
+ 'gfx/shrink2',
+]
+
+def recursive_read(filename):
+ def recurse(filename_):
+ lines = []
+ for line in open(filename_):
+ if 'include "' in line.lower():
+ lines += recurse(line.split('"')[1])
+ else:
+ lines += [line]
+ return lines
+ lines = recurse(filename)
+ return ''.join(lines)
+
+base_stats = None
+def get_base_stats():
+ global base_stats
+ if not base_stats:
+ base_stats = recursive_read('data/pokemon/base_stats.asm')
+ return base_stats
+
+def get_pokemon_dimensions(name):
+ try:
+ if name == 'egg':
+ return 5, 5
+ if name == 'questionmark':
+ return 7, 7
+ if name.startswith('unown_'):
+ name = 'unown'
+ base_stats = get_base_stats()
+ # hack for pidgeot/mew (previously caught by being substrings of pidgeotto/mewtwo)
+ pattern = re.compile('\s+db {0}\s+'.format(name.upper()))
+ start = pattern.search(base_stats).start()
+ start = base_stats.find('\tdn ', start)
+ end = base_stats.find('\n', start)
+ line = base_stats[start:end].replace(',', ' ')
+ w, h = map(int, line.split()[1:3])
+ return w, h
+ except:
+ return 7, 7
+
+mail_px8 = ['eon_mail_border_2', 'grass', 'lovely_mail_border', 'lovely_mail_underline',
+ 'morph_mail_border', 'morph_mail_divider', 'portrail_mail_border',
+ 'portraitmail_border', 'portraitmail_underline', 'small_heart', 'small_note',
+ 'small_pokeball', 'small_triangle', 'wave']
+mail_px16 = ['eon_mail_border_1', 'flower_1', 'flower_2', 'large_circle', 'large_heart',
+ 'large_pokeball', 'large_triangle', 'morph_mail_corner','music_mail_border', 'oddish',
+ 'sentret', 'unused_grass']
+mail_px24 = ['cloud', 'ditto', 'dratini', 'eevee', 'lapras', 'mew', 'natu', 'poliwag']
+mail_border_stretch = ['surf_mail_border', 'flower_mail_border', 'litebluemail_border']
+
+overworld_px8 = ['boulder_dust', 'fishing_rod', 'grass_rustle', 'heal_machine', 'shadow',
+ 'trainer_battle_pokeball_tiles']
+overworld_px16 = ['chris_fish', 'cut_grass', 'cut_tree', 'headbutt_tree']
+
+def filepath_rules(filepath):
+ """Infer attributes of certain graphics by their location in the filesystem."""
+ args = {}
+
+ filedir, filename = os.path.split(filepath)
+ if filedir.startswith('./'):
+ filedir = filedir[2:]
+
+ name, ext = os.path.splitext(filename)
+ if ext == '.lz':
+ name, ext = os.path.splitext(name)
+
+ pokemon_name = ''
+
+ if 'gfx/pokemon/' in filedir:
+ pokemon_name = filedir.split('/')[-1]
+ if pokemon_name.startswith('unown_'):
+ index = filedir.find(pokemon_name)
+ if index != -1:
+ filedir = filedir[:index + len('unown')] + filedir[index + len('unown_a'):]
+ # startswith to handle front_gold / front_silver
+ if name.startswith('front'):
+ args['pal_file'] = os.path.join(filedir, 'normal.pal')
+ args['pic'] = True
+ # TODO: way to handle Crystal and Gold/Silver simultaneously?
+ # args['animate'] = True
+ # startswith to handle back_gold / back_silver
+ elif name.startswith('back'):
+ args['pal_file'] = os.path.join(filedir, 'shiny.pal')
+ args['pic'] = True
+
+ elif 'gfx/trainers' in filedir:
+ trainer_name = filedir.split('/')[-1]
+ args['pal_file'] = os.path.join(filedir, trainer_name + '.pal')
+ args['pic'] = True
+
+ elif 'gfx/battle' in filedir:
+ if name == 'dude':
+ args['pic_dimensions'] = 6, 6
+ elif name == 'balls':
+ args['width'] = 32
+
+ elif 'gfx/font' in filedir:
+ if name == 'font_inversed':
+ args['width'] = 128
+
+ elif 'gfx/icons' in filedir:
+ if name == 'mail_big':
+ args['width'] = 16
+
+ elif 'gfx/intro' in filedir:
+ if name == 'logo_star':
+ args['width'] = 8
+ elif name in ['gamefreak_logo', 'logo_sparkle']:
+ args['width'] = 24
+ elif name == 'gamefreak_presents':
+ args['width'] = 104
+ elif name == 'copyright':
+ args['width'] = 240
+
+ elif 'gfx/mail' in filedir:
+ if name in mail_px8:
+ args['width'] = 8
+ elif name in mail_px16:
+ args['width'] = 16
+ elif name in mail_px24:
+ args['width'] = 24
+ elif name in mail_border_stretch:
+ args['width'] = 24
+ args['pad_indices'] = [4]
+ elif name == 'large_note':
+ args['width'] = 16
+ args['rows'] = [(1, 1), (0, 2)]
+ elif name == 'dragonite':
+ args['width'] = 56
+ args['rows'] = [(0, 6), (1, 6), (2, 6)]
+
+ elif 'gfx/naming_screen' in filedir:
+ args['width'] = 8
+
+ elif 'gfx/overworld' in filedir:
+ if name == 'heal_machine':
+ args['width'] = 8
+ args['pal_file'] = os.path.join(filedir, name + '.pal')
+ elif name in overworld_px8:
+ args['width'] = 8
+ elif name in overworld_px16:
+ args['width'] = 16
+
+ elif 'gfx/pack' in filedir:
+ if name == 'pack':
+ args['width'] = 40
+ elif name == 'pack_menu':
+ args['width'] = 128
+
+ elif 'gfx/player' in filedir:
+ if name == 'chris_back':
+ args['pic_dimensions'] = 6, 6
+
+ elif 'gfx/pokegear' in filedir:
+ if name == 'pokegear_sprites':
+ args['width'] = 16
+
+ elif 'gfx/sgb' in filedir:
+ args['width'] = 128
+ args['pal_file'] = os.path.join(filedir, name + '.pal')
+
+ elif 'gfx/slots' in filedir:
+ if name == 'slots_1':
+ args['width'] = 16
+ elif name == 'slots_2':
+ args['width'] = 16
+ args['pic_dimensions'] = 2, 2
+ # TODO: this is incomplete
+ elif name == 'slots_3':
+ args['width'] = 24
+
+ elif 'gfx/sprites' in filedir:
+ # TODO: this is incomplete
+ if name == 'big_onix':
+ args['width'] = 32
+ args['rows'] = [(0, 4), (0, 4), (1, 2), (1, 2)]
+ else:
+ args['width'] = 16
+
+ elif 'gfx/trainer_card' in filedir:
+ if name in ['badges', 'trainer_card']:
+ args['width'] = 16
+ elif name == 'card_status':
+ args['width'] = 48
+ elif name == 'chris_card':
+ args['width'] = 40
+ elif name == 'leaders':
+ args['width'] = 80
+
+ elif 'gfx/trade' in filedir:
+ if name in ['arrow_left', 'arrow_right', 'cable']:
+ args['width'] = 8
+ elif name in ['bubble', 'poof']:
+ args['width'] = 16
+ elif name == 'ball':
+ args['width'] = 16
+ args['rows'] = [(0, 1), (0, 1), (0, 2), (0, 2)]
+ elif name == 'border_tiles':
+ args['width'] = 24
+ elif name == 'game_boy':
+ args['width'] = 56
+
+ elif 'gfx/unknown' in filedir:
+ if name == 'gfx_170f1':
+ args['width'] = 128
+ elif name == 'gfx_17079':
+ args['width'] = 16
+
+ elif os.path.join(filedir, name) in pics:
+ args['pic'] = True
+
+ elif filedir == 'gfx/tilesets':
+ args['tileset'] = True
+
+ if args.get('pal_file'):
+ if os.path.exists(args['pal_file']):
+ args['palout'] = args['pal_file']
+ else:
+ del args['pal_file']
+
+ if args.get('pic'):
+ if ext == '.png':
+ w, h = gfx.png.Reader(filepath).asRGBA8()[:2]
+ w = min(w/8, h/8)
+ args['pic_dimensions'] = w, w
+ elif ext == '.2bpp':
+ # startswith to handle front_gold / front_silver
+ if pokemon_name and name.startswith('front'):
+ w, h = get_pokemon_dimensions(pokemon_name)
+ args['pic_dimensions'] = w, w
+ # startswith to handle back_gold / back_silver
+ elif pokemon_name and name.startswith('back'):
+ args['pic_dimensions'] = 6, 6
+ else:
+ args['pic_dimensions'] = 7, 7
+
+ if args.get('tileset'):
+ args['width'] = 128
+ return args
+
+
+def to_1bpp(filename, **kwargs):
+ name, ext = os.path.splitext(filename)
+ if ext == '.1bpp': pass
+ elif ext == '.2bpp': gfx.export_2bpp_to_1bpp(filename, **kwargs)
+ elif ext == '.png': gfx.export_png_to_1bpp(filename, **kwargs)
+ elif ext == '.lz':
+ decompress(filename, **kwargs)
+ to_1bpp(name, **kwargs)
+
+def to_2bpp(filename, **kwargs):
+ name, ext = os.path.splitext(filename)
+ if ext == '.1bpp': gfx.export_1bpp_to_2bpp(filename, **kwargs)
+ elif ext == '.2bpp': pass
+ elif ext == '.png': gfx.export_png_to_2bpp(filename, **kwargs)
+ elif ext == '.lz':
+ decompress(filename, **kwargs)
+ to_2bpp(name, **kwargs)
+
+def to_png(filename, **kwargs):
+ name, ext = os.path.splitext(filename)
+ if ext == '.1bpp': gfx.export_1bpp_to_png(filename, **kwargs)
+ elif ext == '.2bpp':
+ basedir, basename = os.path.split(filename)
+ name, ext = os.path.splitext(basename)
+ # TODO: how to actually make big_onix.png (reusing one from pokecrystal for now)
+ if name in ['big_onix', 'slots_3']:
+ return
+ if name in ['back_gold', 'back_silver']:
+ kwargs['fileout'] = os.path.join(basedir, 'back.png')
+ gfx.export_2bpp_to_png(filename, **kwargs)
+ elif ext == '.png': pass
+ elif ext == '.lz':
+ decompress(filename, **kwargs)
+ to_png(name, **kwargs)
+
+def compress(filename, **kwargs):
+ data = open(filename, 'rb').read()
+ lz_data = lz.Compressed(data).output
+ open(filename + '.lz', 'wb').write(bytearray(lz_data))
+
+def decompress(filename, **kwargs):
+ lz_data = open(filename, 'rb').read()
+ data = lz.Decompressed(lz_data).output
+ # hack to work for Alakazam's silver backsprite; needs to be multiple of 8 anyway
+ data = data[:len(data)//8*8]
+ name, ext = os.path.splitext(filename)
+ open(name, 'wb').write(bytearray(data))
+
+
+methods = {
+ '2bpp': to_2bpp,
+ '1bpp': to_1bpp,
+ 'png': to_png,
+ 'lz': compress,
+ 'unlz': decompress,
+}
+
+def main(method_name, filenames=None):
+ if filenames is None: filenames = []
+ for filename in filenames:
+ args = filepath_rules(filename)
+ method = methods.get(method_name)
+ if method:
+ method(filename, **args)
+
+def get_args():
+ ap = argparse.ArgumentParser()
+ ap.add_argument('method_name')
+ ap.add_argument('filenames', nargs='*')
+ args = ap.parse_args()
+ return args
+
+if __name__ == '__main__':
+ main(**get_args().__dict__)