ref: fdc1609f2a658b26599a9191bfbe99095248b6fc
parent: f10019710f70f855beaa67c1170337e28cc7a57f
parent: ca22f95db0a89cfa73ba445951e83f131c425d6c
author: Bryan Bishop <[email protected]>
date: Fri May 17 21:49:03 EDT 2013
Merge pull request #138 from yenatch/master automated png compression + gfx fixes + no more windows
--- a/Makefile
+++ b/Makefile
@@ -11,35 +11,24 @@
text/common_3.tx \
main.tx
-VERTGFX = \
- gfx/pics/%.png \
- gfx/trainers/%.png
+PNG_PICS = $(shell find gfx/pics/ -type f -name 'front.png')
+PNG_ANIMS = $(shell find gfx/pics/ -type f -name 'tiles.png')
+PNG_TRAINERS = gfx/trainers/*.png
+PNG_GFX = $(PNG_PICS) $(PNG_ANIMS) $(PNG_TRAINERS), $(filter-out $(shell find gfx/ -type f -name '*.png'))
-HORIZGFX = $(filter-out gfx/%.png, $(VERTGFX))
+LZ_PICS = $(shell find gfx/pics/ -type f -name 'front.lz')
+LZ_ANIMS = $(shell find gfx/pics/ -type f -name 'tiles.lz')
+LZ_TRAINERS = gfx/trainers/*.lz
+LZ_GFX = $(filter-out $(LZ_PICS) $(LZ_ANIMS) $(LZ_TRAINERS), $(shell find gfx/ -type f -name '*.lz'))
-# uncomment this build target to enable png import:
-
-#all: lzs
-
-# the recompressed graphics may be larger than the originals,
-# so take care to reorganize accordingly
-
all: pokecrystal.gbc
cmp baserom.gbc $<
-
-win: pokecrystal.gbc
- fc baserom.gbc $<
-
clean:
rm -f main.tx pokecrystal.o pokecrystal.gbc ${TEXTFILES}
-
-winclean:
- del main.tx pokecrystal.o pokecrystal.gbc .\text\sweethoney.tx .\text\phone\bill.tx .\text\phone\elm.tx .\text\phone\mom.tx .\text\phone\trainers1.tx .\text\common.tx .\text\common_2.tx .\text\common_3.tx
-
-pokecrystal.o: pokecrystal.asm constants.asm wram.asm ${TEXTFILES}
+pokecrystal.o: pokecrystal.asm constants.asm wram.asm ${TEXTFILES} lzs
rgbasm -o pokecrystal.o pokecrystal.asm
-
+
.asm.tx:
python preprocessor.py < $< > $@
@@ -47,19 +36,19 @@
rgblink -o $@ $<
rgbfix -Cjv -i BYTE -k 01 -l 0x33 -m 0x10 -p 0 -r 3 -t PM_CRYSTAL $@
-
-lzs: ${VERTGFX} ${HORIZGFX}
-
pngs:
cd extras && python gfx.py mass-decompress && python gfx.py dump-pngs
+lzs: $(LZ_PICS) $(LZ_ANIMS) $(LZ_TRAINERS) $(LZ_GFX)
-front.png: tiles.png
- cd extras && python gfx.py png-to-lz --front $@ $(OBJECT_DIRECTORY)/tiles.2bpp
-tiles.png:
- cd extras && python gfx.py png-to-2bpp $@
-.png:: ${VERTGFX}
- cd extras && python gfx.py png-to-lz --vert $@
-.png:: ${HORIZGFX}
- cd extras && python gfx.py png-to-lz $@
+gfx/pics/%/front.lz: gfx/pics/%/front.png gfx/pics/%/tiles.2bpp
+ python extras/gfx.py png-to-lz --front $< $(@D)/tiles.2bpp
+gfx/pics/%/tiles.2bpp:
+ python extras/gfx.py png-to-2bpp $<
+gfx/pics/%/back.lz: gfx/pics/%/back.png
+ python extras/gfx.py png-to-lz --vert $<
+gfx/trainers/%.lz: gfx/trainers/%.png
+ python extras/gfx.py png-to-lz --vert $<
+.png.lz:
+ python extras/gfx.py png-to-lz $<
--- a/extras/gfx.py
+++ b/extras/gfx.py
@@ -1,8 +1,8 @@
# -*- coding: utf-8 -*-
import os
+import sys
import png
-import argparse
from math import sqrt, floor, ceil
from crystal import load_rom
@@ -26,28 +26,28 @@
def hex_dump(input, debug = True):
"""display hex dump in rows of 16 bytes"""
-
+
dump = ''
output = ''
stream = ''
address = 0x00
margin = 2 + len(hex(len(input))[2:])
-
+
# dump
for byte in input:
cool = hex(byte)[2:].zfill(2)
dump += cool + ' '
if debug: stream += cool
-
+
# convenient for testing quick edits in bgb
if debug: output += stream + '\n'
-
+
# get dump info
bytes_per_line = 16
chars_per_byte = 3 # '__ '
chars_per_line = bytes_per_line * chars_per_byte
num_lines = int(ceil(float(len(dump)) / float(chars_per_line)))
-
+
# top
# margin
for char in range(margin):
@@ -56,7 +56,7 @@
for byte in range(bytes_per_line):
output += hex(byte)[2:].zfill(2) + ' '
output = output[:-1] # last space
-
+
# print hex
for line in range(num_lines):
# address
@@ -66,16 +66,16 @@
end = chars_per_line + start - 1 # ignore last space
output += dump[start:end]
address += 0x10
-
+
return output
-
+
def get_tiles(image):
"""split a 2bpp image into 8x8 tiles"""
tiles = []
tile = []
bytes_per_tile = 16
-
+
cur_byte = 0
for byte in image:
# build tile
@@ -97,11 +97,11 @@
for byte in tile:
out.append(byte)
return out
-
+
def transpose(tiles):
"""transpose a tile arrangement along line y=x"""
-
+
# horizontal <-> vertical
# 00 01 02 03 04 05 00 06 0c 12 18 1e
# 06 07 08 09 0a 0b 01 07 0d 13 19 1f
@@ -110,7 +110,7 @@
# 18 19 1a 1b 1c 1d 04 0a 10 16 1c 22
# 1e 1f 20 21 22 23 05 0b 11 17 1d 23
# etc
-
+
flipped = []
t = 0 # which tile we're on
w = int(sqrt(len(tiles))) # assume square image
@@ -185,14 +185,15 @@
class Compressed:
"""compress 2bpp data"""
-
+
def __init__(self, image = None, mode = 'horiz', size = None):
-
+
assert image, 'need something to compress!'
+ image = list(image)
self.image = image
self.pic = []
self.animtiles = []
-
+
# only transpose pic (animtiles were never transposed in decompression)
if size != None:
for byte in range((size*size)*16):
@@ -201,21 +202,21 @@
self.animtiles += image[byte]
else:
self.pic = image
-
+
if mode == 'vert':
self.tiles = get_tiles(self.pic)
self.tiles = transpose(self.tiles)
self.pic = connect(self.tiles)
-
+
self.image = self.pic + self.animtiles
-
+
self.end = len(self.image)
-
+
self.byte = None
self.address = 0
-
+
self.stream = []
-
+
self.zeros = []
self.alts = []
self.iters = []
@@ -223,65 +224,65 @@
self.flips = []
self.reverses = []
self.literals = []
-
+
self.output = []
-
+
self.compress()
-
-
+
+
def compress(self):
"""incomplete, but outputs working compressed data"""
-
+
self.address = 0
-
+
# todo
#self.scanRepeats()
-
+
while ( self.address < self.end ):
-
+
#if (self.repeats):
# self.doRepeats()
-
+
#if (self.flips):
# self.doFlips()
-
+
#if (self.reverses):
# self.doReverses
-
+
if (self.checkWhitespace()):
self.doLiterals()
self.doWhitespace()
-
+
elif (self.checkIter()):
self.doLiterals()
self.doIter()
-
+
elif (self.checkAlts()):
self.doLiterals()
self.doAlts()
-
+
else: # doesn't fit any pattern -> literal
self.addLiteral()
self.next()
-
+
self.doStream()
-
+
# add any literals we've been sitting on
self.doLiterals()
-
+
# done
self.output.append(lz_end)
-
-
+
+
def getCurByte(self):
if self.address < self.end:
self.byte = ord(self.image[self.address])
else: self.byte = None
-
+
def next(self):
self.address += 1
self.getCurByte()
-
+
def addLiteral(self):
self.getCurByte()
self.literals.append(self.byte)
@@ -289,7 +290,7 @@
raise Exception, "literals exceeded max length and the compressor didn't catch it"
elif len(self.literals) == max_length:
self.doLiterals()
-
+
def doLiterals(self):
if len(self.literals) > lowmax:
self.output.append( (lz_hi << 5) | (lz_lit << 2) | ((len(self.literals) - 1) >> 8) )
@@ -298,24 +299,24 @@
self.output.append( (lz_lit << 5) | (len(self.literals) - 1) )
for byte in self.literals:
self.output.append(byte)
- self.literals = []
-
+ self.literals = []
+
def doStream(self):
for byte in self.stream:
self.output.append(byte)
self.stream = []
-
-
+
+
def scanRepeats(self):
"""works, but doesn't do flipped/reversed streams yet
-
+
this takes up most of the compress time and only saves a few bytes
it might be more feasible to exclude it entirely"""
-
+
self.repeats = []
self.flips = []
self.reverses = []
-
+
# make a 5-letter word list of the sequence
letters = 5 # how many bytes it costs to use a repeat over a literal
# any shorter and it's not worth the trouble
@@ -326,11 +327,11 @@
for j in range(letters):
word.append( ord(self.image[i+j]) )
words.append((word, i))
-
+
zeros = []
for zero in range(letters):
zeros.append( 0 )
-
+
# check for matches
def get_matches():
# TODO:
@@ -348,9 +349,9 @@
# remove zeros
if this[0] != zeros:
yield [this[0], this[1], words[that][1]]
-
+
matches = list(get_matches())
-
+
# remove more zeros
buffer = []
for match in matches:
@@ -368,7 +369,7 @@
# (and likely to already be accounted for)
buffer.append(match)
matches = buffer
-
+
# combine overlapping matches
buffer = []
for this, match in enumerate(matches):
@@ -384,11 +385,11 @@
buffer.append(match)
# else we've gone past it and we can ignore it
else: # no more overlaps
- buffer.append(match)
+ buffer.append(match)
else: # last match, so there's nothing to check
buffer.append(match)
matches = buffer
-
+
# remove alternating sequences
buffer = []
for match in matches:
@@ -397,25 +398,25 @@
buffer.append(match)
break
matches = buffer
-
+
self.repeats = matches
-
-
+
+
def doRepeats(self):
"""doesn't output the right values yet"""
-
+
unusedrepeats = []
for repeat in self.repeats:
if self.address >= repeat[2]:
-
+
# how far in we are
length = (len(repeat[0]) - (self.address - repeat[2]))
-
+
# decide which side we're copying from
if (self.address - repeat[1]) <= 0x80:
self.doLiterals()
self.stream.append( (lz_repeat << 5) | length - 1 )
-
+
# wrong?
self.stream.append( (((self.address - repeat[1])^0xff)+1)&0xff )
@@ -422,24 +423,24 @@
else:
self.doLiterals()
self.stream.append( (lz_repeat << 5) | length - 1 )
-
+
# wrong?
self.stream.append(repeat[1]>>8)
self.stream.append(repeat[1]&0xff)
-
+
#print hex(self.address) + ': ' + hex(len(self.output)) + ' ' + hex(length)
self.address += length
-
+
else: unusedrepeats.append(repeat)
-
+
self.repeats = unusedrepeats
-
-
+
+
def checkWhitespace(self):
self.zeros = []
self.getCurByte()
original_address = self.address
-
+
if ( self.byte == 0 ):
while ( self.byte == 0 ) & ( len(self.zeros) <= max_length ):
self.zeros.append(self.byte)
@@ -448,7 +449,7 @@
return True
self.address = original_address
return False
-
+
def doWhitespace(self):
if (len(self.zeros) + 1) >= lowmax:
self.stream.append( (lz_hi << 5) | (lz_zeros << 2) | ((len(self.zeros) - 1) >> 8) )
@@ -457,20 +458,20 @@
self.stream.append( lz_zeros << 5 | (len(self.zeros) - 1) )
else:
raise Exception, "checkWhitespace() should prevent this from happening"
-
-
+
+
def checkAlts(self):
self.alts = []
self.getCurByte()
original_address = self.address
num_alts = 0
-
+
# make sure we don't check for alts at the end of the file
- if self.address+2 >= self.end: return False
-
+ if self.address+3 >= self.end: return False
+
self.alts.append(self.byte)
self.alts.append(ord(self.image[self.address+1]))
-
+
# are we onto smething?
if ( ord(self.image[self.address+2]) == self.alts[0] ):
cur_alt = 0
@@ -485,17 +486,17 @@
elif num_alts > 2:
return True
return False
-
+
def doAlts(self):
original_address = self.address
self.getCurByte()
-
+
#self.alts = []
#num_alts = 0
-
+
#self.alts.append(self.byte)
#self.alts.append(ord(self.image[self.address+1]))
-
+
#i = 0
#while (ord(self.image[self.address+1]) == self.alts[i^1]) & (num_alts <= max_length):
# num_alts += 1
@@ -503,9 +504,9 @@
# self.next()
## include the last alternated byte
#num_alts += 1
-
+
num_alts = len(self.iters) + 1
-
+
if num_alts > lowmax:
self.stream.append( (lz_hi << 5) | (lz_alt << 2) | ((num_alts - 1) >> 8) )
self.stream.append( num_alts & 0xff )
@@ -517,11 +518,11 @@
self.stream.append( self.alts[1] )
else:
raise Exception, "checkAlts() should prevent this from happening"
-
+
self.address = original_address
self.address += num_alts
-
+
def checkIter(self):
self.iters = []
self.getCurByte()
@@ -535,19 +536,19 @@
# 3 or fewer isn't worth the trouble and actually longer
# if part of a larger literal set
return True
-
+
return False
-
+
def doIter(self):
self.getCurByte()
iter = self.byte
original_address = self.address
-
+
self.iters = []
while (self.byte == iter) & (len(self.iters) < max_length):
self.iters.append(self.byte)
self.next()
-
+
if (len(self.iters) - 1) >= lowmax:
self.stream.append( (lz_hi << 5) | (lz_iter << 2) | ((len(self.iters)-1) >> 8) )
self.stream.append( (len(self.iters) - 1) & 0xff )
@@ -567,65 +568,65 @@
class Decompressed:
"""parse compressed 2bpp data
-
+
parameters:
[compressed 2bpp data]
[tile arrangement] default: 'vert'
[size of pic] default: None
[start] (optional)
-
+
splits output into pic [size] and animation tiles if applicable
data can be fed in from rom if [start] is specified"""
-
+
def __init__(self, lz = None, mode = None, size = None, start = 0):
# todo: play nice with Compressed
-
+
assert lz, 'need something to compress!'
self.lz = lz
-
+
self.byte = None
self.address = 0
self.start = start
-
+
self.output = []
-
+
self.decompress()
-
+
debug = False
# print tuple containing start and end address
if debug: print '(' + hex(self.start) + ', ' + hex(self.start + self.address+1) + '),'
-
+
# only transpose pic
self.pic = []
self.animtiles = []
-
+
if size != None:
self.tiles = get_tiles(self.output)
self.pic = connect(self.tiles[:(size*size)])
self.animtiles = connect(self.tiles[(size*size):])
else: self.pic = self.output
-
+
if mode == 'vert':
self.tiles = get_tiles(self.pic)
self.tiles = transpose(self.tiles)
self.pic = connect(self.tiles)
-
+
self.output = self.pic + self.animtiles
-
-
+
+
def decompress(self):
"""replica of crystal's decompression"""
-
+
self.output = []
-
+
while True:
self.getCurByte()
-
+
if (self.byte == lz_end):
break
-
+
self.cmd = (self.byte & 0b11100000) >> 5
-
+
if self.cmd == lz_hi: # 10-bit param
self.cmd = (self.byte & 0b00011100) >> 2
self.length = (self.byte & 0b00000011) << 8
@@ -633,7 +634,7 @@
self.length += self.byte + 1
else: # 5-bit param
self.length = (self.byte & 0b00011111) + 1
-
+
# literals
if self.cmd == lz_lit:
self.doLiteral()
@@ -643,7 +644,7 @@
self.doAlt()
elif self.cmd == lz_zeros:
self.doZeros()
-
+
else: # repeaters
self.next()
if self.byte > 0x7f: # negative
@@ -653,7 +654,7 @@
self.displacement = self.byte * 0x100
self.next()
self.displacement += self.byte
-
+
if self.cmd == lz_flip:
self.doFlip()
elif self.cmd == lz_reverse:
@@ -660,30 +661,30 @@
self.doReverse()
else: # lz_repeat
self.doRepeat()
-
+
self.address += 1
#self.next() # somewhat of a hack
-
-
+
+
def getCurByte(self):
self.byte = ord(self.lz[self.start+self.address])
-
+
def next(self):
self.address += 1
self.getCurByte()
-
+
def doLiteral(self):
# copy 2bpp data directly
for byte in range(self.length):
self.next()
self.output.append(self.byte)
-
+
def doIter(self):
# write one byte repeatedly
self.next()
for byte in range(self.length):
self.output.append(self.byte)
-
+
def doAlt(self):
# write alternating bytes
self.alts = []
@@ -691,15 +692,15 @@
self.alts.append(self.byte)
self.next()
self.alts.append(self.byte)
-
+
for byte in range(self.length):
self.output.append(self.alts[byte&1])
-
+
def doZeros(self):
# write zeros
for byte in range(self.length):
self.output.append(0x00)
-
+
def doFlip(self):
# repeat flipped bytes from 2bpp output
# eg 11100100 -> 00100111
@@ -707,12 +708,12 @@
for byte in range(self.length):
flipped = sum(1<<(7-i) for i in range(8) if self.output[self.displacement+byte]>>i&1)
self.output.append(flipped)
-
+
def doReverse(self):
# repeat reversed bytes from 2bpp output
for byte in range(self.length):
self.output.append(self.output[self.displacement-byte])
-
+
def doRepeat(self):
# repeat bytes from 2bpp output
for byte in range(self.length):
@@ -745,15 +746,15 @@
base_stats = 0x51424
# print monster sizes
address = base_stats + 0x11
-
+
output = ''
-
+
for id in range(top):
size = (ord(rom[address])) & 0x0f
if id % 16 == 0: output += '\n\t'
output += str(size) + ', '
address += 0x20
-
+
print output
@@ -772,7 +773,7 @@
# decompress
fx = Decompressed(rom, 'horiz', num_tiles, address)
return fx
-
+
def decompress_fx():
for id in range(num_fx):
fx = decompress_fx_by_id(id)
@@ -806,7 +807,7 @@
# decompress
monster = Decompressed(rom, 'vert', size, address)
return monster
-
+
def decompress_monsters(type = front):
for id in range(num_monsters):
# decompress
@@ -843,7 +844,7 @@
for letter in range(num_unowns):
# decompress
unown = decompress_unown_by_id(letter, type)
-
+
if not type: # front
filename = 'front.2bpp'
folder = '../gfx/pics/' + str(unown_dex).zfill(3) + chr(ord('a') + letter) + '/'
@@ -955,7 +956,7 @@
def decompress_all(debug = False):
"""decompress all known compressed data in baserom"""
-
+
if debug: print 'fronts'
decompress_monsters(front)
if debug: print 'backs'
@@ -964,25 +965,25 @@
decompress_unowns(front)
if debug: print 'unown backs'
decompress_unowns(back)
-
+
if debug: print 'trainers'
decompress_trainers()
-
+
if debug: print 'fx'
decompress_fx()
-
+
if debug: print 'intro'
decompress_intro()
-
+
if debug: print 'title'
decompress_title()
-
+
if debug: print 'tilesets'
decompress_tilesets()
-
+
if debug: print 'misc'
decompress_misc()
-
+
return
@@ -996,9 +997,9 @@
f = open(filein, 'rb')
image = f.read()
f.close()
-
+
de = Decompressed(image, mode, size)
-
+
to_file(fileout, de.pic)
@@ -1006,9 +1007,9 @@
f = open(filein, 'rb')
image = f.read()
f.close()
-
+
lz = Compressed(image, mode)
-
+
to_file(fileout, lz.output)
@@ -1016,18 +1017,18 @@
def compress_monster_frontpic(id, fileout):
mode = 'vert'
-
+
fpic = '../gfx/pics/' + str(id).zfill(3) + '/front.2bpp'
fanim = '../gfx/pics/' + str(id).zfill(3) + '/tiles.2bpp'
-
+
pic = open(fpic, 'rb').read()
anim = open(fanim, 'rb').read()
image = pic + anim
-
+
lz = Compressed(image, mode, sizes[id-1])
-
+
out = '../gfx/pics/' + str(id).zfill(3) + '/front.lz'
-
+
to_file(out, lz.output)
@@ -1074,38 +1075,38 @@
def dump_monster_pals():
rom = load_rom()
-
+
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+'"'
@@ -1112,25 +1113,25 @@
def dump_trainer_pals():
rom = load_rom()
-
+
pals = 0xb0d2
pal_length = 0x4
for trainer in range(67):
-
+
name = trainer_group_names[trainer+1]['constant'].title().replace('_','')
num = str(trainer).zfill(3)
dir = 'gfx/trainers/'
-
+
address = pals + trainer*pal_length
-
+
pal_data = []
for byte in range(pal_length):
pal_data.append(ord(rom[address]))
address += 1
-
+
filename = num+'.pal'
to_file('../'+dir+filename, pal_data)
-
+
spacing = ' ' * (12 - len(name))
print name+'Palette:'+spacing+' INCBIN"'+dir+filename+'"'
@@ -1156,14 +1157,14 @@
"""
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)
@@ -1202,66 +1203,66 @@
"""
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()
-
+
num_pixels = len(image) * 4
-
+
if num_pixels == 0: return 'empty image!'
-
-
+
+
# unless the pic is square, at least one dimension should be given
-
+
if width == None and height == None:
width = int(sqrt(num_pixels))
height = width
-
+
elif height == None:
height = num_pixels / width
elif width == None:
width = num_pixels / height
-
-
+
+
# but try to see if it can be made rectangular
-
+
if width * height != num_pixels:
-
+
# look for possible combos of width/height that would form a rectangle
matches = []
-
+
# this is pretty inefficient, and there is probably a simpler way
for width in range(8,256+1,8): # we only want dimensions that fit in tiles
height = num_pixels / width
if height % 8 == 0:
matches.append((width, height))
-
+
# go for the most square image
width, height = sorted(matches, key=lambda (x,y): x+y)[0] # favors height
-
-
+
+
# if it can't, the only option is a width of 1 tile
-
+
if width * height != num_pixels:
width = 8
height = num_pixels / width
-
-
+
+
# if this still isn't rectangular, then the image isn't made of tiles
-
+
# for now we'll just spit out a warning
if width * height != num_pixels:
print 'Warning! ' + fileout + ' is ' + width + 'x' + height + '(' + width*height + ' pixels),\n' +\
'but ' + filein + ' is ' + num_pixels + ' pixels!'
-
-
+
+
# map it out
-
+
lines = to_lines(flatten(image), width)
-
-
+
+
if pal_file == None:
palette = None
greyscale = True
@@ -1268,14 +1269,14 @@
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)
@@ -1287,44 +1288,44 @@
"""
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)
+ 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:
@@ -1338,10 +1339,10 @@
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,
@@ -1348,24 +1349,24 @@
'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]:
@@ -1373,10 +1374,10 @@
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:
@@ -1385,14 +1386,14 @@
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):
@@ -1406,14 +1407,14 @@
top += ((quad & 2) >> 1) << (7-bit)
image.append(bottom)
image.append(top)
-
+
to_file(fileout, image)
def png_to_lz(filein):
-
+
name = os.path.splitext(filein)[0]
-
+
to_2bpp(filein)
image = open(name+'.2bpp', 'rb').read()
to_file(name+'.lz', Compressed(image).output)
@@ -1441,7 +1442,7 @@
else:
to_png(os.path.join(root, name))
os.utime(os.path.join(root, name), None)
-
+
# only monster and trainer pics for now
for root, dirs, files in os.walk('../gfx/pics/'):
for name in files:
@@ -1452,7 +1453,7 @@
else:
to_png(os.path.join(root, name))
os.utime(os.path.join(root, name), None)
-
+
for root, dirs, files in os.walk('../gfx/trainers/'):
for name in files:
if debug: print os.path.splitext(name), os.path.join(root, name)
@@ -1503,7 +1504,7 @@
"""
assert filename[-3:] == ".lz"
lz_data = open(filename, "rb").read()
- bpp = Decompressed(lz).output
+ bpp = Decompressed(lz_data).output
bpp_filename = filename.replace(".lz", ".2bpp")
to_file(bpp_filename, bpp)
to_png(bpp_filename)
@@ -1518,106 +1519,100 @@
lz_to_png_by_file(tileset_filename)
if __name__ == "__main__":
- parser = argparse.ArgumentParser()
- 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)
- parser.add_argument('arg4', nargs='?', metavar='arg4', type=str)
- parser.add_argument('arg5', nargs='?', metavar='arg5', type=str)
- args = parser.parse_args()
-
+
debug = False
-
- if args.cmd == 'dump-pngs':
+
+ if sys.argv[1] == 'dump-pngs':
mass_to_colored_png()
-
- elif args.cmd == 'png-to-lz':
+
+ elif sys.argv[1] == 'lz-to-png':
+ lz_to_png_by_file(sys.argv[2])
+
+ elif sys.argv[1] == 'png-to-lz':
# python gfx.py png-to-lz [--front anim(2bpp) | --vert] [png]
-
+
# python gfx.py png-to-lz --front [anim(2bpp)] [png]
- if args.arg1 == '--front':
+ if sys.argv[2] == '--front':
# front.png and tiles.png are combined before compression,
# so we have to pass in things like anim file and pic size
- name = os.path.splitext(args.arg3)[0]
-
+ name = os.path.splitext(sys.argv[4])[0]
+
to_2bpp(name+'.png', name+'.2bpp')
pic = open(name+'.2bpp', 'rb').read()
- anim = open(args.arg2, 'rb').read()
+ anim = open(sys.argv[3], 'rb').read()
size = int(sqrt(len(pic)/16)) # assume square pic
to_file(name+'.lz', Compressed(pic + anim, 'vert', size).output)
-
-
+
+
# python gfx.py png-to-lz --vert [png]
- elif args.arg1 == '--vert':
-
+ elif sys.argv[2] == '--vert':
+
# others are vertically oriented (frontpics are always vertical)
-
- name = os.path.splitext(args.arg2)[0]
-
+
+ name = os.path.splitext(sys.argv[3])[0]
+
to_2bpp(name+'.png', name+'.2bpp')
pic = open(name+'.2bpp', 'rb').read()
- to_file(name+'.lz', Compressed(pic + anim, 'vert').output)
-
-
+ to_file(name+'.lz', Compressed(pic, 'vert').output)
+
+
# python gfx.py png-to-lz [png]
else:
-
+
# standard usage
-
- png_to_lz(args.arg1)
-
- elif args.cmd == 'png-to-2bpp':
- to_2bpp(args.arg1)
-
-
- elif args.cmd == 'de':
+
+ png_to_lz(sys.argv[2])
+
+ elif sys.argv[1] == 'png-to-2bpp':
+ to_2bpp(sys.argv[2])
+
+
+ elif sys.argv[1] == 'de':
# python gfx.py de [addr] [fileout] [mode]
-
+
rom = load_rom()
-
- addr = int(args.arg1,16)
- fileout = args.arg2
- mode = args.arg3
+
+ addr = int(sys.argv[2],16)
+ fileout = sys.argv[3]
+ mode = sys.argv[4]
decompress_from_address(addr, fileout, mode)
- if debug: print 'decompressed to ' + args.arg2 + ' from ' + hex(int(args.arg1,16)) + '!'
-
- elif args.cmd == 'lz':
+ if debug: print 'decompressed to ' + sys.argv[3] + ' from ' + hex(int(sys.argv[2],16)) + '!'
+
+ elif sys.argv[1] == 'lz':
# python gfx.py lz [filein] [fileout] [mode]
- filein = args.arg1
- fileout = args.arg2
- mode = args.arg3
+ filein = sys.argv[2]
+ fileout = sys.argv[3]
+ mode = sys.argv[4]
compress_file(filein, fileout, mode)
if debug: print 'compressed ' + filein + ' to ' + fileout + '!'
-
- elif args.cmd == 'lzf':
+
+ elif sys.argv[1] == 'lzf':
# python gfx.py lzf [id] [fileout]
- compress_monster_frontpic(int(args.arg1), args.arg2)
-
- elif args.cmd == 'un':
+ compress_monster_frontpic(int(sys.argv[2]), sys.argv[3])
+
+ elif sys.argv[1] == 'un':
# python gfx.py un [address] [num_tiles] [filename]
rom = load_rom()
- get_uncompressed_gfx(int(args.arg1,16), int(args.arg2), args.arg3)
-
- elif args.cmd == 'pal':
+ get_uncompressed_gfx(int(sys.argv[2],16), int(sys.argv[3]), sys.argv[4])
+
+ elif sys.argv[1] == 'pal':
# python gfx.py pal [address] [length]
rom = load_rom()
- 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)
+ print grab_palettes(int(sys.argv[2],16), int(sys.argv[3]))
+
+ elif sys.argv[1] == 'png':
+
+ if '.2bpp' in sys.argv[2]:
+ if sys.argv[4] == 'greyscale':
+ to_png(sys.argv[2], sys.argv[3])
else:
- to_png(args.arg1, args.arg2, args.arg3)
-
- elif '.png' in args.arg1:
- to_2bpp(args.arg1, args.arg2)
-
- elif args.cmd == 'mass-decompress':
+ to_png(sys.argv[2], sys.argv[3], sys.argv[4])
+
+ elif '.png' in sys.argv[2]:
+ to_2bpp(sys.argv[2], sys.argv[3])
+
+ elif sys.argv[1] == 'mass-decompress':
mass_decompress()
if debug: print 'decompressed known gfx to pokecrystal/gfx/!'
-
--- a/pokecrystal.bat
+++ /dev/null
@@ -1,3 +1,0 @@
-@set PATH=%PATH%;C:\Program Files (x86)\GnuWin32\bin\;C:\Python27\
-@make winclean && make win
-@pause