diff options
author | yenatch <yenatch@github.com> | 2013-02-07 21:03:19 -0500 |
---|---|---|
committer | yenatch <yenatch@github.com> | 2013-02-07 21:03:19 -0500 |
commit | 9536d00bcc81e049a24e44c5aa4e559aa09bad09 (patch) | |
tree | d85af2ff571c4a28b2dfb6c68aea2d9200b65148 /gfx.py | |
parent | bb425e6cec31ba6ff670b54b4303a660760440e8 (diff) |
implement png import/export
palette export works fine, but palette import is disabled for now
original-commit-id: da205909c056fd2299fec5bc546999929192a629
Diffstat (limited to 'gfx.py')
-rw-r--r-- | gfx.py | 304 |
1 files changed, 297 insertions, 7 deletions
@@ -1,17 +1,14 @@ # -*- coding: utf-8 -*- import os -import sys -import errno -import string -from copy import copy, deepcopy -import random +import png import argparse from math import sqrt, floor, ceil -from datetime import datetime from crystal import load_rom +from pokemon_constants import pokemon_constants + rom = load_rom() @@ -1078,9 +1075,290 @@ def grab_palettes(address, length = 0x80): return output + + + + + +def dump_monster_pals(): + pals = 0xa8d6 + pal_length = 0x4 + for mon in range(251): + + name = pokemon_constants[mon+1].title().replace('_','') + num = str(mon+1).zfill(3) + dir = 'gfx/pics/'+num+'/' + + address = pals + mon*pal_length*2 + + + pal_data = [] + for byte in range(pal_length): + pal_data.append(ord(rom[address])) + address += 1 + + filename = 'normal.pal' + to_file('../'+dir+filename, pal_data) + + spacing = ' ' * (15 - len(name)) + #print name+'Palette:'+spacing+' INCBIN "'+dir+filename+'"' + + + pal_data = [] + for byte in range(pal_length): + pal_data.append(ord(rom[address])) + address += 1 + + filename = 'shiny.pal' + to_file('../'+dir+filename, pal_data) + + spacing = ' ' * (10 - len(name)) + #print name+'ShinyPalette:'+spacing+' INCBIN "'+dir+filename+'"' + + + +def flatten(planar): + """ + Flattens planar 2bpp image data into a quaternary pixel map. + """ + strips = [] + for pair in range(len(planar)/2): + bottom = ord(planar[(pair*2) ]) + top = ord(planar[(pair*2)+1]) + strip = [] + for i in range(7,-1,-1): + color = ((bottom >> i) & 1) + (((top >> i-1) if i > 0 else (top << 1-i)) & 2) + strip.append(color) + strips += strip + return strips + + +def to_lines(image, width): + """ + Converts a tiled quaternary pixel map to lines of quaternary pixels. + """ + + tile = 8 * 8 + + # so we know how many strips of 8px we're putting into a line + num_columns = width / 8 + # number of lines + height = len(image) / width + + lines = [] + for cur_line in range(height): + tile_row = int(cur_line / 8) + line = [] + for column in range(num_columns): + anchor = num_columns*tile_row*tile + column*tile + (cur_line%8)*8 + line += image[anchor:anchor+8] + lines.append(line) + return lines + +def dmg2rgb(word): + red = word & 0b11111 + word >>= 5 + green = word & 0b11111 + word >>= 5 + blue = word & 0b11111 + alpha = 255 + return ((red<<3)+0b100, (green<<3)+0b100, (blue<<3)+0b100, alpha) + + +def png_pal(filename): + palette = [] + palette.append((255,255,255,255)) + with open(filename, 'rb') as pal_data: + words = pal_data.read() + dmg_pals = [] + for word in range(len(words)/2): + dmg_pals.append(ord(words[word*2]) + ord(words[word*2+1])*0x100) + for word in dmg_pals: + palette.append(dmg2rgb(word)) + palette.append((000,000,000,255)) + return palette + + +def to_png(filein, fileout=None, pal_file=None, height=None, width=None): + """ + Takes a planar 2bpp graphics file and converts it to png. + """ + + if fileout == None: fileout = ''.join(filein.split('.')[:-1]) + '.png' + + image = open(filein, 'rb').read() + + + # unless the pic is square, at least one dimension should be given + + if height == None and width == None: + height = int(sqrt(len(image)*4)) + width = height + + elif height == None: height = len(image)*4 / width + + elif width == None: width = len(image)*4 / height + + assert height * width == len(image)*4, 'Please specify dimensions for non-square image!' + + + # map it out + + lines = to_lines(flatten(image), width) + + + if pal_file == None: + palette = None + greyscale = True + bitdepth = 2 + inverse = { 0:3, 1:2, 2:1, 3:0 } + map = [[inverse[pixel] for pixel in line] for line in lines] + + else: # gbc color + palette = png_pal(pal_file) + greyscale = False + bitdepth = 8 + map = [[pixel for pixel in line] for line in lines] + + + w = png.Writer(width, height, palette=palette, compression = 9, greyscale = greyscale, bitdepth = bitdepth) + with open(fileout, 'wb') as file: + w.write(file, map) + + + + +def to_2bpp(filein, fileout=None, palout=None): + """ + Takes a png and converts it to planar 2bpp. + """ + + if fileout == None: fileout = ''.join(filein.split('.')[:-1]) + '.2bpp' + + with open(filein, 'rb') as file: + + r = png.Reader(file) + info = r.asRGBA8() + + width = info[0] + height = info[1] + + rgba = list(info[2]) + greyscale = info[3]['greyscale'] + + + # commented out for the moment + + padding = { 'left': 0, + 'right': 0, + 'top': 0, + 'bottom': 0, } + + #if width % 8 != 0: + # padding['left'] = int(ceil((width / 8 + 8 - width) / 2)) + # padding['right'] = int(floor((width / 8 + 8 - width) / 2)) + + #if height % 8 != 0: + # padding['top'] = int(ceil((height / 8 + 8 - height) / 2)) + # padding['bottom'] = int(floor((height / 8 + 8 - height) / 2)) + + + # turn the flat values into something more workable + + pixel_length = 4 # rgba + image = [] + + # while we're at it, let's size up the palette + + palette = [] + + for line in rgba: + newline = [] + for pixel in range(len(line)/pixel_length): + i = pixel*pixel_length + color = { 'r': line[i ], + 'g': line[i+1], + 'b': line[i+2], + 'a': line[i+3], } + newline.append(color) + if color not in palette: palette.append(color) + image.append(newline) + + + # sort by luminance, because we can + + def luminance(color): + # this is actually in reverse, thanks to dmg/cgb palette ordering + rough = { 'r': 4.7, + 'g': 1.4, + 'b': 13.8, } + return sum(color[key] * -rough[key] for key in rough.keys()) + + palette = sorted(palette, key = lambda x:luminance(x)) + + # no palette fixing for now + + assert len(palette) <= 4, 'Palette should be 4 colors, is really ' + str(len(palette)) + + + # spit out new palette (disabled for now) + + def rgb_to_dmg(color): + word = (color['r'] / 8) << 10 + word += (color['g'] / 8) << 5 + word += (color['b'] / 8) + return word + + palout = None + + if palout != None: + output = [] + for color in palette[1:3]: + word = rgb_to_dmg(color) + output.append(word>>8) + output.append(word&0xff) + to_file(palout, output) + + + # create a new map consisting of quaternary color ids + + map = [] + if padding['top']: map += [0] * (width + padding['left'] + padding['right']) * padding['top'] + for line in image: + if padding['left']: map += [0] * padding['left'] + for color in line: + map.append(palette.index(color)) + if padding['right']: map += [0] * padding['right'] + if padding['bottom']: map += [0] * (width + padding['left'] + padding['right']) * padding['bottom'] + + # split it into strips of 8, and make them planar + + num_columns = width / 8 + num_rows = height / 8 + + tile = 8 * 8 + + image = [] + for row in range(num_rows): + for column in range(num_columns): + for strip in range(tile / 8): + anchor = row*num_columns*tile + column*tile/8 + strip*width + line = map[anchor:anchor+8] + bottom = 0 + top = 0 + for bit, quad in enumerate(line): + bottom += (quad & 1) << (7-bit) + top += ((quad & 2) >> 1) << (7-bit) + image.append(bottom) + image.append(top) + + to_file(fileout, image) + + + if __name__ == "__main__": parser = argparse.ArgumentParser() - parser.add_argument('cmd', nargs='?', metavar='cmd', type=str) + parser.add_argument('cmd', nargs='?', metavar='cmd', type=str) parser.add_argument('arg1', nargs='?', metavar='arg1', type=str) parser.add_argument('arg2', nargs='?', metavar='arg2', type=str) parser.add_argument('arg3', nargs='?', metavar='arg3', type=str) @@ -1116,7 +1394,19 @@ if __name__ == "__main__": # python gfx.py pal [address] [length] print grab_palettes(int(args.arg1,16), int(args.arg2)) + elif args.cmd == 'png': + + if '.2bpp' in args.arg1: + if args.arg3 == 'greyscale': + to_png(args.arg1, args.arg2) + else: + to_png(args.arg1, args.arg2, args.arg3) + + elif '.png' in args.arg1: + to_2bpp(args.arg1, args.arg2) + #else: ## python gfx.py #decompress_all() #if debug: print 'decompressed known gfx to ../gfx/!' + |