KTX Javascript Wrappers Reference 4.4.0
Libraries and tools to create and read KTX image texture files.
All Pages
libktx Binding

WebIDL for the binding

Items marked with ** are only available in the full libktx.js wrapper. Unmarked items are available in both libktx.js and libktx_read.js.

interface Orientation {
readonly attribute OrientationX x;
readonly attribute OrientationY y;
readonly attribute OrientationZ z;
};
interface UploadResult {
readonly attribute WebGLTexture texture;
readonly attribute GLenum target;
readonly attribute GLenum error;
};
interface textureCreateInfo { // **
constructor();
attribute long vkFormat;
attribute long baseWidth;
attribute long baseHeight;
attribute long baseDepth;
attribute long numDimensions;
attribute long numLevels;
attribute long numLayers;
attribute long numFaces;
attribute boolean isArray;
attribute boolean generateMipmaps;
};
interface astcParams { // **
constructor();
attribute boolean verbose;
attribute long threadCount;
attribute astc_block_dimension blockDimension;
attribute pack_astc_encoder_mode mode;
attribute long qualityLevel;
attribute boolean normalMap;
attribute DOMString inputSwizzle;
};
interface basisParams { // **
constructor();
attribute boolean uastc,
attribute boolean verbose,
attribute boolean noSSE,
attribute long threadCount,
attribute DOMString inputSwizzle,
attribute boolean preSwizzle,
// ETC1S/Basis-LZ parameters.
attribute long compressionLevel,
attribute long qualityLevel,
attribute long maxEndpoints,
attribute float endpointRDOThreshold,
attribute long maxSelectors,
attribute float selectorRDOThreshold,
attribute boolean normalMap,
attribute boolean noEndpointRDO,
attribute boolean noSelectorRDO,
// UASTC parameters.
attribute pack_uastc_flag_bits uastcFlags,
attribute boolean uastcRDO,
attribute float uastcRDOQualityScalar,
attribute long uastcRDODictSize,
attribute float uastcRDOMaxSmoothBlockErrorScale,
attribute float uastcRDOMaxSmoothBlockStdDev,
attribute boolean uastcRDODontFavorSimplerModes,
attribute boolean uastcRDONoMultithreading
};
interface texture {
constructor(ArrayBufferView fileData);
constructor(textureCreateInfo createInfo, // **
CreateStorageEnum? storage);
error_code compressAstc(ktxAstcParams params); // **
error_code compressBasis(ktxBasisParams params); // **
texture createCopy(); // **
error_code defateZLIB(); // **
error_code deflateZstd(); // **
ArrayBufferView getImage(long level, long layer, long faceSlice);
UploadResult glUpload();
error_code setImageFromMemory(long level, long layer, long faceSlice,
ArrayBufferView imageData); // **
error_code transcodeBasis(transcode_fmt? target, transcode_flag_bits
decodeFlags);
ArrayBufferView writeToMemory(); // **
error_code addKVPairString(DOMString key, DOMString value); // **
error_code addKVPairByte(DOMString key, ArrayBuffewView value); // **
deleteKVPair(DOMString key); // **
DOMString? findKeyValue(DOMString key);
readonly attribute long baseWidth;
readonly attribute long baseHeight;
readonly attribute boolean isSRGB;
readonly attribute boolean isPremultiplied;
readonly attribute boolean needsTranscoding;
readonly attribute long numComponents;
readonly attribute long vkFormat;
readonly attribute SupercmpScheme supercompressionScheme;
readonly attribute ktxOrientation orientation;
attribute khr_df_transfer OETF; // Setting available only in libktx.js.
attribute khr_df_primaries primaries; // Setting available only in libktx.js.
};
enum error_code = {
"SUCCESS",
"FILE_DATA_ERROR",
"FILE_ISPIPE",
"FILE_OPEN_FAILED",
"FILE_OVERFLOW",
"FILE_READ_ERROR",
"FILE_SEEK_ERROR",
"FILE_UNEXPECTED_ERROR",
"FILE_WRITE_ERROR",
"GL_ERROR",
"INVALID_OPERATION",
"INVALID_VALUE",
"NOT_FOUND",
"OUT_OF_MEMORY",
"TRANSCODE_FAILED",
"UNKNOWN_FILE_FORMAT",
"UNSUPPORTED_TEXTURE_TYPE",
"UNSUPPORTED_FEATURE",
"LIBRARY_NOT_LINKED"
};
enum CreateStorageEnum = {
"NO_STORAGE",
"ALLOC_STORAGE"
};
// Some targets may not be available depending on options used when compiling
// the web assembly. ktxTexture.transcodeBasis will report this.
enum transcode_fmt = {
"ETC1_RGB",
"BC1_RGB",
"BC4_R",
"BC5_RG",
"BC3_RGBA",
"BC1_OR_3",
"PVRTC1_4_RGB",
"PVRTC1_4_RGBA",
"BC7_RGBA",
"ETC2_RGBA",
"ASTC_4x4_RGBA",
"RGBA32",
"RGB565",
"BGR565",
"RGBA4444",
"PVRTC2_4_RGB",
"PVRTC2_4_RGBA",
"ETC",
"EAC_R11",
"EAC_RG11"
};
enum transcode_flag_bits {
"TRANSCODE_ALPHA_DATA_TO_OPAQUE_FORMATS"
};
enum OrientationX {
"LEFT",
"RIGHT"
};
enum OrientationY {
"UP",
"DOWN"
};
enum OrientationZ {
"IN",
"OUT"
};
enum SupercmpScheme {
"NONE",
"BASIS_LZ",
"ZSTD"
"ZLIB"
};
enum khr_df_primaries = {
// These are the values needed for KTX with HTML5/WebGL.
"UNSPECIFIED",
"BT709",
"SRGB"
"DISPLAYP3"
};
enum khr_df_transfer = {
// These are the values needed for KTX with HTML5/WebGL.
"UNSPECIFIED",
"LINEAR",
"SRGB",
// DisplayP3 uses the SRGB transfer function.
};
enum VkFormat = {
"R8G8B8A8_SRGB",
"R8G8B8A8_UNORM"
// Full list omitted as its length will distract from the documentation
// purpose of this IDL. Any VkFormat valid for KTX can be used. As shown
// here, omit the VK_FORMAT_ prefix and enclose in quotes.
enum pack_astc_quality_levels = { // **
"FASTEST",
"FAST",
"MEDIUM",
"THOROUGH",
"EXHAUSTIVE",
};
enum pack_astc_block_dimension = { // **
// 2D formats
"D4x4",
"D5x4",
"D5x5",
"D6x5",
"D6x6",
"D8x5",
"D8x6",
"D10x5",
"D10x6",
"D8x8",
"D10x8",
"D10x10",
"D12x10",
"D12x12",
// 3D formats
"D3x3x3",
"D4x3x3",
"D4x4x3",
"D4x4x4",
"D5x4x4",
"D5x5x4",
"D5x5x5",
"D6x5x5",
"D6x6x5",
"D6x6x6"
};
enum pack_astc_encoder_mode = { // **
"DEFAULT",
"LDR",
"HDR"
};
enum pack_uastc_flag_bits = { // **
"LEVEL_FASTEST",
"LEVEL_FASTER",
"LEVEL_DEFAULT",
"LEVEL_SLOWER",
"LEVEL_VERYSLOW",
};
const DOMString ANIMDATA_KEY = "KTXanimData";
const DOMStringORIENTATION_KEY = "KTXorientation";
const DOMString SWIZZLE_KEY = "KTXswizzle";
const DOMString WRITER_KEY = "KTXwriter";
const DOMString WRITER_SCPARAMS_KEY = "KTXwriterScParams";
const unsigned long FACESLICE_WHOLE_lEVEL = UINT_MAX;
const unsigned long ETC1S_DEFAULT_COMPRESSION_LEVEL = 2;

How to use

Put libktx.js and libktx.wasm in a directory on your server. Create a script tag with libktx.js as the src in your .html as shown below, changing the path as necessary for the relative locations of your .html file and the script source. libktx.js will automatically load libktx.wasm.

<script src="libktx.js"></script>
Note
For the read-only version of the library, use libktx_read.js and libktx_read.wasm instead.

Create an instance of the ktx module

To avoid polluting the global window name space all methods, variables and tokens related to libktx are wrapped in a function that returns a promise. The promise is fulfilled with a module instance when it is safe to run the compiled code. To use any of the features your code must call the function, wait for the promise to be fulfulled and use the returned instance. Before calling the function your code must create your WebGL context. The context is needed during module initialization so that the glUpload function can provide WebGLTexture object handles on the same context.

The function is called createKtxModule. In previous releases it was called LIBKTX. It has been renamed to clarify what it is actually doing. Old scripts should be updated to the new name as the old name will be removed soon.

Note
In libktx_read.js the function is called createKtxReadModule.

Add the following to the top of your script to call the function, wait for the instance of the ktx module, make it available in the window name space, make your WebGL context the current context in Emscripten's OpenGL emulation and call your main().

This snippet shows WebGL context creation as well.

const canvas = document.querySelector('#glcanvas');
gl = canvas.getContext('webgl2');
// If we don't have a GL context, give up now
if (!gl) {
alert('Unable to initialize WebGL. Your browser or machine may not support it.');
} else {
createKtxModule({preinitializedWebGLContext: gl}).then(instance => {
window.ktx = instance;
// Make existing WebGL context current for Emscripten OpenGL.
ktx.GL.makeContextCurrent(
ktx.GL.createContext(document.getElementById("glcanvas"),
{ majorVersion: 2.0 })
);
main()
});
}

This calls main() after the module instance has been created. Start the rest of your code there.

Downloading and using an existing KTX texture.

To download an existing texture and create a WebGL texture from it, execute a function like loadTexture in the following:

var myTexture;
main() {
loadTexture(gl, "myTextureUrl");
}
function loadTexture(gl, url)
{
// Create placeholder which will be replaced once the data arrives.
myTexture = createPlaceholderTexture(gl, [0, 0, 255, 255]);
gl.bindTexture(myTexture.target, myTexture.object);
var xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.responseType = "arraybuffer";
xhr.onload = function(){
var ktxdata = new Uint8Array(this.response);
ktexture = new ktx.texture(ktxdata);
const tex = uploadTextureToGl(gl, ktexture);
setTexParameters(tex, ktexture);
gl.bindTexture(tex.target, tex.object);
gl.deleteTexture(texture.object);
texture = tex;
// Use code like this to display the transcode target format.
// elem('format').innerText = tex.format;
ktexture.delete();
};
//xhr.onprogress = runProgress;
//xhr.onloadstart = openProgress;
xhr.send();
}

This is the function for creating the place holder texture.

function createPlaceholderTexture(gl, color)
{
const placeholder = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, placeholder);
const level = 0;
const internalFormat = gl.RGBA;
const width = 1;
const height = 1;
const border = 0;
const srcFormat = gl.RGBA;
const srcType = gl.UNSIGNED_BYTE;
const pixel = new Uint8Array(color);
gl.texImage2D(gl.TEXTURE_2D, level, internalFormat,
width, height, border, srcFormat, srcType,
pixel);
return {
target: gl.TEXTURE_2D,
object: placeholder,
format: formatString,
};
}

Uploading the KTX texture to the WebGL context is done like this. This function returns the created WebGL texture object and matching texture target.

function uploadTextureToGl(gl, ktexture) {
const { transcode_fmt } = ktx;
var formatString;
if (ktexture.needsTranscoding) {
var format;
if (astcSupported) {
formatString = 'ASTC';
format = transcode_fmt.ASTC_4x4_RGBA;
} else if (dxtSupported) {
formatString = ktexture.numComponents == 4 ? 'BC3' : 'BC1';
format = transcode_fmt.BC1_OR_3;
} else if (pvrtcSupported) {
formatString = 'PVRTC1';
format = transcode_fmt.PVRTC1_4_RGBA;
} else if (etcSupported) {
formatString = 'ETC';
format = transcode_fmt.ETC;
} else {
formatString = 'RGBA4444';
format = transcode_fmt.RGBA4444;
}
if (ktexture.transcodeBasis(format, 0) != ktx.error_code.SUCCESS) {
alert('Texture transcode failed. See console for details.');
return undefined;
}
}
const result = ktexture.glUpload();
if (result.error != gl.NO_ERROR) {
alert('WebGL error when uploading texture, code = '
+ result.error.toString(16));
return undefined;
}
if (result.object === undefined) {
alert('Texture upload failed. See console for details.');
return undefined;
}
if (result.target != gl.TEXTURE_2D) {
alert('Loaded texture is not a TEXTURE2D.');
return undefined;
}
return {
target: result.target,
object: result.object,
format: formatString,
}
}

This is the function to correctly set the TexParameters for the loaded texture. It expects that the WebGLTexture object in the texture parameter was created from the content of the ktexture parameter.

function setTexParameters(texture, ktexture) {
gl.bindTexture(texture.target, texture.object);
if (ktexture.numLevels > 1 || ktexture.generateMipmaps) {
// Enable bilinear mipmapping.
gl.texParameteri(texture.target,
gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_NEAREST);
} else {
gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
}
gl.texParameteri(texture.target, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
gl.bindTexture(texture.target, null);
}
Note
It is not clear if glUpload can be used with, e.g. THREE.js. It may be necessary to expose the ktxTexture_IterateLevelFaces or ktxTexture_IterateLoadLevelFaces API to JS with those calling a callback in JS to upload each image to WebGL.

Creating a new KTX texture

This function shows the main steps:

async function runTests(filename) {
const img = await loadImage(filename);
const imageData = await loadImageData(img);
const ktexture = await createTexture(imageData);
}

Step 1 is to fetch the image via code such as this:

async function loadImage(src){
return new Promise((resolve, reject) => {
let img = new Image();
div = items[origImageItem].element;
img.onload = () => { div.appendChild(img); resolve(img); }
img.onerror = reject;
img.src = src;
})
}
}

Step 2 is to get the image data via code such as the following. Note that to get data at the original image size you must use img.naturalWidth and img.naturalHeight as shown here. If you use img.width and img.height the image data will be rendered at whatever size your CSS is displaying the canvas.

async function loadImageData (img, flip = false) {
const canvas = document.createElement("canvas");
const context = canvas.getContext("2d");
const width = img.naturalWidth;
const height = img.naturalHeight;
canvas.width = width;
canvas.height = height;
if (flip) {
context.translate(0, height);
context.scale(1, -1);
}
context.drawImage(img, 0, 0, width, height);
const imageData = context.getImageData(0, 0, width, height);
return imageData;
};

Step 3 is to create the KTX texture object as shonw here:

async function createTexture(imageData) {
const createInfo = new ktx.textureCreateInfo();
const colorSpace = imageData.colorSpace;
createInfo.baseWidth = imageData.width;
createInfo.baseHeight = imageData.height;
createInfo.baseDepth = 1;
createInfo.numDimensions = 2;
createInfo.numLevels = 1;
createInfo.numLayers = 1;
createInfo.numFaces = 1;
createInfo.isArray = false;
createInfo.generateMipmaps = false;
var displayP3;
// Image data from 2d canvases is always 8-bit RGBA.
// The only possible ImageData colorSpace choices are undefined, "srgb"
// and "displayp3." All use the sRGB transfer function.
createInfo.vkFormat = ktx.VkFormat.R8G8B8A8_SRGB;
if ( imageData.colorSpace == "display-p3") {
displayP3 = true;
}
const ktexture = new ktx.texture(createInfo, ktx.CreateStorageEnum.ALLOC_STORAGE);
if (ktexture != null) {
if (displayP3) {
ktexture.primaries = ktx.khr_df_primaries.DISPLAYP3;
}
result = ktexture.setImageFromMemory(0, 0, 0, imageData.data);
}
return ktexture;
}

The texture can now be uploaded to WebGL with uploadTextureToGl, that was listed earlier, and then displayed.

The texture can be compressed to one of the Basis universal formats with code like the following.

async function testEncodeBasis(ktexture) {
const basisu_options = new ktx.basisParams();
basisu_options.uastc = false;
basisu_options.noSSE = true;
basisu_options.verbose = false;
basisu_options.qualityLevel = 200;
basisu_options.compressionLevel = ktx.ETC1S_DEFAULT_COMPRESSION_LEVEL;
var result = ktexture.compressBasis(basisu_options);
// Check result for ktx.error_code.SUCCESS.
}

Finally the texture can be written back to Javascript with this single line of code:

const serializedTexture = ktexture.writeToMemory();

serializedTexture is a TypedArray. The web client can write the data to a local file or upload it to a server.