0
\$\begingroup\$

I'm trying to programmatically convert the largest mipmap of a 32x32 DXT5-compressed VTF file to a PNG. Here is what the image should look like after conversion:

enter image description here

And here is what mine looks like:

enter image description here

Obviously, something is wrong. Here is my code:

import texture2ddecoder, numpy
from PIL import Image

# 16x16 because VTF is 32x32
# and since we divide by 2 to get mipmap sizes
# largest mipmap would be 32/2 by 32/2 or 16 by 16
img_width =  16
img_height = 16

# ring_dot.vtf contains just the last 256 bytes of the original VTF
# My logic behind keeping just the last 256 bytes:
    # Mipmap height = 16 px
    # Mipmap width = 16 px
    # Mipmap area (px) = 16*16 = 256 px
    # 1 px = 8 bits (this is because of how DXT5 compression works)
    # Mipmap area (bits) = 256 * 8 = 2048 bits
    # 8 bits = 1 byte 
    # Mipmap area (bytes) = 2048 / 8 = 256 bytes
    # 1 row (in hex editor) = 16 bytes
    # Mipmap area (rows) = 256 / 16 = 16 rows
encoded_binary = open('ring_dot.vtf','rb').read()

#decompressing dxt5 to get actual pixel colors, returns BGRA bytes
decoded_binary = texture2ddecoder.decode_bc3(encoded_binary, img_width, img_height)

#creating RGBA PNG, converting from BGRA
dec_img = Image.frombytes("RGBA", (img_width, img_height), decoded_binary, 'raw', ("BGRA"))

dec_img.show()
dec_img.save('ringDot.png')

I tried changing img_width and img_height to higher values like 32x32, 48x48, and 64x64 but to no avail. I suspect I'm determining the size of the largest mipmap (and therefore which bytes represent it in the binary) incorrectly, but the calculations make sense to me and if the largest mipmap really does come last, I don't see why it wouldn't be the last 256 bytes in this case. What am I doing wrong here?

\$\endgroup\$

1 Answer 1

0
\$\begingroup\$

Mipmaps cannot be greater than the original texture/image size, but they can be less than or equal to it. So, for a 32x32 VTF, the largest mipmap would actually be 32x32, not 16x16. This means that (1)img_width and img_height should both be 32 instead of 16 and (2)I would actually need to decompress the last 1024 bytes, not the last 256 bytes. Below is the working code, with some (I think) better comments and variable names:

import texture2ddecoder
from PIL import Image

# 32x32 because VTF is 32x32
# and since mipmaps can be <= original size
# largest mipmap would be 32x32
img_width =  32
img_height = 32

# ring_dot.vtf contains just the last 1024 bytes of the original VTF (will deal with parsing them out of the complete file later)
# My logic behind keeping just the last 1024 bytes:
    # Mipmap height = 32 px
    # Mipmap width = 32 px
    # Mipmap area (px) = 32*32= 1024 px
    # 1 px = 1 byte (this is because of how DXT5 compression works)
    # 1024 px = 1024 bytes
compressed_binary = open('ring_dot.vtf','rb').read()

#decompressing dxt5 to get actual pixel colors, returns BGRA bytes
decompressed_binary = texture2ddecoder.decode_bc3(compressed_binary, img_width, img_height)

#creating RGBA PNG, converting from BGRA
decompressed_img = Image.frombytes("RGBA", (img_width, img_height), decompressed_binary, 'raw', ("BGRA"))

decompressed_img.show()
decompressed_img.save('ringDot.png')
```
\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .