#!/usr/bin/env python3 """Convert PNG to BLP2 (DXT3 compressed, with mipmaps).""" import struct import sys from PIL import Image def rgb_to_565(r, g, b): return ((r >> 3) << 11) | ((g >> 2) << 5) | (b >> 3) def color_565_to_rgb(c): r = ((c >> 11) & 0x1F) << 3 g = ((c >> 5) & 0x3F) << 2 b = (c & 0x1F) << 3 return r, g, b def color_distance(r0, g0, b0, r1, g1, b1): return (r0 - r1) ** 2 + (g0 - g1) ** 2 + (b0 - b1) ** 2 def encode_dxt1_block(pixels): """Encode a 4x4 block of (r,g,b) tuples into 8-byte DXT1 color block. pixels: list of 16 (r,g,b) tuples in row-major order. """ # Find min/max colors by luminance min_c = min(pixels, key=lambda c: c[0] * 299 + c[1] * 587 + c[2] * 114) max_c = max(pixels, key=lambda c: c[0] * 299 + c[1] * 587 + c[2] * 114) color0 = rgb_to_565(*max_c) color1 = rgb_to_565(*min_c) # Ensure color0 > color1 for 4-color mode if color0 == color1: # All same color, indices all 0 return struct.pack('> 4) << (i * 4) alpha_data = struct.pack(' 1 or h > 1: w = max(1, w // 2) h = max(1, h // 2) levels.append(img.resize((w, h), Image.LANCZOS)) return [(l.size[0], l.size[1], l.tobytes()) for l in levels] def png_to_blp2(png_path, blp_path): img = Image.open(png_path).convert('RGBA') width, height = img.size mipmaps = generate_mipmaps(img) num_mipmaps = min(len(mipmaps), 16) # Encode each mipmap level as DXT3 encoded = [] for w, h, data in mipmaps[:num_mipmaps]: encoded.append(encode_dxt3_image(data, w, h)) # BLP2 header: 1172 bytes # 4 (magic) + 4 (type) + 1 (compression) + 1 (alpha_depth) + 1 (alpha_type) # + 1 (has_mips) + 4 (width) + 4 (height) + 64 (offsets) + 64 (lengths) # + 1024 (palette) = 1172 header_size = 4 + 4 + 1 + 1 + 1 + 1 + 4 + 4 + 64 + 64 + 1024 # Calculate offsets offsets = [0] * 16 lengths = [0] * 16 offset = header_size for i, data in enumerate(encoded): offsets[i] = offset lengths[i] = len(data) offset += len(data) # Build header header = bytearray() header.extend(b'BLP2') header.extend(struct.pack(' {blp_path}') print(f' {width}x{height}, {num_mipmaps} mipmaps, DXT3') print(f' {offset} bytes total') if __name__ == '__main__': if len(sys.argv) != 3: print(f'Usage: {sys.argv[0]} input.png output.blp') sys.exit(1) png_to_blp2(sys.argv[1], sys.argv[2])