implement png import/export

palette export works fine, but palette import is disabled for now
This commit is contained in:
yenatch 2013-02-07 21:03:19 -05:00
parent 4d44c2c0e6
commit da205909c0
1 changed files with 297 additions and 7 deletions

View File

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