Task 347: .KT File Format
Task 347: .KT File Format
File Format Specifications for the .KT File Format
The .KT file format is the KTX (Khronos Texture) format, a binary container for GPU textures used in OpenGL, OpenGL ES, Vulkan, and other graphics APIs. It supports various image formats, mipmaps, array layers, cubemaps, and supercompression. The format is little-endian, with a fixed identifier for validation. The file structure includes a header, an index section, optional descriptors and data sections, and the actual texture data.
List of all the properties of this file format intrinsic to its file system:
- Identifier: 12-byte fixed sequence for file validation.
- vkFormat: UInt32 specifying the Vulkan format enum for the texture data.
- typeSize: UInt32 indicating the size in bytes of the base data type.
- pixelWidth: UInt32 for the width in pixels of the base level.
- pixelHeight: UInt32 for the height in pixels of the base level.
- pixelDepth: UInt32 for the depth in pixels of the base level (0 for 2D textures).
- layerCount: UInt32 for the number of array layers.
- faceCount: UInt32 for the number of cubemap faces (1 or 6).
- levelCount: UInt32 for the number of mipmap levels.
- supercompressionScheme: UInt32 indicating the supercompression method (e.g., 0 for none, 1 for BasisLZ).
- dfdByteOffset: UInt32 offset to the Data Format Descriptor (DFD).
- dfdByteLength: UInt32 length of the DFD.
- kvdByteOffset: UInt32 offset to the Key/Value Data (KVD).
- kvdByteLength: UInt32 length of the KVD.
- sgdByteOffset: UInt64 offset to the Supercompression Global Data (SGD).
- sgdByteLength: UInt64 length of the SGD.
- Level Index: Array of levelCount entries, each with byteOffset (UInt64), byteLength (UInt64), and uncompressedByteLength (UInt64) for each mipmap level's data.
Two direct download links for files of format .KT:
- https://raw.githubusercontent.com/KhronosGroup/KTX-Software/master/tests/testimages/color_grid_uastc_rgba.ktx2
- https://raw.githubusercontent.com/KhronosGroup/KTX-Software/master/tests/testimages/color_grid_zstd.ktx2
Ghost blog embedded HTML JavaScript for drag and drop .KT file to dump properties to screen:
Drag and drop a .KT file here
- Python class that can open any file of format .KT and decode read and write and print to console all the properties from the above list:
import struct
import sys
class KTFile:
def __init__(self, filename):
self.filename = filename
self.properties = {}
self.read_file()
def read_file(self):
with open(self.filename, 'rb') as f:
data = f.read()
# Identifier (bytes 0-11)
self.properties['identifier'] = data[0:12]
# Unpack little-endian
self.properties['vkFormat'] = struct.unpack_from('<I', data, 12)[0]
self.properties['typeSize'] = struct.unpack_from('<I', data, 16)[0]
self.properties['pixelWidth'] = struct.unpack_from('<I', data, 20)[0]
self.properties['pixelHeight'] = struct.unpack_from('<I', data, 24)[0]
self.properties['pixelDepth'] = struct.unpack_from('<I', data, 28)[0]
self.properties['layerCount'] = struct.unpack_from('<I', data, 32)[0]
self.properties['faceCount'] = struct.unpack_from('<I', data, 36)[0]
level_count = struct.unpack_from('<I', data, 40)[0]
self.properties['levelCount'] = level_count
self.properties['supercompressionScheme'] = struct.unpack_from('<I', data, 44)[0]
self.properties['dfdByteOffset'] = struct.unpack_from('<I', data, 48)[0]
self.properties['dfdByteLength'] = struct.unpack_from('<I', data, 52)[0]
self.properties['kvdByteOffset'] = struct.unpack_from('<I', data, 56)[0]
self.properties['kvdByteLength'] = struct.unpack_from('<I', data, 60)[0]
self.properties['sgdByteOffset'] = struct.unpack_from('<Q', data, 64)[0]
self.properties['sgdByteLength'] = struct.unpack_from('<Q', data, 72)[0]
# Level Index
self.properties['levelIndex'] = []
for i in range(level_count):
base = 80 + i * 24
byte_offset = struct.unpack_from('<Q', data, base)[0]
byte_length = struct.unpack_from('<Q', data, base + 8)[0]
uncompressed_byte_length = struct.unpack_from('<Q', data, base + 16)[0]
self.properties['levelIndex'].append({
'byteOffset': byte_offset,
'byteLength': byte_length,
'uncompressedByteLength': uncompressed_byte_length
})
self.print_properties()
def print_properties(self):
print("KT File Properties:")
for key, value in self.properties.items():
if key == 'identifier':
print(f"{key}: {value}")
elif key == 'levelIndex':
print(f"{key}:")
for idx, level in enumerate(value):
print(f" Level {idx}:")
for k, v in level.items():
print(f" {k}: {v}")
else:
print(f"{key}: {value}")
def write_file(self, output_filename):
# Simple write: recreate header from properties, assuming no data sections for simplicity
header = b''
header += self.properties['identifier']
header += struct.pack('<I', self.properties['vkFormat'])
header += struct.pack('<I', self.properties['typeSize'])
header += struct.pack('<I', self.properties['pixelWidth'])
header += struct.pack('<I', self.properties['pixelHeight'])
header += struct.pack('<I', self.properties['pixelDepth'])
header += struct.pack('<I', self.properties['layerCount'])
header += struct.pack('<I', self.properties['faceCount'])
header += struct.pack('<I', self.properties['levelCount'])
header += struct.pack('<I', self.properties['supercompressionScheme'])
header += struct.pack('<I', self.properties['dfdByteOffset'])
header += struct.pack('<I', self.properties['dfdByteLength'])
header += struct.pack('<I', self.properties['kvdByteOffset'])
header += struct.pack('<I', self.properties['kvdByteLength'])
header += struct.pack('<Q', self.properties['sgdByteOffset'])
header += struct.pack('<Q', self.properties['sgdByteLength'])
for level in self.properties['levelIndex']:
header += struct.pack('<Q', level['byteOffset'])
header += struct.pack('<Q', level['byteLength'])
header += struct.pack('<Q', level['uncompressedByteLength'])
with open(output_filename, 'wb') as f:
f.write(header)
print(f"Written to {output_filename}")
# Example usage
if __name__ == '__main__':
if len(sys.argv) < 2:
print("Usage: python kt_parser.py input.kt [output.kt]")
sys.exit(1)
kt = KTFile(sys.argv[1])
if len(sys.argv) > 2:
kt.write_file(sys.argv[2])
- Java class that can open any file of format .KT and decode read and write and print to console all the properties from the above list:
import java.io.*;
import java.nio.*;
import java.nio.channels.FileChannel;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
public class KTFile {
private String filename;
private Map<String, Object> properties = new HashMap<>();
public KTFile(String filename) {
this.filename = filename;
readFile();
}
private void readFile() {
try {
byte[] data = Files.readAllBytes(Paths.get(filename));
ByteBuffer buffer = ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN);
// Identifier (bytes 0-11)
byte[] identifier = new byte[12];
buffer.position(0);
buffer.get(identifier);
properties.put("identifier", identifier);
// vkFormat (UInt32 at 12)
properties.put("vkFormat", buffer.getInt(12));
// typeSize (UInt32 at 16)
properties.put("typeSize", buffer.getInt(16));
// pixelWidth (UInt32 at 20)
properties.put("pixelWidth", buffer.getInt(20));
// pixelHeight (UInt32 at 24)
properties.put("pixelHeight", buffer.getInt(24));
// pixelDepth (UInt32 at 28)
properties.put("pixelDepth", buffer.getInt(28));
// layerCount (UInt32 at 32)
properties.put("layerCount", buffer.getInt(32));
// faceCount (UInt32 at 36)
properties.put("faceCount", buffer.getInt(36));
// levelCount (UInt32 at 40)
int levelCount = buffer.getInt(40);
properties.put("levelCount", levelCount);
// supercompressionScheme (UInt32 at 44)
properties.put("supercompressionScheme", buffer.getInt(44));
// dfdByteOffset (UInt32 at 48)
properties.put("dfdByteOffset", buffer.getInt(48));
// dfdByteLength (UInt32 at 52)
properties.put("dfdByteLength", buffer.getInt(52));
// kvdByteOffset (UInt32 at 56)
properties.put("kvdByteOffset", buffer.getInt(56));
// kvdByteLength (UInt32 at 60)
properties.put("kvdByteLength", buffer.getInt(60));
// sgdByteOffset (UInt64 at 64)
properties.put("sgdByteOffset", buffer.getLong(64));
// sgdByteLength (UInt64 at 72)
properties.put("sgdByteLength", buffer.getLong(72));
// Level Index
ArrayList<Map<String, Long>> levelIndex = new ArrayList<>();
for (int i = 0; i < levelCount; i++) {
int base = 80 + i * 24;
Map<String, Long> level = new HashMap<>();
level.put("byteOffset", buffer.getLong(base));
level.put("byteLength", buffer.getLong(base + 8));
level.put("uncompressedByteLength", buffer.getLong(base + 16));
levelIndex.add(level);
}
properties.put("levelIndex", levelIndex);
printProperties();
} catch (IOException e) {
e.printStackTrace();
}
}
public void printProperties() {
System.out.println("KT File Properties:");
for (Map.Entry<String, Object> entry : properties.entrySet()) {
if (entry.getKey().equals("identifier")) {
byte[] id = (byte[]) entry.getValue();
System.out.print(entry.getKey() + ": ");
for (byte b : id) {
System.out.print(b + " ");
}
System.out.println();
} else if (entry.getKey().equals("levelIndex")) {
System.out.println(entry.getKey() + ":");
ArrayList<Map<String, Long>> levels = (ArrayList<Map<String, Long>>) entry.getValue();
for (int i = 0; i < levels.size(); i++) {
System.out.println(" Level " + i + ":");
for (Map.Entry<String, Long> levelEntry : levels.get(i).entrySet()) {
System.out.println(" " + levelEntry.getKey() + ": " + levelEntry.getValue());
}
}
} else {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
}
}
public void writeFile(String outputFilename) {
try (RandomAccessFile file = new RandomAccessFile(outputFilename, "rw");
FileChannel channel = file.getChannel()) {
ByteBuffer buffer = ByteBuffer.allocate(80 + ((int)properties.get("levelCount") * 24)).order(ByteOrder.LITTLE_ENDIAN);
// Identifier
buffer.put((byte[])properties.get("identifier"));
// vkFormat
buffer.putInt((int)properties.get("vkFormat"));
// typeSize
buffer.putInt((int)properties.get("typeSize"));
// pixelWidth
buffer.putInt((int)properties.get("pixelWidth"));
// pixelHeight
buffer.putInt((int)properties.get("pixelHeight"));
// pixelDepth
buffer.putInt((int)properties.get("pixelDepth"));
// layerCount
buffer.putInt((int)properties.get("layerCount"));
// faceCount
buffer.putInt((int)properties.get("faceCount"));
// levelCount
buffer.putInt((int)properties.get("levelCount"));
// supercompressionScheme
buffer.putInt((int)properties.get("supercompressionScheme"));
// dfdByteOffset
buffer.putInt((int)properties.get("dfdByteOffset"));
// dfdByteLength
buffer.putInt((int)properties.get("dfdByteLength"));
// kvdByteOffset
buffer.putInt((int)properties.get("kvdByteOffset"));
// kvdByteLength
buffer.putInt((int)properties.get("kvdByteLength"));
// sgdByteOffset
buffer.putLong((long)properties.get("sgdByteOffset"));
// sgdByteLength
buffer.putLong((long)properties.get("sgdByteLength"));
// Level Index
ArrayList<Map<String, Long>> levels = (ArrayList<Map<String, Long>>) properties.get("levelIndex");
for (Map<String, Long> level : levels) {
buffer.putLong(level.get("byteOffset"));
buffer.putLong(level.get("byteLength"));
buffer.putLong(level.get("uncompressedByteLength"));
}
buffer.flip();
channel.write(buffer);
System.out.println("Written to " + outputFilename);
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
if (args.length < 1) {
System.out.println("Usage: java KTFile input.kt [output.kt]");
System.exit(1);
}
KTFile kt = new KTFile(args[0]);
if (args.length > 1) {
kt.writeFile(args[1]);
}
}
}
- JavaScript class that can open any file of format .KT and decode read and write and print to console all the properties from the above list:
const fs = require('fs'); // For Node.js
class KTFile {
constructor(filename) {
this.filename = filename;
this.properties = {};
this.readFile();
}
readFile() {
const data = fs.readFileSync(this.filename);
const dataView = new DataView(data.buffer);
// Identifier (bytes 0-11)
let identifier = '';
for (let i = 0; i < 12; i++) {
identifier += dataView.getUint8(i) + ' ';
}
this.properties.identifier = identifier.trim();
// vkFormat (UInt32 at 12)
this.properties.vkFormat = dataView.getUint32(12, true);
// typeSize (UInt32 at 16)
this.properties.typeSize = dataView.getUint32(16, true);
// pixelWidth (UInt32 at 20)
this.properties.pixelWidth = dataView.getUint32(20, true);
// pixelHeight (UInt32 at 24)
this.properties.pixelHeight = dataView.getUint32(24, true);
// pixelDepth (UInt32 at 28)
this.properties.pixelDepth = dataView.getUint32(28, true);
// layerCount (UInt32 at 32)
this.properties.layerCount = dataView.getUint32(32, true);
// faceCount (UInt32 at 36)
this.properties.faceCount = dataView.getUint32(36, true);
// levelCount (UInt32 at 40)
const levelCount = dataView.getUint32(40, true);
this.properties.levelCount = levelCount;
// supercompressionScheme (UInt32 at 44)
this.properties.supercompressionScheme = dataView.getUint32(44, true);
// dfdByteOffset (UInt32 at 48)
this.properties.dfdByteOffset = dataView.getUint32(48, true);
// dfdByteLength (UInt32 at 52)
this.properties.dfdByteLength = dataView.getUint32(52, true);
// kvdByteOffset (UInt32 at 56)
this.properties.kvdByteOffset = dataView.getUint32(56, true);
// kvdByteLength (UInt32 at 60)
this.properties.kvdByteLength = dataView.getUint32(60, true);
// sgdByteOffset (UInt64 at 64)
this.properties.sgdByteOffset = Number(dataView.getBigUint64(64, true));
// sgdByteLength (UInt64 at 72)
this.properties.sgdByteLength = Number(dataView.getBigUint64(72, true));
// Level Index
this.properties.levelIndex = [];
for (let i = 0; i < levelCount; i++) {
const base = 80 + i * 24;
this.properties.levelIndex.push({
byteOffset: Number(dataView.getBigUint64(base, true)),
byteLength: Number(dataView.getBigUint64(base + 8, true)),
uncompressedByteLength: Number(dataView.getBigUint64(base + 16, true))
});
}
this.printProperties();
}
printProperties() {
console.log('KT File Properties:');
for (const [key, value] of Object.entries(this.properties)) {
if (key === 'levelIndex') {
console.log(`${key}:`);
value.forEach((level, idx) => {
console.log(` Level ${idx}:`);
for (const [k, v] of Object.entries(level)) {
console.log(` ${k}: ${v}`);
}
});
} else {
console.log(`${key}: ${value}`);
}
}
}
writeFile(outputFilename) {
const buffer = new ArrayBuffer(80 + this.properties.levelCount * 24);
const dataView = new DataView(buffer);
// Identifier
const idBytes = this.properties.identifier.split(' ').map(Number);
idBytes.forEach((byte, i) => dataView.setUint8(i, byte));
// vkFormat
dataView.setUint32(12, this.properties.vkFormat, true);
// typeSize
dataView.setUint32(16, this.properties.typeSize, true);
// pixelWidth
dataView.setUint32(20, this.properties.pixelWidth, true);
// pixelHeight
dataView.setUint32(24, this.properties.pixelHeight, true);
// pixelDepth
dataView.setUint32(28, this.properties.pixelDepth, true);
// layerCount
dataView.setUint32(32, this.properties.layerCount, true);
// faceCount
dataView.setUint32(36, this.properties.faceCount, true);
// levelCount
dataView.setUint32(40, this.properties.levelCount, true);
// supercompressionScheme
dataView.setUint32(44, this.properties.supercompressionScheme, true);
// dfdByteOffset
dataView.setUint32(48, this.properties.dfdByteOffset, true);
// dfdByteLength
dataView.setUint32(52, this.properties.dfdByteLength, true);
// kvdByteOffset
dataView.setUint32(56, this.properties.kvdByteOffset, true);
// kvdByteLength
dataView.setUint32(60, this.properties.kvdByteLength, true);
// sgdByteOffset
dataView.setBigUint64(64, BigInt(this.properties.sgdByteOffset), true);
// sgdByteLength
dataView.setBigUint64(72, BigInt(this.properties.sgdByteLength), true);
// Level Index
this.properties.levelIndex.forEach((level, i) => {
const base = 80 + i * 24;
dataView.setBigUint64(base, BigInt(level.byteOffset), true);
dataView.setBigUint64(base + 8, BigInt(level.byteLength), true);
dataView.setBigUint64(base + 16, BigInt(level.uncompressedByteLength), true);
});
fs.writeFileSync(outputFilename, new Uint8Array(buffer));
console.log(`Written to ${outputFilename}`);
}
}
// Example usage: node kt_parser.js input.kt [output.kt]
if (process.argv.length < 3) {
console.log('Usage: node kt_parser.js input.kt [output.kt]');
process.exit(1);
}
const kt = new KTFile(process.argv[2]);
if (process.argv.length > 3) {
kt.writeFile(process.argv[3]);
}
- C class that can open any file of format .KT and decode read and write and print to console all the properties from the above list:
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
typedef struct {
uint8_t identifier[12];
uint32_t vkFormat;
uint32_t typeSize;
uint32_t pixelWidth;
uint32_t pixelHeight;
uint32_t pixelDepth;
uint32_t layerCount;
uint32_t faceCount;
uint32_t levelCount;
uint32_t supercompressionScheme;
uint32_t dfdByteOffset;
uint32_t dfdByteLength;
uint32_t kvdByteOffset;
uint32_t kvdByteLength;
uint64_t sgdByteOffset;
uint64_t sgdByteLength;
struct Level {
uint64_t byteOffset;
uint64_t byteLength;
uint64_t uncompressedByteLength;
} *levelIndex;
} KTFile;
void read_kt_file(const char *filename, KTFile *kt) {
FILE *f = fopen(filename, "rb");
if (!f) {
perror("Error opening file");
exit(1);
}
fseek(f, 0, SEEK_END);
long size = ftell(f);
fseek(f, 0, SEEK_SET);
uint8_t *data = malloc(size);
fread(data, 1, size, f);
fclose(f);
memcpy(kt->identifier, data, 12);
memcpy(&kt->vkFormat, data + 12, 4);
memcpy(&kt->typeSize, data + 16, 4);
memcpy(&kt->pixelWidth, data + 20, 4);
memcpy(&kt->pixelHeight, data + 24, 4);
memcpy(&kt->pixelDepth, data + 28, 4);
memcpy(&kt->layerCount, data + 32, 4);
memcpy(&kt->faceCount, data + 36, 4);
memcpy(&kt->levelCount, data + 40, 4);
memcpy(&kt->supercompressionScheme, data + 44, 4);
memcpy(&kt->dfdByteOffset, data + 48, 4);
memcpy(&kt->dfdByteLength, data + 52, 4);
memcpy(&kt->kvdByteOffset, data + 56, 4);
memcpy(&kt->kvdByteLength, data + 60, 4);
memcpy(&kt->sgdByteOffset, data + 64, 8);
memcpy(&kt->sgdByteLength, data + 72, 8);
kt->levelIndex = malloc(kt->levelCount * sizeof(struct Level));
for (uint32_t i = 0; i < kt->levelCount; i++) {
size_t base = 80 + i * 24;
memcpy(&kt->levelIndex[i].byteOffset, data + base, 8);
memcpy(&kt->levelIndex[i].byteLength, data + base + 8, 8);
memcpy(&kt->levelIndex[i].uncompressedByteLength, data + base + 16, 8);
}
free(data);
}
void print_kt_properties(const KTFile *kt) {
printf("KT File Properties:\n");
printf("identifier: ");
for (int i = 0; i < 12; i++) {
printf("%u ", kt->identifier[i]);
}
printf("\n");
printf("vkFormat: %u\n", kt->vkFormat);
printf("typeSize: %u\n", kt->typeSize);
printf("pixelWidth: %u\n", kt->pixelWidth);
printf("pixelHeight: %u\n", kt->pixelHeight);
printf("pixelDepth: %u\n", kt->pixelDepth);
printf("layerCount: %u\n", kt->layerCount);
printf("faceCount: %u\n", kt->faceCount);
printf("levelCount: %u\n", kt->levelCount);
printf("supercompressionScheme: %u\n", kt->supercompressionScheme);
printf("dfdByteOffset: %u\n", kt->dfdByteOffset);
printf("dfdByteLength: %u\n", kt->dfdByteLength);
printf("kvdByteOffset: %u\n", kt->kvdByteOffset);
printf("kvdByteLength: %u\n", kt->kvdByteLength);
printf("sgdByteOffset: %lu\n", kt->sgdByteOffset);
printf("sgdByteLength: %lu\n", kt->sgdByteLength);
printf("levelIndex:\n");
for (uint32_t i = 0; i < kt->levelCount; i++) {
printf(" Level %u:\n", i);
printf(" byteOffset: %lu\n", kt->levelIndex[i].byteOffset);
printf(" byteLength: %lu\n", kt->levelIndex[i].byteLength);
printf(" uncompressedByteLength: %lu\n", kt->levelIndex[i].uncompressedByteLength);
}
}
void write_kt_file(const char *output_filename, const KTFile *kt) {
size_t header_size = 80 + kt->levelCount * 24;
uint8_t *header = malloc(header_size);
memcpy(header, kt->identifier, 12);
memcpy(header + 12, &kt->vkFormat, 4);
memcpy(header + 16, &kt->typeSize, 4);
memcpy(header + 20, &kt->pixelWidth, 4);
memcpy(header + 24, &kt->pixelHeight, 4);
memcpy(header + 28, &kt->pixelDepth, 4);
memcpy(header + 32, &kt->layerCount, 4);
memcpy(header + 36, &kt->faceCount, 4);
memcpy(header + 40, &kt->levelCount, 4);
memcpy(header + 44, &kt->supercompressionScheme, 4);
memcpy(header + 48, &kt->dfdByteOffset, 4);
memcpy(header + 52, &kt->dfdByteLength, 4);
memcpy(header + 56, &kt->kvdByteOffset, 4);
memcpy(header + 60, &kt->kvdByteLength, 4);
memcpy(header + 64, &kt->sgdByteOffset, 8);
memcpy(header + 72, &kt->sgdByteLength, 8);
for (uint32_t i = 0; i < kt->levelCount; i++) {
size_t base = 80 + i * 24;
memcpy(header + base, &kt->levelIndex[i].byteOffset, 8);
memcpy(header + base + 8, &kt->levelIndex[i].byteLength, 8);
memcpy(header + base + 16, &kt->levelIndex[i].uncompressedByteLength, 8);
}
FILE *f = fopen(output_filename, "wb");
if (!f) {
perror("Error opening output file");
exit(1);
}
fwrite(header, 1, header_size, f);
fclose(f);
free(header);
printf("Written to %s\n", output_filename);
}
void free_kt(KTFile *kt) {
free(kt->levelIndex);
}
int main(int argc, char **argv) {
if (argc < 2) {
printf("Usage: %s input.kt [output.kt]\n", argv[0]);
return 1;
}
KTFile kt;
read_kt_file(argv[1], &kt);
print_kt_properties(&kt);
if (argc > 2) {
write_kt_file(argv[2], &kt);
}
free_kt(&kt);
return 0;
}