shithub: pokecrystal

Download patch

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/!'
+