Skip to content

Repository files navigation

tiny_ktx

Small C based KTX texture loader (inspired by syoyo tiny libraries)

KTX textures can handle

  • Almost any format of texture data
  • 1D, 2D, 3D and cubemaps textures
  • Texture arrays
  • Mipmaps
  • Key value pairs for custom data extensions

Its an efficient open format for almost every realtime texture data you could want.

What it does?

  • Loads Khronos KTX textures
  • Saves Khronos KTX textures
  • Optionally provide format in either native GL/KTX style or Vulkan/Dx12/Metal style
  • Optionally provides GL defines requires to read KTX files without GL

tiny_ktx is a very low level API as such it only handles these parts, it doesn't process the data in any way

Requirements

None except a C compiler (TODO test which version 89 or 99)

By default uses 4 std lib headers

  • stdint.h -for uint32_t and int64_t
  • stdbool.h - for bool
  • stddef.h - for size_t
  • string.h - for memcpy

However if the types/functions are provided you can opt out of these being included via TINYKTX_HAVE_UINTXX_T etc.

How to build

  • include tinyktx.h from include/tiny_ktx in your project
  • in 1 file in your project define TINYKTX_IMPLEMENTATION before tinyktx.h

if using cmake and want a library version just add this using add_subdirectory and add tiny_ktx as a dependency

Handling KTX format

KTX file are based on OpenGL which has evolved a fairly complex pixel format system

Whilst this may be useful if you are importing into a GL application for other its quite hard to convert.

An optional part of TinyKtx (default is on) will convert these into a more Vulkan/Dx12/Metal format.

Rather than the multiple uint32_t types KTX stores format in, TinyKtx provides a single large enum.

TinyKtx_GetFormatGL will give you the format directly as stored in the KTX file

TinyKtx_GetFormat will provide a single value converted from the KTX GL provided type.

if TinyKtx_GetFormat can't convert it will return TKTX_UNDEFINED

How to load a KTX

Create a contex using TinyKtx_CreateContext passing in callbacks for

  • optional error report
  • alloc
  • free
  • read
  • seek
  • tell

All are provides a void* user data argument for file handle etc.

Read the header (TinyKtx_ReadHeader).
A tell/read should be at the start of the KTX data
Query the dimension, format etc. (TinyKtx_Width etc.)
For each mipmap level in the file (TinyKtx_NumberOfMipmaps)
get the mipmap size (TinyKtx_ImageSize)
get the mipmap data (TinyKtx_ImageRawData)

Load snippet

staticvoidtinyktxCallbackError(void*user, charconst*msg) {
LOGERROR("Tiny_Ktx ERROR: %s", msg);
}
staticvoid*tinyktxCallbackAlloc(void*user, size_tsize) {
returnMEMORY_MALLOC(size);
}
staticvoidtinyktxCallbackFree(void*user, void*data) {
MEMORY_FREE(data);
}
staticsize_ttinyktxCallbackRead(void*user, void*data, size_tsize) {
auto handle= (VFile_Handle) user;
returnVFile_Read(handle, data, size);
}
staticbooltinyktxCallbackSeek(void*user, int64_toffset) {
auto handle= (VFile_Handle) user;
returnVFile_Seek(handle, offset, VFile_SD_Begin);
}
staticint64_ttinyktxCallbackTell(void*user) {
auto handle= (VFile_Handle) user;
returnVFile_Tell(handle);
}
AL2O3_EXTERN_CImage_ImageHeaderconst*Image_LoadKTX(VFile_Handlehandle) {
TinyKtx_Callbackscallbacks {
&tinyktxCallbackError,
&tinyktxCallbackAlloc,
&tinyktxCallbackFree,
tinyktxCallbackRead,
&tinyktxCallbackSeek,
&tinyktxCallbackTell
};
auto ctx=TinyKtx_CreateContext( &callbacks, handle);
TinyKtx_ReadHeader(ctx);
uint32_tw=TinyKtx_Width(ctx);
uint32_th=TinyKtx_Height(ctx);
uint32_td=TinyKtx_Depth(ctx);
uint32_ts=TinyKtx_ArraySlices(ctx);
ImageFormatfmt=ImageFormatToTinyKtxFormat(TinyKtx_GetFormat(ctx));
if(fmt==ImageFormat_UNDEFINED) {
TinyKtx_DestroyContext(ctx);
returnnullptr;
}
Image_ImageHeaderconst*topImage=nullptr;
Image_ImageHeaderconst*prevImage=nullptr;
for(auto i=0u; i<TinyKtx_NumberOfMipmaps(ctx);++i) {
auto image=Image_CreateNoClear(w, h, d, s, fmt);
if(i==0) topImage=image;
if(Image_ByteCountOf(image) !=TinyKtx_ImageSize(ctx, i)) {
LOGERROR("KTX file %s mipmap %i size error", VFile_GetName(handle), i);
Image_Destroy(topImage);
TinyKtx_DestroyContext(ctx);
returnnullptr;
}
memcpy(Image_RawDataPtr(image), TinyKtx_ImageRawData(ctx, i), Image_ByteCountOf(image));
if(prevImage) {
auto p= (Image_ImageHeader*)prevImage;
p->nextType=Image_NextType::Image_IT_MipMaps;
p->nextImage=image;
}
if(w>1) w=w / 2;
if(h>1) h=h / 2;
if(d>1) d=d / 2;
prevImage=image;
}
TinyKtx_DestroyContext(ctx);
returntopImage;
}

How to save a KTX

Saving doesn't need a context just a TinyKtx_WriteCallbacks with

  • error reporting
  • alloc (not currently used)
  • free (not currently used)
  • write
A TinyKtx_WriteImage or TinyKtx_WriteImageGL are the only API entry point for saving a KTX file.
Provide it with the format (in either style), dimensions and whether its a cube map or not
Pass the number of mipmaps and arrays filled with the size of each mipmap image and a pointer to the data

Save snippet

staticvoidtinyktxCallbackError(void*user, charconst*msg) {
LOGERROR("Tiny_Ktx ERROR: %s", msg);
}
staticvoid*tinyktxCallbackAlloc(void*user, size_tsize) {
returnMEMORY_MALLOC(size);
}
staticvoidtinyktxCallbackFree(void*user, void*data) {
MEMORY_FREE(data);
}
staticvoidtinyktxCallbackWrite(void*user, voidconst*data, size_tsize) {
auto handle= (VFile_Handle) user;
VFile_Write(handle, data, size);
}
AL2O3_EXTERN_CboolImage_SaveKTX(Image_ImageHeader*image, VFile_Handlehandle) {
usingnamespaceImage;
TinyKtx_WriteCallbackscallback{
&tinyktxCallbackError,
&tinyktxCallbackAlloc,
&tinyktxCallbackFree,
&tinyktxCallbackWrite,
};
TinyKtx_Formatfmt=ImageFormatToTinyKtxFormat(image->format);
if(fmt==TKTX_UNDEFINED) return false;
uint32_tnumMipmaps= (image->nextType==Image_NextType::Image_IT_None) ? 1 : (uint32_t)Image_LinkedImageCountOf(image);
uint32_tmipmapsizes[TINYKTX_MAX_MIPMAPLEVELS];
voidconst*mipmaps[TINYKTX_MAX_MIPMAPLEVELS];
memset(mipmapsizes, 0, sizeof(uint32_t)*TINYKTX_MAX_MIPMAPLEVELS);
memset(mipmaps, 0, sizeof(voidconst*)*TINYKTX_MAX_MIPMAPLEVELS);
for(size_ti=0; i<numMipmaps; ++i) {
mipmapsizes[i] = (uint32_t) Image_LinkedImageOf(image, i)->dataSize;
mipmaps[i] =Image_RawDataPtr(Image_LinkedImageOf(image, i));
}
returnTinyKtx_WriteImage(&callback,
handle,
image->width,
image->height,
image->depth,
image->slices,
numMipmaps,
fmt,
Image_IsCubemap(image),
mipmapsizes,
mipmaps );
}

Tests

Testing is done using my Taylor scriptable content processor.

taylor_imagetest - script and data

taylor - app that runs the test script)

TODO

Lots of validation/tests

Save key data pairs (currently will load them but doesn't write any)

Handle endianness?

Higher level

tiny_ktx is the lowel level part of my gfx_imageio and gfx_image libraries.

They handle conversion or data, reading and writing and load/save other format ast well

taylor is a lua scripted content command line program that uses the above library for processing.

If you want higher level of tiny_ktx or how tiny_ktx is used see the links below

The snippets above are from gfx_imageio

gfx_imageio - higher level import/export image using tiny_ktx (and other formats)

taylor - lua scripted image processer using gfx_imageio

About

Small C based KTX texture loader (inspired by syoyo tiny libraries)

Resources

Stars

97 stars

Watchers

2 watching

Forks

Releases

Packages

Used by

Contributors

Languages