ref: da205909c056fd2299fec5bc546999929192a629
parent: 4d44c2c0e64d7e1eed97fe1b246638c251d00972
author: yenatch <[email protected]>
date: Thu Feb 7 16:03:19 EST 2013
implement png import/export palette export works fine, but palette import is disabled for now
--- a/extras/gfx.py
+++ b/extras/gfx.py
@@ -1,19 +1,16 @@
# -*- 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 @@
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 @@
# 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/!'
+