mirror of https://github.com/pret/pokecrystal.git
implement png import/export
palette export works fine, but palette import is disabled for now
This commit is contained in:
parent
4d44c2c0e6
commit
da205909c0
304
extras/gfx.py
304
extras/gfx.py
|
@ -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/!'
|
||||
|
||||
|
|
Loading…
Reference in New Issue