1
0
forked from Rowland/EG
EG/Cesium-1.132/node_modules/ktx-parse/src/read.ts
2025-08-25 17:48:13 +08:00

227 lines
7.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { BufferReader } from './buffer-reader.js';
import { KTX2_ID } from './constants-internal.js';
import { KHR_DF_SAMPLE_DATATYPE_SIGNED, type Supercompression, type VKFormat } from './constants.js';
import type { KTX2BasicFormatSample, KTX2Container, KTX2DataFormatDescriptorBasicFormat } from './container.js';
import { createDefaultContainer } from './create-default-container.js';
import { decodeText } from './util.js';
/**
* Parses a KTX 2.0 file, returning an unpacked {@link KTX2Container} instance with all associated
* data. The container's mip levels and other binary data are pointers into the original file, not
* copies, so the original file should not be overwritten after reading.
*
* @param data Bytes of KTX 2.0 file, as Uint8Array or Buffer.
*/
export function read(data: Uint8Array): KTX2Container {
///////////////////////////////////////////////////
// KTX 2.0 Identifier.
///////////////////////////////////////////////////
const id = new Uint8Array(data.buffer, data.byteOffset, KTX2_ID.length);
if (
id[0] !== KTX2_ID[0] || // '´'
id[1] !== KTX2_ID[1] || // 'K'
id[2] !== KTX2_ID[2] || // 'T'
id[3] !== KTX2_ID[3] || // 'X'
id[4] !== KTX2_ID[4] || // ' '
id[5] !== KTX2_ID[5] || // '2'
id[6] !== KTX2_ID[6] || // '0'
id[7] !== KTX2_ID[7] || // 'ª'
id[8] !== KTX2_ID[8] || // '\r'
id[9] !== KTX2_ID[9] || // '\n'
id[10] !== KTX2_ID[10] || // '\x1A'
id[11] !== KTX2_ID[11] // '\n'
) {
throw new Error('Missing KTX 2.0 identifier.');
}
const container = createDefaultContainer();
///////////////////////////////////////////////////
// Header.
///////////////////////////////////////////////////
const headerByteLength = 17 * Uint32Array.BYTES_PER_ELEMENT;
const headerReader = new BufferReader(data, KTX2_ID.length, headerByteLength, true);
container.vkFormat = headerReader._nextUint32() as VKFormat;
container.typeSize = headerReader._nextUint32();
container.pixelWidth = headerReader._nextUint32();
container.pixelHeight = headerReader._nextUint32();
container.pixelDepth = headerReader._nextUint32();
container.layerCount = headerReader._nextUint32();
container.faceCount = headerReader._nextUint32();
container.levelCount = headerReader._nextUint32();
container.supercompressionScheme = headerReader._nextUint32() as Supercompression;
const dfdByteOffset = headerReader._nextUint32();
const dfdByteLength = headerReader._nextUint32();
const kvdByteOffset = headerReader._nextUint32();
const kvdByteLength = headerReader._nextUint32();
const sgdByteOffset = headerReader._nextUint64();
const sgdByteLength = headerReader._nextUint64();
///////////////////////////////////////////////////
// Level Index.
///////////////////////////////////////////////////
const levelByteLength = Math.max(container.levelCount, 1) * 3 * 8;
const levelReader = new BufferReader(data, KTX2_ID.length + headerByteLength, levelByteLength, true);
for (let i = 0, il = Math.max(container.levelCount, 1); i < il; i++) {
container.levels.push({
levelData: new Uint8Array(data.buffer, data.byteOffset + levelReader._nextUint64(), levelReader._nextUint64()),
uncompressedByteLength: levelReader._nextUint64(),
});
}
///////////////////////////////////////////////////
// Data Format Descriptor (DFD).
///////////////////////////////////////////////////
const dfdReader = new BufferReader(data, dfdByteOffset, dfdByteLength, true);
dfdReader._skip(4); // totalSize
const vendorId = dfdReader._nextUint16();
const descriptorType = dfdReader._nextUint16();
const versionNumber = dfdReader._nextUint16();
const descriptorBlockSize = dfdReader._nextUint16();
const colorModel = dfdReader._nextUint8();
const colorPrimaries = dfdReader._nextUint8();
const transferFunction = dfdReader._nextUint8();
const flags = dfdReader._nextUint8();
const texelBlockDimension = [
dfdReader._nextUint8(),
dfdReader._nextUint8(),
dfdReader._nextUint8(),
dfdReader._nextUint8(),
] as KTX2DataFormatDescriptorBasicFormat['texelBlockDimension'];
const bytesPlane = [
dfdReader._nextUint8(),
dfdReader._nextUint8(),
dfdReader._nextUint8(),
dfdReader._nextUint8(),
dfdReader._nextUint8(),
dfdReader._nextUint8(),
dfdReader._nextUint8(),
dfdReader._nextUint8(),
] as KTX2DataFormatDescriptorBasicFormat['bytesPlane'];
const samples = [] as KTX2BasicFormatSample[];
const dfd: KTX2DataFormatDescriptorBasicFormat = {
vendorId,
descriptorType,
versionNumber,
colorModel,
colorPrimaries,
transferFunction,
flags,
texelBlockDimension,
bytesPlane,
samples,
};
const sampleStart = 6;
const sampleWords = 4;
const numSamples = (descriptorBlockSize / 4 - sampleStart) / sampleWords;
for (let i = 0; i < numSamples; i++) {
const sample = {
bitOffset: dfdReader._nextUint16(),
bitLength: dfdReader._nextUint8(),
channelType: dfdReader._nextUint8(),
samplePosition: [dfdReader._nextUint8(), dfdReader._nextUint8(), dfdReader._nextUint8(), dfdReader._nextUint8()],
sampleLower: Number.NEGATIVE_INFINITY,
sampleUpper: Number.POSITIVE_INFINITY,
};
if (sample.channelType & KHR_DF_SAMPLE_DATATYPE_SIGNED) {
sample.sampleLower = dfdReader._nextInt32();
sample.sampleUpper = dfdReader._nextInt32();
} else {
sample.sampleLower = dfdReader._nextUint32();
sample.sampleUpper = dfdReader._nextUint32();
}
dfd.samples[i] = sample;
}
container.dataFormatDescriptor.length = 0;
container.dataFormatDescriptor.push(dfd);
///////////////////////////////////////////////////
// Key/Value Data (KVD).
///////////////////////////////////////////////////
const kvdReader = new BufferReader(data, kvdByteOffset, kvdByteLength, true);
while (kvdReader._offset < kvdByteLength) {
const keyValueByteLength = kvdReader._nextUint32();
const keyData = kvdReader._scan(keyValueByteLength);
const key = decodeText(keyData);
container.keyValue[key] = kvdReader._nextUint8Array(keyValueByteLength - keyData.byteLength - 1);
if (key.match(/^ktx/i)) {
const text = decodeText(container.keyValue[key] as Uint8Array);
container.keyValue[key] = text.substring(0, text.lastIndexOf('\x00'));
}
const kvPadding = keyValueByteLength % 4 ? 4 - (keyValueByteLength % 4) : 0; // align(4)
// 4-byte alignment.
kvdReader._skip(kvPadding);
}
///////////////////////////////////////////////////
// Supercompression Global Data (SGD).
///////////////////////////////////////////////////
if (sgdByteLength <= 0) return container;
const sgdReader = new BufferReader(data, sgdByteOffset, sgdByteLength, true);
const endpointCount = sgdReader._nextUint16();
const selectorCount = sgdReader._nextUint16();
const endpointsByteLength = sgdReader._nextUint32();
const selectorsByteLength = sgdReader._nextUint32();
const tablesByteLength = sgdReader._nextUint32();
const extendedByteLength = sgdReader._nextUint32();
const imageDescs = [];
for (let i = 0, il = Math.max(container.levelCount, 1); i < il; i++) {
imageDescs.push({
imageFlags: sgdReader._nextUint32(),
rgbSliceByteOffset: sgdReader._nextUint32(),
rgbSliceByteLength: sgdReader._nextUint32(),
alphaSliceByteOffset: sgdReader._nextUint32(),
alphaSliceByteLength: sgdReader._nextUint32(),
});
}
const endpointsByteOffset = sgdByteOffset + sgdReader._offset;
const selectorsByteOffset = endpointsByteOffset + endpointsByteLength;
const tablesByteOffset = selectorsByteOffset + selectorsByteLength;
const extendedByteOffset = tablesByteOffset + tablesByteLength;
const endpointsData = new Uint8Array(data.buffer, data.byteOffset + endpointsByteOffset, endpointsByteLength);
const selectorsData = new Uint8Array(data.buffer, data.byteOffset + selectorsByteOffset, selectorsByteLength);
const tablesData = new Uint8Array(data.buffer, data.byteOffset + tablesByteOffset, tablesByteLength);
const extendedData = new Uint8Array(data.buffer, data.byteOffset + extendedByteOffset, extendedByteLength);
container.globalData = {
endpointCount,
selectorCount,
imageDescs,
endpointsData,
selectorsData,
tablesData,
extendedData,
};
return container;
}