From 01ceb31ea49e3f71f730c4085cf0a6226fe174a1 Mon Sep 17 00:00:00 2001 From: yenatch Date: Thu, 3 Apr 2014 04:08:02 -0400 Subject: pokered: pic de/compression. --- pokemontools/pic.py | 447 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 447 insertions(+) create mode 100644 pokemontools/pic.py diff --git a/pokemontools/pic.py b/pokemontools/pic.py new file mode 100644 index 0000000..a10208f --- /dev/null +++ b/pokemontools/pic.py @@ -0,0 +1,447 @@ +# coding: utf-8 + +""" +A library for use with compressed monster and trainer pics in pokered. +""" + +import os +import sys +import argparse +from math import sqrt + +from gfx import transpose_tiles + + +def bitflip(x, n): + r = 0 + while n: + r = (r << 1) | (x & 1) + x >>= 1 + n -= 1 + return r + + +class Decompressor: + """ + pokered pic decompression. + + Lifted from github.com/magical/pokemon-sprites-rby. + """ + + table1 = [(2 << i) - 1 for i in xrange(16)] + table2 = [ + [0x0, 0x1, 0x3, 0x2, 0x7, 0x6, 0x4, 0x5, 0xf, 0xe, 0xc, 0xd, 0x8, 0x9, 0xb, 0xa], + [0xf, 0xe, 0xc, 0xd, 0x8, 0x9, 0xb, 0xa, 0x0, 0x1, 0x3, 0x2, 0x7, 0x6, 0x4, 0x5], # prev ^ 0xf + [0x0, 0x8, 0xc, 0x4, 0xe, 0x6, 0x2, 0xa, 0xf, 0x7, 0x3, 0xb, 0x1, 0x9, 0xd, 0x5], + [0xf, 0x7, 0x3, 0xb, 0x1, 0x9, 0xd, 0x5, 0x0, 0x8, 0xc, 0x4, 0xe, 0x6, 0x2, 0xa], # prev ^ 0xf + ] + table3 = [bitflip(i, 4) for i in xrange(16)] + + tilesize = 8 + + + def __init__(self, f, mirror=False, planar=True): + self.bs = fbitstream(f) + self.mirror = mirror + self.planar = planar + self.data = None + + def decompress(self): + rams = [[], []] + + self.sizex = self._readint(4) * self.tilesize + self.sizey = self._readint(4) + + self.size = self.sizex * self.sizey + + self.ramorder = self._readbit() + + r1 = self.ramorder + r2 = self.ramorder ^ 1 + + self._fillram(rams[r1]) + mode = self._readbit() + if mode: + mode += self._readbit() + self._fillram(rams[r2]) + + rams[0] = bytearray(bitgroups_to_bytes(rams[0])) + rams[1] = bytearray(bitgroups_to_bytes(rams[1])) + + if mode == 0: + self._decode(rams[0]) + self._decode(rams[1]) + elif mode == 1: + self._decode(rams[r1]) + self._xor(rams[r1], rams[r2]) + elif mode == 2: + self._decode(rams[r2], mirror=False) + self._decode(rams[r1]) + self._xor(rams[r1], rams[r2]) + else: + raise Exception, "Invalid deinterlace mode!" + + data = [] + if self.planar: + for a, b in zip(rams[0], rams[1]): + data += [a, b] + self.data = bytearray(data) + else: + for a, b in zip(bitstream(rams[0]), bitstream(rams[1])): + data.append(a | (b << 1)) + self.data = bitgroups_to_bytes(data) + + def _fillram(self, ram): + mode = ['rle', 'data'][self._readbit()] + size = self.size * 4 + while len(ram) < size: + if mode == 'rle': + self._read_rle_chunk(ram) + mode = 'data' + elif mode == 'data': + self._read_data_chunk(ram, size) + mode = 'rle' + if len(ram) > size: + #ram = ram[:size] + raise ValueError(size, len(ram)) + + ram[:] = self._deinterlace_bitgroups(ram) + + def _read_rle_chunk(self, ram): + + i = 0 + while self._readbit(): + i += 1 + + n = self.table1[i] + a = self._readint(i + 1) + n += a + + for i in xrange(n): + ram.append(0) + + def _read_data_chunk(self, ram, size): + while 1: + bitgroup = self._readint(2) + if bitgroup == 0: + break + ram.append(bitgroup) + + if size <= len(ram): + break + + def _decode(self, ram, mirror=None): + if mirror is None: + mirror = self.mirror + + for x in xrange(self.sizex): + bit = 0 + for y in xrange(self.sizey): + i = y * self.sizex + x + a = (ram[i] >> 4) & 0xf + b = ram[i] & 0xf + + a = self.table2[bit][a] + bit = a & 1 + if mirror: + a = self.table3[a] + + b = self.table2[bit][b] + bit = b & 1 + if mirror: + b = self.table3[b] + + ram[i] = (a << 4) | b + + def _xor(self, ram1, ram2, mirror=None): + if mirror is None: + mirror = self.mirror + + for i in xrange(len(ram2)): + if mirror: + a = (ram2[i] >> 4) & 0xf + b = ram2[i] & 0xf + a = self.table3[a] + b = self.table3[b] + ram2[i] = (a << 4) | b + + ram2[i] ^= ram1[i] + + def _deinterlace_bitgroups(self, bits): + l = [] + for y in xrange(self.sizey): + for x in xrange(self.sizex): + i = 4 * y * self.sizex + x + for j in xrange(4): + l.append(bits[i]) + i += self.sizex + return l + + + def _readbit(self): + return next(self.bs) + + def _readint(self, count): + return readint(self.bs, count) + + +def fbitstream(f): + while 1: + char = f.read(1) + if not char: + break + byte = ord(char) + + for i in xrange(7, -1, -1): + yield (byte >> i) & 1 + +def bitstream(b): + for byte in b: + for i in xrange(7, -1, -1): + yield (byte >> i) & 1 + +def readint(bs, count): + n = 0 + while count: + n <<= 1 + n |= next(bs) + count -= 1 + return n + +def bitgroups_to_bytes(bits): + l = [] + for i in xrange(0, len(bits) - 3, 4): + n = ((bits[i + 0] << 6) + | (bits[i + 1] << 4) + | (bits[i + 2] << 2) + | (bits[i + 3] << 0)) + l.append(n) + return bytearray(l) + + +def bytes_to_bits(bytelist): + return list(bitstream(bytelist)) + + +class Compressor: + """ + pokered pic compression. + + Adapted from stag019's C compressor. + """ + + table1 = [(2 << i) - 1 for i in xrange(16)] + table2 = [ + [0x0, 0x1, 0x3, 0x2, 0x6, 0x7, 0x5, 0x4, 0xc, 0xd, 0xf, 0xe, 0xa, 0xb, 0x9, 0x8], + [0x8, 0x9, 0xb, 0xa, 0xe, 0xf, 0xd, 0xc, 0x4, 0x5, 0x7, 0x6, 0x2, 0x3, 0x1, 0x0], # reverse + ] + table3 = [bitflip(i, 4) for i in xrange(16)] + + def __init__(self, image, width=None, height=None): + self.image = bytearray(image) + self.size = len(self.image) + + planar_tile = 8 * 8 / 4 + tile_size = self.size / planar_tile + if height and not width: width = tile_size / height + elif width and not height: height = tile_size / width + elif not width and not height: width = height = int(sqrt(tile_size)) + self.width, self.height = width, height + + def compress(self): + """ + Compress the image six times (twice for each mode) and use the smallest one. + + TODO: Better tiebreaking for same-size pics. + """ + rams = [[],[]] + datas = [] + for mode in xrange(3): + # Note: order is redundant for mode 0. + for order in xrange(2): + rams[0] = self.image[0::2] + rams[1] = self.image[1::2] + self._interpret_compress(rams, mode, order) + datas += [self.data[:]] + self.data = sorted(datas, key=len)[0] + + def _interpret_compress(self, rams, mode, order): + self.data = [] + self.which_bit = 0 + + r1 = order + r2 = order ^ 1 + + if mode == 0: + self._encode(rams[1]) + self._encode(rams[0]) + elif mode == 1: + self._xor(rams[r1], rams[r2]) + self._encode(rams[r1]) + elif mode == 2: + self._xor(rams[r1], rams[r2]) + self._encode(rams[r1]) + self._encode(rams[r2], mirror=False) + else: + raise Exception, 'invalid interlace mode!' + + self._writeint(self.height, 4) + self._writeint(self.width, 4) + + self._writebit(order) + + self._fillram(rams[r1]) + if mode == 0: + self._writebit(0) + else: + self._writebit(1) + self._writebit(mode - 1) + self._fillram(rams[r2]) + + def _fillram(self, ram): + rle = 0 + nums = 0 + bitgroups = [] + for i in xrange(self.height * self.width * 32): + byte = i / (self.width * 32) + byte = byte * self.width * 8 + i % (self.width * 8) + bit = i / (self.width * 8) + bit = (bit * 2) % 8 + bitgroup = (ram[byte] >> (6 - bit)) & 3 + if bitgroup == 0: + if rle == 0: + self._writebit(0) + elif rle == 1: + nums += 1 + else: + self._data_packet(bitgroups) + self._writebit(0) + self._writebit(0) + rle = 1 + bitgroups = [] + else: + if rle == 0: + self._writebit(1) + elif rle == 1: + self._rle(nums) + rle = -1 + bitgroups += [bitgroup] + nums = 0 + if rle == 1: + self._rle(nums) + else: + self._data_packet(bitgroups) + + def _data_packet(self, bitgroups): + for bitgroup in bitgroups: + self._writebit((bitgroup >> 1) & 1) + self._writebit((bitgroup >> 0) & 1) + + def _rle(self, nums): + bitcount = -1 + nums += 1 + search = nums + while search > 0: + try: + bitcount = self.table1.index(search) + break + except: + search -= 1 + number = nums - self.table1[bitcount] + for j in xrange(bitcount): + self._writebit(1) + self._writebit(0) + for j in xrange(bitcount, -1, -1): + self._writebit((number >> j) & 1) + + def _encode(self, ram, mirror=None): + a = b = 0 + for i in xrange(len(ram)): + j = i / self.height + j += i % self.height * self.width * 8 + if i % self.height == 0: + b = 0 + + a = (ram[j] >> 4) & 0xf + table = b & 1 + code_1 = self.table2[table][a] + + b = ram[j] & 0xf + table = a & 1 + code_2 = self.table2[table][b] + + ram[j] = (code_1 << 4) | code_2 + + def _xor(self, ram1, ram2): + for i in xrange(len(ram2)): + ram2[i] ^= ram1[i] + + def _writebit(self, bit): + self.which_bit -= 1 + if self.which_bit == -1: + self.which_bit = 7 + self.data += [0] + self.data[-1] |= bit << self.which_bit + + def _writeint(self, num, size=None): + bits = [] + if size is not None: + for i in xrange(size): + bits += [num & 1] + num >>= 1 + else: + while num > 0: + bits += [num & 1] + num >>= 1 + for bit in reversed(bits): + self._writebit(bit) + + +def decompress(f, offset=None, mirror=False): + """ + Decompress a pic given a file object. Return a planar 2bpp image. + + Optional: offset (for roms). + """ + if offset is not None: + f.seek(offset) + dcmp = Decompressor(f, mirror=mirror) + dcmp.decompress() + return dcmp.data + + +def compress(f): + """ + Compress a planar 2bpp into a pic. + """ + comp = Compressor(f) + comp.compress() + return comp.data + +def main(): + ap = argparse.ArgumentParser() + ap.add_argument('mode') + ap.add_argument('filenames', nargs='*') + args = ap.parse_args() + + for filename in args.filenames: + if args.mode == 'decompress': + pic = open(filename, 'rb') + image = decompress(pic) + image = transpose_tiles(image) + image = bytearray(image) + output_filename = os.path.splitext(filename)[0] + '.2bpp' + with open(output_filename, 'wb') as out: + out.write(image) + elif args.mode == 'compress': + image = open(filename, 'rb').read() + image = transpose_tiles(image) + pic = compress(image) + pic = bytearray(pic) + output_filename = os.path.splitext(filename)[0] + '.pic' + with open(output_filename, 'wb') as out: + out.write(pic) + +if __name__ == '__main__': + main() + -- cgit v1.2.3 From 21f44b754c50aa57c9c6e9f88e0ded71c6faf1fe Mon Sep 17 00:00:00 2001 From: yenatch Date: Sat, 5 Apr 2014 00:07:48 -0400 Subject: 1:1 conversion for pics. --- pokemontools/pic.py | 72 +++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 50 insertions(+), 22 deletions(-) diff --git a/pokemontools/pic.py b/pokemontools/pic.py index a10208f..4d5c84b 100644 --- a/pokemontools/pic.py +++ b/pokemontools/pic.py @@ -250,20 +250,36 @@ class Compressor: def compress(self): """ - Compress the image six times (twice for each mode) and use the smallest one. - - TODO: Better tiebreaking for same-size pics. + Compress the image five times (twice for each mode, except 0) + and use the smallest one (in bits). """ rams = [[],[]] datas = [] + for mode in xrange(3): - # Note: order is redundant for mode 0. + + # Order is redundant for mode 0. + + # While this seems like an optimization, + # it's actually required for 1:1 compression + # to the original compressed pics. + + # This appears to be the algorithm + # that Game Freak's compressor used. + + # Using order 0 instead of 1 breaks this feature. + for order in xrange(2): - rams[0] = self.image[0::2] - rams[1] = self.image[1::2] + if mode == 0 and order == 0: + continue + for i in xrange(2): + rams[i] = self.image[i::2] self._interpret_compress(rams, mode, order) - datas += [self.data[:]] - self.data = sorted(datas, key=len)[0] + datas += [(self.data[:], int(self.which_bit))] + + # Pick the smallest pic, measured in bits. + datas = sorted(datas, key=lambda (data, bit): (len(data), -bit)) + self.data, self.which_bit = datas[0] def _interpret_compress(self, rams, mode, order): self.data = [] @@ -418,6 +434,30 @@ def compress(f): comp.compress() return comp.data + +def decompress_file(filename): + """ + Decompress a pic given a filename. + Export the resulting planar 2bpp image to + """ + pic = open(filename, 'rb') + image = decompress(pic) + image = transpose_tiles(image) + image = bytearray(image) + output_filename = os.path.splitext(filename)[0] + '.2bpp' + with open(output_filename, 'wb') as out: + out.write(image) + +def compress_file(filename): + image = open(filename, 'rb').read() + image = transpose_tiles(image) + pic = compress(image) + pic = bytearray(pic) + output_filename = os.path.splitext(filename)[0] + '.pic' + with open(output_filename, 'wb') as out: + out.write(pic) + + def main(): ap = argparse.ArgumentParser() ap.add_argument('mode') @@ -426,21 +466,9 @@ def main(): for filename in args.filenames: if args.mode == 'decompress': - pic = open(filename, 'rb') - image = decompress(pic) - image = transpose_tiles(image) - image = bytearray(image) - output_filename = os.path.splitext(filename)[0] + '.2bpp' - with open(output_filename, 'wb') as out: - out.write(image) + decompress_file(filename) elif args.mode == 'compress': - image = open(filename, 'rb').read() - image = transpose_tiles(image) - pic = compress(image) - pic = bytearray(pic) - output_filename = os.path.splitext(filename)[0] + '.pic' - with open(output_filename, 'wb') as out: - out.write(pic) + compress_file(filename) if __name__ == '__main__': main() -- cgit v1.2.3 From 1c349f84a39eb1e567b821465297e27e7eb52257 Mon Sep 17 00:00:00 2001 From: yenatch Date: Sat, 5 Apr 2014 01:37:05 -0400 Subject: Add "norepeat" and "arrange" image attributes. Eliminates repeated tiles and also spits out a tilemap respectively. One-way for now. Likely to be used in pokered, but useful in its own right. --- pokemontools/gfx.py | 34 +++++++++++++++++++++++++++++++--- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/pokemontools/gfx.py b/pokemontools/gfx.py index a1a7bcf..5457161 100644 --- a/pokemontools/gfx.py +++ b/pokemontools/gfx.py @@ -112,6 +112,18 @@ def deinterleave_tiles(image, width): return connect(deinterleave(get_tiles(image), width)) +def condense_tiles_to_map(image): + tiles = get_tiles(image) + new_tiles = [] + tilemap = [] + for tile in tiles: + if tile not in new_tiles: + new_tiles += [tile] + tilemap += [new_tiles.index(tile)] + new_image = connect(new_tiles) + return new_image, tilemap + + def to_file(filename, data): file = open(filename, 'wb') for byte in data: @@ -1282,6 +1294,11 @@ def read_filename_arguments(filename): parsed_arguments['pic_dimensions'] = (int(w), int(h)) elif argument == 'interleave': parsed_arguments['interleave'] = True + elif argument == 'norepeat': + parsed_arguments['norepeat'] = True + elif argument == 'arrange': + parsed_arguments['norepeat'] = True + parsed_arguments['tilemap'] = True return parsed_arguments @@ -1413,12 +1430,16 @@ def export_png_to_2bpp(filein, fileout=None, palout=None, tile_padding=0, pic_di } arguments.update(read_filename_arguments(filein)) - image, palette = png_to_2bpp(filein, **arguments) + image, palette, tmap = png_to_2bpp(filein, **arguments) if fileout == None: fileout = os.path.splitext(filein)[0] + '.2bpp' to_file(fileout, image) + if tmap != None: + mapout = os.path.splitext(fileout)[0] + '.tilemap' + to_file(mapout, tmap) + if palout == None: palout = os.path.splitext(fileout)[0] + '.pal' export_palette(palette, palout) @@ -1454,6 +1475,8 @@ def png_to_2bpp(filein, **kwargs): tile_padding = kwargs.get('tile_padding', 0) pic_dimensions = kwargs.get('pic_dimensions', None) interleave = kwargs.get('interleave', False) + norepeat = kwargs.get('norepeat', False) + tilemap = kwargs.get('tilemap', False) with open(filein, 'rb') as data: width, height, rgba, info = png.Reader(data).asRGBA8() @@ -1555,7 +1578,12 @@ def png_to_2bpp(filein, **kwargs): if interleave: image = deinterleave_tiles(image, num_columns) - return image, palette + if norepeat: + image, tmap = condense_tiles_to_map(image) + if not tilemap: + tmap = None + + return image, palette, tmap def export_palette(palette, filename): @@ -1645,7 +1673,7 @@ def export_png_to_1bpp(filename, fileout=None): to_file(fileout, image) def png_to_1bpp(filename, **kwargs): - image, palette = png_to_2bpp(filename, **kwargs) + image, palette, tmap = png_to_2bpp(filename, **kwargs) return convert_2bpp_to_1bpp(image) -- cgit v1.2.3 From 5188de5a4e796ab96b6a38df3e296764d4d5e07d Mon Sep 17 00:00:00 2001 From: yenatch Date: Sat, 5 Apr 2014 16:24:04 -0400 Subject: gfx: Don't pad images smaller than a tile. Also fix a bug where images smaller than a tile are not converted at all. --- pokemontools/gfx.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pokemontools/gfx.py b/pokemontools/gfx.py index 5457161..d6767fa 100644 --- a/pokemontools/gfx.py +++ b/pokemontools/gfx.py @@ -1454,12 +1454,12 @@ def get_image_padding(width, height, wstep=8, hstep=8): 'bottom': 0, } - if width % wstep: + if width % wstep and width >= wstep: pad = float(width % wstep) / 2 padding['left'] = int(ceil(pad)) padding['right'] = int(floor(pad)) - if height % hstep: + if height % hstep and height >= hstep: pad = float(height % hstep) / 2 padding['top'] = int(ceil(pad)) padding['bottom'] = int(floor(pad)) @@ -1543,15 +1543,15 @@ def png_to_2bpp(filein, **kwargs): # Graphics are stored in tiles instead of lines tile_width = 8 tile_height = 8 - num_columns = width / tile_width - num_rows = height / tile_height + num_columns = max(width, tile_width) / tile_width + num_rows = max(height, tile_height) / tile_height image = [] for row in xrange(num_rows): for column in xrange(num_columns): # Split it up into strips to convert to planar data - for strip in xrange(tile_height): + for strip in xrange(min(tile_height, height)): anchor = ( row * num_columns * tile_width * tile_height + column * tile_width + -- cgit v1.2.3 From c9b6df3193c574ed20ade3c14184f57a3dc7c37f Mon Sep 17 00:00:00 2001 From: yenatch Date: Sun, 6 Apr 2014 19:37:23 -0400 Subject: Optimize pic compression to be 50% faster. --- pokemontools/pic.py | 85 ++++++++++++++++++++++++++++++----------------------- 1 file changed, 49 insertions(+), 36 deletions(-) diff --git a/pokemontools/pic.py b/pokemontools/pic.py index 4d5c84b..25b1e0a 100644 --- a/pokemontools/pic.py +++ b/pokemontools/pic.py @@ -318,31 +318,33 @@ class Compressor: rle = 0 nums = 0 bitgroups = [] - for i in xrange(self.height * self.width * 32): - byte = i / (self.width * 32) - byte = byte * self.width * 8 + i % (self.width * 8) - bit = i / (self.width * 8) - bit = (bit * 2) % 8 - bitgroup = (ram[byte] >> (6 - bit)) & 3 - if bitgroup == 0: - if rle == 0: - self._writebit(0) - elif rle == 1: - nums += 1 - else: - self._data_packet(bitgroups) - self._writebit(0) - self._writebit(0) - rle = 1 - bitgroups = [] - else: - if rle == 0: - self._writebit(1) - elif rle == 1: - self._rle(nums) - rle = -1 - bitgroups += [bitgroup] - nums = 0 + + for x in xrange(self.width): + for bit in xrange(0, 8, 2): + byte = x * self.height * 8 + for y in xrange(self.height * 8): + bitgroup = (ram[byte] >> (6 - bit)) & 3 + if bitgroup == 0: + if rle == 0: + self._writebit(0) + elif rle == 1: + nums += 1 + else: + self._data_packet(bitgroups) + self._writebit(0) + self._writebit(0) + rle = 1 + bitgroups = [] + else: + if rle == 0: + self._writebit(1) + elif rle == 1: + self._rle(nums) + rle = -1 + bitgroups += [bitgroup] + nums = 0 + byte += 1 + if rle == 1: self._rle(nums) else: @@ -354,16 +356,27 @@ class Compressor: self._writebit((bitgroup >> 0) & 1) def _rle(self, nums): - bitcount = -1 nums += 1 - search = nums - while search > 0: - try: - bitcount = self.table1.index(search) - break - except: - search -= 1 - number = nums - self.table1[bitcount] + + # Get the previous power of 2. + # Deriving the bitcount from that seems to be + # faster on average than using the lookup table. + v = nums + v += 1 + v |= v >> 1 + v |= v >> 2 + v |= v >> 4 + v |= v >> 8 + v |= v >> 16 + v -= v >> 1 + v -= 1 + number = nums - v + + bitcount = -1 + while v: + v >>= 1 + bitcount += 1 + for j in xrange(bitcount): self._writebit(1) self._writebit(0) @@ -397,11 +410,11 @@ class Compressor: if self.which_bit == -1: self.which_bit = 7 self.data += [0] - self.data[-1] |= bit << self.which_bit + if bit: self.data[-1] |= bit << self.which_bit def _writeint(self, num, size=None): bits = [] - if size is not None: + if size: for i in xrange(size): bits += [num & 1] num >>= 1 -- cgit v1.2.3 From 345c792b693517d0c9b8fc24b638cf7a4386316d Mon Sep 17 00:00:00 2001 From: yenatch Date: Mon, 14 Apr 2014 14:24:05 -0400 Subject: Clearer explanation for porting https://github.com/magical/pokemon-sprites-rby to python 2.7. --- pokemontools/pic.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pokemontools/pic.py b/pokemontools/pic.py index 25b1e0a..34e88f5 100644 --- a/pokemontools/pic.py +++ b/pokemontools/pic.py @@ -25,7 +25,7 @@ class Decompressor: """ pokered pic decompression. - Lifted from github.com/magical/pokemon-sprites-rby. + Ported to python 2.7 from the python 3 code at https://github.com/magical/pokemon-sprites-rby. """ table1 = [(2 << i) - 1 for i in xrange(16)] -- cgit v1.2.3 From 69f622a1710fe88b4bd29fa074921584c86cf553 Mon Sep 17 00:00:00 2001 From: yenatch Date: Tue, 15 Apr 2014 00:06:05 -0400 Subject: gfx: Transpose blocks of images instead of only the first block. This allows transposed images to be grouped together (e.g. animations). --- pokemontools/gfx.py | 43 ++++++++++++++++++++++++++++++------------- 1 file changed, 30 insertions(+), 13 deletions(-) diff --git a/pokemontools/gfx.py b/pokemontools/gfx.py index d6767fa..0ea5112 100644 --- a/pokemontools/gfx.py +++ b/pokemontools/gfx.py @@ -1356,15 +1356,22 @@ def convert_2bpp_to_png(image, **kwargs): # Pad the image by a given number of tiles if asked. image += chr(0) * 0x10 * tile_padding - # Frontpics are transposed independently of animation graphics. + # Some images are transposed in blocks. if pic_dimensions: w, h = pic_dimensions - i = w * h * 0x10 - pic = ''.join(transpose_tiles(image[:i], w)) - anim = image[i:] - image = pic + anim - # Pad out animation tiles as well. - image += chr(0) * 0x10 * ((w - len(get_tiles(image)) % h) % w) + if not width: width = w * 8 + + pic_length = w * h * 0x10 + + trailing = len(image) % pic_length + + pic = [] + for i in xrange(0, len(image) - trailing, pic_length): + pic += transpose_tiles(image[i:i+pic_length], w) + image = ''.join(pic) + image[len(image) - trailing:] + + # Pad out trailing lines. + image += chr(0) * 0x10 * ((w - (len(image) / 0x10) % h) % w) def px_length(img): return len(img) * 4 @@ -1564,13 +1571,23 @@ def png_to_2bpp(filein, **kwargs): top += (quad /2 & 1) << (7 - bit) image += [bottom, top] - # Frontpics are transposed independently of animation graphics. if pic_dimensions: - w, h = pic_dimensions - i = w * h * 0x10 - pic = transpose_tiles(image[:i], w) - anim = image[i:] - image = pic + anim + w, h = pic_dimensions + + tiles = get_tiles(image) + pic_length = w * h + tile_width = width / 8 + trailing = len(tiles) % pic_length + new_image = [] + for block in xrange(len(tiles) / pic_length): + offset = (h * tile_width) * ((block * w) / tile_width) + ((block * w) % tile_width) + pic = [] + for row in xrange(h): + index = offset + (row * tile_width) + pic += tiles[index:index + w] + new_image += transpose(pic, w) + new_image += tiles[len(tiles) - trailing:] + image = connect(new_image) # Remove any tile padding used to make the png rectangular. image = image[:len(image) - tile_padding * 0x10] -- cgit v1.2.3 From ac2df6b844dd2aefb4d91b87617fa1e9b583cd5a Mon Sep 17 00:00:00 2001 From: yenatch Date: Tue, 15 Apr 2014 00:17:25 -0400 Subject: gfx: Decompress graphics that can be converted to png. Before, converting a .2bpp.lz file required calling decompress and convert_to_png on the compressed and decompressed files respectively. gfx.py unlz {}.lz gfx.py png {} Putting a .lz file into convert_to_png would raise an exception. Instead, passing compressed graphics into convert_to_png will decompress them first. This skips the (now optional) manual decompress step. --- pokemontools/gfx.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/pokemontools/gfx.py b/pokemontools/gfx.py index 0ea5112..456b105 100644 --- a/pokemontools/gfx.py +++ b/pokemontools/gfx.py @@ -1834,7 +1834,7 @@ def expand_pic_palettes(): def convert_to_2bpp(filenames=[]): for filename in filenames: - name, extension = os.path.splitext(filename) + filename, name, extension = try_decompress(filename) if extension == '.1bpp': export_1bpp_to_2bpp(filename) elif extension == '.2bpp': @@ -1846,7 +1846,7 @@ def convert_to_2bpp(filenames=[]): def convert_to_1bpp(filenames=[]): for filename in filenames: - name, extension = os.path.splitext(filename) + filename, name, extension = try_decompress(filename) if extension == '.1bpp': pass elif extension == '.2bpp': @@ -1858,7 +1858,7 @@ def convert_to_1bpp(filenames=[]): def convert_to_png(filenames=[]): for filename in filenames: - name, extension = os.path.splitext(filename) + filename, name, extension = try_decompress(filename) if extension == '.1bpp': export_1bpp_to_png(filename) elif extension == '.2bpp': @@ -1881,6 +1881,19 @@ def decompress(filenames=[]): data = Decompressed(lz_data).output to_file(name, data) +def try_decompress(filename): + """ + Try to decompress a graphic when determining the filetype. + This skips the manual unlz step when attempting + to convert lz-compressed graphics to png. + """ + name, extension = os.path.splitext(filename) + if extension == '.lz': + decompress([filename]) + filename = name + name, extension = os.path.splitext(filename) + return filename, name, extension + def main(): ap = argparse.ArgumentParser() -- cgit v1.2.3