| # astc_writer.py |
| # |
| # Minimal ASTC writer that mirrors the C/C++ write_astc_file() logic from example_capi.c. |
| # Writes a valid single-slice 2D ASTC texture file (no array slices, no 3D, no mips). |
| # |
| # Usage: |
| # from astc_writer import write_astc_file |
| # write_astc_file("output.astc", blocks, block_width, block_height, width, height) |
| # |
| # "blocks" must be a bytes-like object containing the full ASTC block data |
| # using 16 bytes per block (standard ASTC block size). |
| |
| |
| def write_astc_file( |
| filename: str, |
| blocks: bytes, |
| block_width: int, |
| block_height: int, |
| width: int, |
| height: int |
| ) -> None: |
| """ |
| Write an ASTC file to disk. |
| |
| Parameters: |
| filename : Output filename ("something.astc") |
| blocks : Bytes-like object containing ASTC blocks (16 bytes per block) |
| block_width : ASTC block width (e.g. 4-12) |
| block_height : ASTC block height (e.g. 4-12) |
| width : Original image width in pixels |
| height : Original image height in pixels |
| |
| Notes: |
| - ASTC files use 2D blocks; depth is always 1. |
| - Block layout goes row-major: (num_blocks_y num_blocks_x) blocks. |
| - No mipmaps are stored in this format. |
| """ |
| |
| # Validate block dimensions |
| if block_width < 4 or block_width > 12: |
| raise ValueError(f"ASTC block_width {block_width} out of range (412)") |
| if block_height < 4 or block_height > 12: |
| raise ValueError(f"ASTC block_height {block_height} out of range (412)") |
| |
| # Compute block grid |
| num_blocks_x = (width + block_width - 1) // block_width |
| num_blocks_y = (height + block_height - 1) // block_height |
| total_blocks = num_blocks_x * num_blocks_y |
| expected_size = total_blocks * 16 # 16 bytes per ASTC block (always) |
| |
| if len(blocks) != expected_size: |
| raise ValueError( |
| f"ASTC block buffer incorrect size: expected {expected_size}, got {len(blocks)}" |
| ) |
| |
| # Write file |
| with open(filename, "wb") as f: |
| # ASTC magic number (0x13AB A15C) |
| f.write(bytes([0x13, 0xAB, 0xA1, 0x5C])) |
| |
| # Block dims: x, y, z (z=1) |
| f.write(bytes([ |
| block_width & 0xFF, |
| block_height & 0xFF, |
| 1 |
| ])) |
| |
| # ASTC stores width/height/depth as 24-bit LE |
| def write_24bit_le(v: int): |
| f.write(bytes([ |
| v & 0xFF, |
| (v >> 8) & 0xFF, |
| (v >> 16) & 0xFF |
| ])) |
| |
| write_24bit_le(width) |
| write_24bit_le(height) |
| write_24bit_le(1) # depth |
| |
| # Write actual block payload |
| f.write(blocks) |
| |
| print(f"[ASTC Writer] Wrote: {filename} ({width}x{height}, {block_width}x{block_height} blocks)") |