Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- #!/bin/env python3
- # coding: utf-8
- """
- A library for use with compressed monster and trainer pics in pokered.
- """
- from __future__ import absolute_import
- from __future__ import division
- import os
- import sys
- import argparse
- from math import sqrt
- def connect(tiles):
- """
- Combine 8x8 tiles into a 2bpp image.
- """
- return [byte for tile in tiles for byte in tile]
- def transpose(tiles, width=None):
- """
- Transpose a tile arrangement along line y=-x.
- 00 01 02 03 04 05 00 06 0c 12 18 1e
- 06 07 08 09 0a 0b 01 07 0d 13 19 1f
- 0c 0d 0e 0f 10 11 <-> 02 08 0e 14 1a 20
- 12 13 14 15 16 17 03 09 0f 15 1b 21
- 18 19 1a 1b 1c 1d 04 0a 10 16 1c 22
- 1e 1f 20 21 22 23 05 0b 11 17 1d 23
- 00 01 02 03 00 04 08
- 04 05 06 07 <-> 01 05 09
- 08 09 0a 0b 02 06 0a
- 03 07 0b
- """
- if width == None:
- width = int(sqrt(len(tiles))) # assume square image
- tiles = sorted(enumerate(tiles), key= lambda i_tile: i_tile[0] % width)
- return [tile for i, tile in tiles]
- def transpose_tiles(image, width=None):
- return connect(transpose(get_tiles(image), width))
- def bitflip(x, n):
- r = 0
- while n:
- r = (r << 1) | (x & 1)
- x >>= 1
- n -= 1
- return r
- class Decompressor:
- """
- pokered pic decompression.
- 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 range(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 range(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 range(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 range(self.sizex):
- bit = 0
- for y in range(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 range(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 range(self.sizey):
- for x in range(self.sizex):
- i = 4 * y * self.sizex + x
- for j in range(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 range(7, -1, -1):
- yield (byte >> i) & 1
- def bitstream(b):
- for byte in b:
- for i in range(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 range(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 range(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 range(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 five times (twice for each mode, except 0)
- and use the smallest one (in bits).
- """
- rams = [[],[]]
- datas = []
- for mode in range(3):
- # 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 range(2):
- if mode == 0 and order == 0:
- continue
- for i in range(2):
- rams[i] = self.image[i::2]
- self._interpret_compress(rams, mode, order)
- datas += [(self.data[:], int(self.which_bit))]
- # Pick the smallest pic, measured in bits.
- datas = sorted(datas, key=lambda data_bit: (len(data_bit[0]), -data_bit[1]))
- self.data, self.which_bit = datas[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 x in range(self.width):
- for bit in range(0, 8, 2):
- byte = x * self.height * 8
- for y in range(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:
- 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):
- nums += 1
- # 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 range(bitcount):
- self._writebit(1)
- self._writebit(0)
- for j in range(bitcount, -1, -1):
- self._writebit((number >> j) & 1)
- def _encode(self, ram, mirror=None):
- a = b = 0
- for i in range(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 range(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]
- if bit: self.data[-1] |= bit << self.which_bit
- def _writeint(self, num, size=None):
- bits = []
- if size:
- for i in range(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 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)
- if __name__ == '__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':
- decompress_file(filename)
- elif args.mode == 'compress':
- compress_file(filename)
- #
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement