veekun_pokedex/pokedex/extract/lib/etc1.py

112 lines
4 KiB
Python
Raw Normal View History

"""Parse ETC1, a terrible micro block-based image compression format.
Please enjoy the docs.
https://www.khronos.org/registry/gles/extensions/OES/OES_compressed_ETC1_RGB8_texture.txt
"""
import io
import itertools
three_bit_twos_complement = [0, 1, 2, 3, -4, -3, -2, -1]
etc1_modifier_tables = [
( 2, 8, -2, -8),
( 5, 17, -5, -17),
( 9, 29, -9, -29),
(13, 42, -13, -42),
(18, 60, -18, -60),
(24, 80, -24, -80),
(33, 106, -33, -106),
(47, 183, -47, -183),
]
def decode_etc1(data):
# TODO sizes are hardcoded here
f = io.BytesIO(data)
f.read(0x80)
outpixels = [[None] * 128 for _ in range(128)]
for blocky in range(0, 128, 8):
for blockx in range(0, 128, 8):
for z in range(4):
row = f.read(16)
if not row:
raise RuntimeError
alpha = row[:8]
etc1 = int.from_bytes(row[8:], 'big')
diffbit = row[12] & 2
flipbit = row[12] & 1
lopixelbits = int.from_bytes(row[8:10], 'little')
hipixelbits = int.from_bytes(row[10:12], 'little')
if diffbit:
red1 = row[15] >> 3
red2 = max(0, red1 + three_bit_twos_complement[row[15] & 0x7])
green1 = row[14] >> 3
green2 = max(0, green1 + three_bit_twos_complement[row[14] & 0x7])
blue1 = row[13] >> 3
blue2 = max(0, blue1 + three_bit_twos_complement[row[13] & 0x7])
red1 = (red1 << 3) | (red1 >> 2)
green1 = (green1 << 3) | (green1 >> 2)
blue1 = (blue1 << 3) | (blue1 >> 2)
red2 = (red2 << 3) | (red2 >> 2)
green2 = (green2 << 3) | (green2 >> 2)
blue2 = (blue2 << 3) | (blue2 >> 2)
else:
red1 = row[15] >> 4
red2 = row[15] & 0xf
green1 = row[14] >> 4
green2 = row[14] & 0xf
blue1 = row[13] >> 4
blue2 = row[13] & 0xf
red1 = (red1 << 4) | red1
green1 = (green1 << 4) | green1
blue1 = (blue1 << 4) | blue1
red2 = (red2 << 4) | red2
green2 = (green2 << 4) | green2
blue2 = (blue2 << 4) | blue2
base1 = red1, green1, blue1
base2 = red2, green2, blue2
codeword1 = row[12] >> 5
codeword2 = (row[12] >> 2) & 0x7
table1 = etc1_modifier_tables[codeword1]
table2 = etc1_modifier_tables[codeword2]
def nybbles(b):
for byte in b:
yield (byte & 0xf) << 4
yield byte >> 4 << 4
it = nybbles(alpha)
for c in range(4):
for r in range(4):
x = blockx + c
y = blocky + r
if z in (1, 3):
x += 4
if z in (2, 3):
y += 4
if flipbit:
# Horizontal
whichblock = 1 if r < 2 else 2
else:
whichblock = 1 if c < 2 else 2
if whichblock == 1:
table = table1
base = base1
else:
table = table2
base = base2
pixelbit = c * 4 + r
idx = 2 * ((hipixelbits >> pixelbit) & 1) + ((lopixelbits >> pixelbit) & 1)
mod = table[idx]
color = tuple(min(255, max(0, b + mod)) for b in base) + (next(it),)
outpixels[y][x] = color
return 128, 128, 4, None, outpixels