summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBryan Bishop <kanzure@gmail.com>2013-11-23 02:11:07 -0600
committerBryan Bishop <kanzure@gmail.com>2013-11-23 02:11:07 -0600
commitfeef50505e8ff78ca68a09d1f6f535e002db6c13 (patch)
treedc538862918ef0fc732c46a0a5ac0407028a4c5c
parent0099da43b0ff1d7453715a771b0769b58d4c1b5b (diff)
generate images of maps
-rw-r--r--pokemontools/map_gfx.py246
1 files changed, 246 insertions, 0 deletions
diff --git a/pokemontools/map_gfx.py b/pokemontools/map_gfx.py
new file mode 100644
index 0000000..f851f92
--- /dev/null
+++ b/pokemontools/map_gfx.py
@@ -0,0 +1,246 @@
+"""
+Map-related graphic functions.
+"""
+
+import os
+
+from PIL import (
+ Image,
+)
+
+import crystal
+import gfx
+
+tile_width = 8
+tile_height = 8
+block_width = 4
+block_height = 4
+
+# use the same configuration
+gfx.config = crystal.conf
+config = gfx.config
+
+def add_pokecrystal_paths_to_configuration(config=config):
+ """
+ Assumes that the current working directory is the pokecrystal project path.
+ """
+ config.gfx_dir = os.path.join(os.path.abspath("."), "gfx/tilesets/")
+ config.block_dir = os.path.join(os.path.abspath("."), "tilesets/")
+ config.palmap_dir = config.block_dir
+ config.palette_dir = config.block_dir
+
+add_pokecrystal_paths_to_configuration(config=config)
+
+def read_map_blockdata(map_header):
+ """
+ Reads out the list of bytes representing the blockdata for the current map.
+ """
+ width = map_header.second_map_header.blockdata.width.byte
+ height = map_header.second_map_header.blockdata.height.byte
+
+ start_address = map_header.second_map_header.blockdata.address
+ end_address = start_address + (width * height)
+
+ blockdata = crystal.rom[start_address : end_address]
+
+ return [ord(x) for x in blockdata]
+
+def load_png(filepath):
+ """
+ Makes an image object from file.
+ """
+ return Image.open(filepath)
+
+def read_blocks(tileset_id, config=config):
+ """
+ Makes a list of blocks, such that each block is a list of tiles by id, for
+ the given tileset.
+ """
+ blocks = []
+
+ block_width = 4
+ block_height = 4
+ block_length = block_width * block_height
+
+ filename = "{id}{ext}".format(id=str(tileset_id).zfill(2), ext="_metatiles.bin")
+ filepath = os.path.join(config.block_dir, filename)
+
+ blocksetdata = bytearray(open(filepath, "rb").read())
+
+ for blockbyte in xrange(len(blocksetdata) / block_length):
+ block_num = blockbyte * block_length
+ block = blocksetdata[block_num : block_num + block_length]
+ blocks += [block]
+
+ return blocks
+
+def colorize_tile(tile, palette):
+ """
+ Make the tile have colors.
+ """
+ (width, height) = tile.size
+ tile = tile.convert("RGB")
+ px = tile.load()
+
+ for y in xrange(height):
+ for x in xrange(width):
+ # assume greyscale
+ which_color = 3 - (px[x, y][0] / 0x55)
+ (r, g, b) = [v * 8 for v in palette[which_color]]
+ px[x, y] = (r, g, b)
+
+ return tile
+
+def read_tiles(tileset_id, palette_map, palettes, config=config):
+ """
+ Opens the tileset png file and reads bytes for each tile in the tileset.
+ """
+ tile_width = 8
+ tile_height = 8
+
+ tiles = []
+
+ filename = "{id}.{ext}".format(id=str(tileset_id).zfill(2), ext="png")
+ filepath = os.path.join(config.gfx_dir, filename)
+
+ image = load_png(filepath)
+ (image.width, image.height) = image.size
+
+ cur_tile = 0
+
+ for y in xrange(0, image.height, tile_height):
+ for x in xrange(0, image.width, tile_width):
+ tile = image.crop((x, y, x + tile_width, y + tile_height))
+
+ # palette maps are padded to make vram mapping easier
+ pal = palette_map[cur_tile + 0x20 if cur_tile > 0x60 else cur_tile] & 0x7
+ tile = colorize_tile(tile, palettes[pal])
+
+ tiles.append(tile)
+
+ cur_tile += 1
+
+ return tiles
+
+def read_palette_map(tileset_id, config=config):
+ """
+ Loads a palette map.
+ """
+ filename = "{id}{ext}".format(id=str(tileset_id).zfill(2), ext="_palette_map.bin")
+ filepath = os.path.join(config.palmap_dir, filename)
+
+ palette_map = []
+
+ palmap = bytearray(open(filepath, "rb").read())
+
+ for i in xrange(len(palmap)):
+ palette_map += [palmap[i] & 0xf]
+ palette_map += [(palmap[i] >> 4) & 0xf]
+
+ return palette_map
+
+def read_palettes(time_of_day=1, config=config):
+ """
+ Loads up the .pal file?
+ """
+ palettes = []
+
+ actual_time_of_day = ["morn", "day", "nite"][time_of_day]
+ filename = "{}.pal".format(actual_time_of_day)
+ filepath = os.path.join(config.palette_dir, filename)
+
+ num_colors = 4
+ color_length = 2
+ palette_length = num_colors * color_length
+
+ pals = bytearray(open(filepath, "rb").read())
+ num_pals = len(pals) / palette_length
+
+ for pal in xrange(num_pals):
+ palettes += [[]]
+
+ for color in xrange(num_colors):
+ i = pal * palette_length
+ i += color * color_length
+ word = pals[i] + pals[i+1] * 0x100
+
+ palettes[pal] += [[
+ c & 0x1f for c in [
+ word >> 0,
+ word >> 5,
+ word >> 10,
+ ]
+ ]]
+
+ return palettes
+
+def draw_map(map_group_id, map_id, config=config):
+ """
+ Makes a picture of a map.
+ """
+ # extract data from the ROM
+ crystal.cachably_parse_rom()
+
+ map_header = crystal.map_names[map_group_id][map_id]["header_new"]
+ second_map_header = map_header.second_map_header
+
+ width = second_map_header.blockdata.width.byte
+ height = second_map_header.blockdata.height.byte
+
+ tileset_id = map_header.tileset.byte
+ blockdata = read_map_blockdata(map_header)
+
+ palette_map = read_palette_map(tileset_id, config=config)
+ palettes = read_palettes(config=config)
+
+ tileset_blocks = read_blocks(tileset_id, config=config)
+ tileset_images = read_tiles(tileset_id, palette_map, palettes, config=config)
+
+ map_image = Image.new("RGB", (width * tile_width * block_width, height * tile_height * block_height))
+
+ for block_num in xrange(len(blockdata)):
+ block_x = block_num % width
+ block_y = block_num / width
+
+ block = blockdata[block_y * width + block_x]
+
+ for (tile_num, tile) in enumerate(tileset_blocks[block]):
+ # tile gfx are split in half to make vram mapping easier
+ if tile >= 0x80:
+ tile -= 0x20
+
+ tile_x = block_x * 32 + (tile_num % 4) * 8
+ tile_y = block_y * 32 + (tile_num / 4) * 8
+
+ tile_image = tileset_images[tile]
+
+ map_image.paste(tile_image, (tile_x, tile_y))
+
+ return map_image
+
+def save_map(map_group_id, map_id, savedir, config=config):
+ """
+ Makes a map and saves it to a file in savedir.
+ """
+ # this could be moved into a decorator
+ crystal.cachably_parse_rom()
+
+ map_name = crystal.map_names[map_group_id][map_id]["label"]
+ filename = "{name}.{ext}".format(name=map_name, ext="png")
+ filepath = os.path.join(savedir, filename)
+
+ print "Drawing {}".format(map_name)
+ map_image = draw_map(map_group_id, map_id, config)
+ map_image.save(filepath)
+
+ return map_image
+
+def save_maps(savedir, config=config):
+ """
+ Draw as many maps as possible.
+ """
+ crystal.cachably_parse_rom()
+
+ for map_group_id in crystal.map_names.keys():
+ for map_id in crystal.map_names[map_group_id].keys():
+ image = save_map(map_group_id, map_id, savedir, config)