pokegold/tools/gfx.py
2020-05-28 19:52:46 +01:00

363 lines
12 KiB
Python

"""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, 'normal.pal')
args['pic'] = True
elif 'gfx/trainers' in filedir:
trainer_name = filedir.split('/')[-1]
args['pal_file'] = os.path.join(filedir, 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/credits' in filedir:
if name in ['bellossom', 'togepi', 'elekid', 'sentret']:
args['width'] = 32
elif name == 'theend':
args['width'] = 64
elif name == 'border':
args['width'] = 72
elif 'gfx/debug' in filedir:
if name == 'color_test':
args['width'] = 176
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
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/pokedex' in filedir:
if name in ['slowpoke', 'pokedex', 'pokedex_sgb']:
args['width'] = 128
elif name == 'question_mark':
args['width'] = 56
args['pic_dimensions'] = 7, 7
elif 'gfx/pokegear' in filedir:
if name == 'pokegear_sprites':
args['width'] = 16
elif name == 'pokegear':
args['width'] = 128
elif 'gfx/mystery_gift' in filedir:
if name == 'mystery_gift':
args['width'] = 128
args['rows'] = [(0, 15), (0, 15), (0, 2)]
elif name == 'mystery_gift_2':
args['width'] = 128
# TODO: this is incomplete
elif name == 'question_mark':
args['width'] = 40
args['rows'] = [(1, 4), (0, 0), (0, 0), (2, 3), (2, 1)]
elif name == 'border':
args['width'] = 56
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/tilesets' in filedir:
if filedir == 'gfx/tilesets/roofs':
args['width'] = 24
elif filedir != 'gfx/tilesets':
args['width'] = 8
else:
args['tileset'] = True
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 os.path.join(filedir, name) in pics:
args['pic'] = 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__)