// MIT license see full LICENSE text at end of file
#pragma once
#ifndef TINY_DDS_TINYDDS_H
#define TINY_DDS_TINYDDS_H

#ifndef TINYDDS_HAVE_UINTXX_T
#include <stdint.h>  // for uint32_t and int64_t
#endif
#ifndef TINYDDS_HAVE_BOOL
#include <stdbool.h>  // for bool
#endif
#ifndef TINYDDS_HAVE_SIZE_T
#include <stddef.h>    // for size_t
#endif
#ifndef TINYDDS_HAVE_MEMCPY
#include <string.h>  // for memcpy
#endif

#ifdef __cplusplus
extern "C" {
#endif

#define TINYDDS_MAX_MIPMAPLEVELS 16

typedef struct TinyDDS_Context *TinyDDS_ContextHandle;

typedef void *(*TinyDDS_AllocFunc)(void *user, size_t size);
typedef void (*TinyDDS_FreeFunc)(void *user, void *memory);
typedef size_t (*TinyDDS_ReadFunc)(void *user, void *buffer, size_t byteCount);
typedef bool (*TinyDDS_SeekFunc)(void *user, int64_t offset);
typedef int64_t (*TinyDDS_TellFunc)(void *user);
typedef void (*TinyDDS_ErrorFunc)(void *user, char const *msg);

typedef struct TinyDDS_Callbacks {
	TinyDDS_ErrorFunc errorFn;
	TinyDDS_AllocFunc allocFn;
	TinyDDS_FreeFunc freeFn;
	TinyDDS_ReadFunc readFn;
	TinyDDS_SeekFunc seekFn;
	TinyDDS_TellFunc tellFn;
} TinyDDS_Callbacks;

TinyDDS_ContextHandle TinyDDS_CreateContext(TinyDDS_Callbacks const *callbacks, void *user);
void TinyDDS_DestroyContext(TinyDDS_ContextHandle handle);

// reset lets you reuse the context for another file (saves an alloc/free cycle)
void TinyDDS_Reset(TinyDDS_ContextHandle handle);

// call this to read the header file should already be at the start of the KTX data
bool TinyDDS_ReadHeader(TinyDDS_ContextHandle handle);

bool TinyDDS_Is1D(TinyDDS_ContextHandle handle);
bool TinyDDS_Is2D(TinyDDS_ContextHandle handle);
bool TinyDDS_Is3D(TinyDDS_ContextHandle handle);
bool TinyDDS_IsCubemap(TinyDDS_ContextHandle handle);
bool TinyDDS_IsArray(TinyDDS_ContextHandle handle);

bool TinyDDS_Dimensions(TinyDDS_ContextHandle handle,
												uint32_t *width,
												uint32_t *height,
												uint32_t *depth,
												uint32_t *slices);
uint32_t TinyDDS_Width(TinyDDS_ContextHandle handle);
uint32_t TinyDDS_Height(TinyDDS_ContextHandle handle);
uint32_t TinyDDS_Depth(TinyDDS_ContextHandle handle);
uint32_t TinyDDS_ArraySlices(TinyDDS_ContextHandle handle);

bool TinyDDS_NeedsGenerationOfMipmaps(TinyDDS_ContextHandle handle);
bool TinyDDS_NeedsEndianCorrecting(TinyDDS_ContextHandle handle);

uint32_t TinyDDS_NumberOfMipmaps(TinyDDS_ContextHandle handle);
uint32_t TinyDDS_ImageSize(TinyDDS_ContextHandle handle, uint32_t mipmaplevel);

// data return by ImageRawData is owned by the context. Don't free it!
void const *TinyDDS_ImageRawData(TinyDDS_ContextHandle handle, uint32_t mipmaplevel);

typedef void (*TinyDDS_WriteFunc)(void *user, void const *buffer, size_t byteCount);

typedef struct TinyDDS_WriteCallbacks {
	TinyDDS_ErrorFunc error;
	TinyDDS_AllocFunc alloc;
	TinyDDS_FreeFunc free;
	TinyDDS_WriteFunc write;
} TinyDDS_WriteCallbacks;

#ifndef TINYIMAGEFORMAT_DXGIFORMAT
#define TINYIMAGEFORMAT_DXGIFORMAT

// early DDS was a direct copy of the Draw Draw surface bits, later on (Dx10) it moved to
// DXGI_FORMAT we use a similar thing to DXGI_FORMAT second form but will synthesis
// the old style when required when saving and vice versa when loading.
typedef enum TinyImageFormat_DXGI_FORMAT {
	TIF_DXGI_FORMAT_UNKNOWN = 0,
	TIF_DXGI_FORMAT_R32G32B32A32_TYPELESS = 1,
	TIF_DXGI_FORMAT_R32G32B32A32_FLOAT = 2,
	TIF_DXGI_FORMAT_R32G32B32A32_UINT = 3,
	TIF_DXGI_FORMAT_R32G32B32A32_SINT = 4,
	TIF_DXGI_FORMAT_R32G32B32_TYPELESS = 5,
	TIF_DXGI_FORMAT_R32G32B32_FLOAT = 6,
	TIF_DXGI_FORMAT_R32G32B32_UINT = 7,
	TIF_DXGI_FORMAT_R32G32B32_SINT = 8,
	TIF_DXGI_FORMAT_R16G16B16A16_TYPELESS = 9,
	TIF_DXGI_FORMAT_R16G16B16A16_FLOAT  = 10,
	TIF_DXGI_FORMAT_R16G16B16A16_UNORM = 11,
	TIF_DXGI_FORMAT_R16G16B16A16_UINT = 12,
	TIF_DXGI_FORMAT_R16G16B16A16_SNORM = 13,
	TIF_DXGI_FORMAT_R16G16B16A16_SINT = 14,
	TIF_DXGI_FORMAT_R32G32_TYPELESS = 15,
	TIF_DXGI_FORMAT_R32G32_FLOAT = 16,
	TIF_DXGI_FORMAT_R32G32_UINT = 17,
	TIF_DXGI_FORMAT_R32G32_SINT = 18,
	TIF_DXGI_FORMAT_R32G8X24_TYPELESS = 19,
	TIF_DXGI_FORMAT_D32_FLOAT_S8X24_UINT = 20,
	TIF_DXGI_FORMAT_R32_FLOAT_X8X24_TYPELESS = 21,
	TIF_DXGI_FORMAT_X32_TYPELESS_G8X24_UINT = 22,
	TIF_DXGI_FORMAT_R10G10B10A2_TYPELESS = 23,
	TIF_DXGI_FORMAT_R10G10B10A2_UNORM = 24,
	TIF_DXGI_FORMAT_R10G10B10A2_UINT = 25,
	TIF_DXGI_FORMAT_R11G11B10_FLOAT = 26,
	TIF_DXGI_FORMAT_R8G8B8A8_TYPELESS = 27,
	TIF_DXGI_FORMAT_R8G8B8A8_UNORM = 28,
	TIF_DXGI_FORMAT_R8G8B8A8_UNORM_SRGB = 29,
	TIF_DXGI_FORMAT_R8G8B8A8_UINT = 30,
	TIF_DXGI_FORMAT_R8G8B8A8_SNORM = 31,
	TIF_DXGI_FORMAT_R8G8B8A8_SINT = 32,
	TIF_DXGI_FORMAT_R16G16_TYPELESS = 33,
	TIF_DXGI_FORMAT_R16G16_FLOAT = 34,
	TIF_DXGI_FORMAT_R16G16_UNORM = 35,
	TIF_DXGI_FORMAT_R16G16_UINT = 36,
	TIF_DXGI_FORMAT_R16G16_SNORM = 37,
	TIF_DXGI_FORMAT_R16G16_SINT = 38,
	TIF_DXGI_FORMAT_R32_TYPELESS = 39,
	TIF_DXGI_FORMAT_D32_FLOAT = 40,
	TIF_DXGI_FORMAT_R32_FLOAT = 41,
	TIF_DXGI_FORMAT_R32_UINT = 42,
	TIF_DXGI_FORMAT_R32_SINT = 43,
	TIF_DXGI_FORMAT_R24G8_TYPELESS = 44,
	TIF_DXGI_FORMAT_D24_UNORM_S8_UINT = 45,
	TIF_DXGI_FORMAT_R24_UNORM_X8_TYPELESS = 46,
	TIF_DXGI_FORMAT_X24_TYPELESS_G8_UINT = 47,
	TIF_DXGI_FORMAT_R8G8_TYPELESS = 48,
	TIF_DXGI_FORMAT_R8G8_UNORM = 49,
	TIF_DXGI_FORMAT_R8G8_UINT = 50,
	TIF_DXGI_FORMAT_R8G8_SNORM = 51,
	TIF_DXGI_FORMAT_R8G8_SINT = 52,
	TIF_DXGI_FORMAT_R16_TYPELESS = 53,
	TIF_DXGI_FORMAT_R16_FLOAT = 54,
	TIF_DXGI_FORMAT_D16_UNORM = 55,
	TIF_DXGI_FORMAT_R16_UNORM = 56,
	TIF_DXGI_FORMAT_R16_UINT = 57,
	TIF_DXGI_FORMAT_R16_SNORM = 58,
	TIF_DXGI_FORMAT_R16_SINT = 59,
	TIF_DXGI_FORMAT_R8_TYPELESS = 60,
	TIF_DXGI_FORMAT_R8_UNORM = 61,
	TIF_DXGI_FORMAT_R8_UINT = 62,
	TIF_DXGI_FORMAT_R8_SNORM = 63,
	TIF_DXGI_FORMAT_R8_SINT = 64,
	TIF_DXGI_FORMAT_A8_UNORM = 65,
	TIF_DXGI_FORMAT_R1_UNORM = 66,
	TIF_DXGI_FORMAT_R9G9B9E5_SHAREDEXP = 67,
	TIF_DXGI_FORMAT_R8G8_B8G8_UNORM = 68,
	TIF_DXGI_FORMAT_G8R8_G8B8_UNORM = 69,
	TIF_DXGI_FORMAT_BC1_TYPELESS = 70,
	TIF_DXGI_FORMAT_BC1_UNORM = 71,
	TIF_DXGI_FORMAT_BC1_UNORM_SRGB = 72,
	TIF_DXGI_FORMAT_BC2_TYPELESS = 73,
	TIF_DXGI_FORMAT_BC2_UNORM = 74,
	TIF_DXGI_FORMAT_BC2_UNORM_SRGB = 75,
	TIF_DXGI_FORMAT_BC3_TYPELESS = 76,
	TIF_DXGI_FORMAT_BC3_UNORM = 77,
	TIF_DXGI_FORMAT_BC3_UNORM_SRGB = 78,
	TIF_DXGI_FORMAT_BC4_TYPELESS = 79,
	TIF_DXGI_FORMAT_BC4_UNORM = 80,
	TIF_DXGI_FORMAT_BC4_SNORM = 81,
	TIF_DXGI_FORMAT_BC5_TYPELESS = 82,
	TIF_DXGI_FORMAT_BC5_UNORM = 83,
	TIF_DXGI_FORMAT_BC5_SNORM = 84,
	TIF_DXGI_FORMAT_B5G6R5_UNORM = 85,
	TIF_DXGI_FORMAT_B5G5R5A1_UNORM = 86,
	TIF_DXGI_FORMAT_B8G8R8A8_UNORM = 87,
	TIF_DXGI_FORMAT_B8G8R8X8_UNORM = 88,
	TIF_DXGI_FORMAT_R10G10B10_XR_BIAS_A2_UNORM = 89,
	TIF_DXGI_FORMAT_B8G8R8A8_TYPELESS = 90,
	TIF_DXGI_FORMAT_B8G8R8A8_UNORM_SRGB = 91,
	TIF_DXGI_FORMAT_B8G8R8X8_TYPELESS = 92,
	TIF_DXGI_FORMAT_B8G8R8X8_UNORM_SRGB = 93,
	TIF_DXGI_FORMAT_BC6H_TYPELESS = 94,
	TIF_DXGI_FORMAT_BC6H_UF16 = 95,
	TIF_DXGI_FORMAT_BC6H_SF16 = 96,
	TIF_DXGI_FORMAT_BC7_TYPELESS = 97,
	TIF_DXGI_FORMAT_BC7_UNORM = 98,
	TIF_DXGI_FORMAT_BC7_UNORM_SRGB = 99,
	TIF_DXGI_FORMAT_AYUV = 100,
	TIF_DXGI_FORMAT_Y410 = 101,
	TIF_DXGI_FORMAT_Y416 = 102,
	TIF_DXGI_FORMAT_NV12 = 103,
	TIF_DXGI_FORMAT_P010 = 104,
	TIF_DXGI_FORMAT_P016 = 105,
	TIF_DXGI_FORMAT_420_OPAQUE = 106,
	TIF_DXGI_FORMAT_YUY2 = 107,
	TIF_DXGI_FORMAT_Y210 = 108,
	TIF_DXGI_FORMAT_Y216 = 109,
	TIF_DXGI_FORMAT_NV11 = 110,
	TIF_DXGI_FORMAT_AI44 = 111,
	TIF_DXGI_FORMAT_IA44 = 112,
	TIF_DXGI_FORMAT_P8 = 113,
	TIF_DXGI_FORMAT_A8P8 = 114,
	TIF_DXGI_FORMAT_B4G4R4A4_UNORM = 115,

	// xbox 360 formats
	TIF_DXGI_FORMAT_R10G10B10_7E3_A2_FLOAT = 116,
	TIF_DXGI_FORMAT_R10G10B10_6E4_A2_FLOAT = 117,
	TIF_DXGI_FORMAT_D16_UNORM_S8_UINT = 118,
	TIF_DXGI_FORMAT_R16_UNORM_X8_TYPELESS = 119,
	TIF_DXGI_FORMAT_X16_TYPELESS_G8_UINT = 120,

	TIF_DXGI_FORMAT_P208 = 130,
	TIF_DXGI_FORMAT_V208 = 131,
	TIF_DXGI_FORMAT_V408 = 132,

	// XBox One formats
	TIF_DXGI_FORMAT_R10G10B10_SNORM_A2_UNORM = 189,
	TIF_DXGI_FORMAT_R4G4_UNORM = 190,

} TinyImageFormat_DXGI_FORMAT;
#endif

typedef enum TinyDDS_Format {
	TDDS_UNDEFINED = TIF_DXGI_FORMAT_UNKNOWN,
	TDDS_B5G6R5_UNORM = TIF_DXGI_FORMAT_B5G6R5_UNORM,
	TDDS_B5G5R5A1_UNORM = TIF_DXGI_FORMAT_B5G5R5A1_UNORM,
	TDDS_R8_UNORM = TIF_DXGI_FORMAT_R8_UNORM,
	TDDS_R8_SNORM = TIF_DXGI_FORMAT_R8_SNORM,
	TDDS_A8_UNORM = TIF_DXGI_FORMAT_A8_UNORM,
	TDDS_R1_UNORM = TIF_DXGI_FORMAT_R1_UNORM,
	TDDS_R8_UINT = TIF_DXGI_FORMAT_R8_UINT,
	TDDS_R8_SINT = TIF_DXGI_FORMAT_R8_SINT,
	TDDS_R8G8_UNORM = TIF_DXGI_FORMAT_R8G8_UNORM,
	TDDS_R8G8_SNORM = TIF_DXGI_FORMAT_R8G8_SNORM,
	TDDS_R8G8_UINT = TIF_DXGI_FORMAT_R8G8_UINT,
	TDDS_R8G8_SINT = TIF_DXGI_FORMAT_R8G8_SINT,
	TDDS_R8G8B8A8_UNORM = TIF_DXGI_FORMAT_R8G8B8A8_UNORM,
	TDDS_R8G8B8A8_SNORM = TIF_DXGI_FORMAT_R8G8B8A8_SNORM,
	TDDS_R8G8B8A8_UINT = TIF_DXGI_FORMAT_R8G8B8A8_UINT,
	TDDS_R8G8B8A8_SINT = TIF_DXGI_FORMAT_R8G8B8A8_SINT,
	TDDS_R8G8B8A8_SRGB = TIF_DXGI_FORMAT_R8G8B8A8_UNORM_SRGB,
	TDDS_B8G8R8A8_UNORM = TIF_DXGI_FORMAT_B8G8R8A8_UNORM,
	TDDS_B8G8R8A8_SRGB = TIF_DXGI_FORMAT_B8G8R8A8_UNORM_SRGB,

	TDDS_R9G9B9E5_UFLOAT = TIF_DXGI_FORMAT_R9G9B9E5_SHAREDEXP,
	TDDS_R10G10B10A2_UNORM = TIF_DXGI_FORMAT_R10G10B10A2_UNORM,
	TDDS_R10G10B10A2_UINT = TIF_DXGI_FORMAT_R10G10B10A2_UINT,
	TDDS_R11G11B10_UFLOAT = TIF_DXGI_FORMAT_R11G11B10_FLOAT,

	TDDS_R16_UNORM = TIF_DXGI_FORMAT_R16_UNORM,
	TDDS_R16_SNORM = TIF_DXGI_FORMAT_R16_SNORM,
	TDDS_R16_UINT = TIF_DXGI_FORMAT_R16_UINT,
	TDDS_R16_SINT = TIF_DXGI_FORMAT_R16_SINT,
	TDDS_R16_SFLOAT = TIF_DXGI_FORMAT_R16_FLOAT,

	TDDS_R16G16_UNORM = TIF_DXGI_FORMAT_R16G16_UNORM,
	TDDS_R16G16_SNORM = TIF_DXGI_FORMAT_R16G16_SNORM,
	TDDS_R16G16_UINT = TIF_DXGI_FORMAT_R16G16_UINT,
	TDDS_R16G16_SINT = TIF_DXGI_FORMAT_R16G16_SINT,
	TDDS_R16G16_SFLOAT = TIF_DXGI_FORMAT_R16G16_FLOAT,

	TDDS_R16G16B16A16_UNORM = TIF_DXGI_FORMAT_R16G16B16A16_UNORM,
	TDDS_R16G16B16A16_SNORM = TIF_DXGI_FORMAT_R16G16B16A16_SNORM,
	TDDS_R16G16B16A16_UINT = TIF_DXGI_FORMAT_R16G16B16A16_UINT,
	TDDS_R16G16B16A16_SINT = TIF_DXGI_FORMAT_R16G16B16A16_SINT,
	TDDS_R16G16B16A16_SFLOAT = TIF_DXGI_FORMAT_R16G16B16A16_FLOAT,

	TDDS_R32_UINT = TIF_DXGI_FORMAT_R32_UINT,
	TDDS_R32_SINT = TIF_DXGI_FORMAT_R32_SINT,
	TDDS_R32_SFLOAT = TIF_DXGI_FORMAT_R32_FLOAT,

	TDDS_R32G32_UINT = TIF_DXGI_FORMAT_R32G32_UINT,
	TDDS_R32G32_SINT = TIF_DXGI_FORMAT_R32G32_SINT,
	TDDS_R32G32_SFLOAT = TIF_DXGI_FORMAT_R32G32_FLOAT,

	TDDS_R32G32B32_UINT = TIF_DXGI_FORMAT_R32G32B32_UINT,
	TDDS_R32G32B32_SINT = TIF_DXGI_FORMAT_R32G32B32_SINT,
	TDDS_R32G32B32_SFLOAT = TIF_DXGI_FORMAT_R32G32B32_FLOAT,

	TDDS_R32G32B32A32_UINT = TIF_DXGI_FORMAT_R32G32B32A32_UINT,
	TDDS_R32G32B32A32_SINT = TIF_DXGI_FORMAT_R32G32B32A32_SINT,
	TDDS_R32G32B32A32_SFLOAT = TIF_DXGI_FORMAT_R32G32B32A32_FLOAT,

	TDDS_BC1_RGBA_UNORM_BLOCK = TIF_DXGI_FORMAT_BC1_UNORM,
	TDDS_BC1_RGBA_SRGB_BLOCK = TIF_DXGI_FORMAT_BC1_UNORM_SRGB,
	TDDS_BC2_UNORM_BLOCK = TIF_DXGI_FORMAT_BC2_UNORM,
	TDDS_BC2_SRGB_BLOCK = TIF_DXGI_FORMAT_BC2_UNORM_SRGB,
	TDDS_BC3_UNORM_BLOCK = TIF_DXGI_FORMAT_BC3_UNORM,
	TDDS_BC3_SRGB_BLOCK = TIF_DXGI_FORMAT_BC3_UNORM_SRGB,
	TDDS_BC4_UNORM_BLOCK = TIF_DXGI_FORMAT_BC4_UNORM,
	TDDS_BC4_SNORM_BLOCK = TIF_DXGI_FORMAT_BC4_SNORM,
	TDDS_BC5_UNORM_BLOCK = TIF_DXGI_FORMAT_BC5_UNORM,
	TDDS_BC5_SNORM_BLOCK = TIF_DXGI_FORMAT_BC5_SNORM,

	TDDS_BC6H_UFLOAT_BLOCK = TIF_DXGI_FORMAT_BC6H_UF16,
	TDDS_BC6H_SFLOAT_BLOCK = TIF_DXGI_FORMAT_BC6H_SF16,
	TDDS_BC7_UNORM_BLOCK = TIF_DXGI_FORMAT_BC7_UNORM,
	TDDS_BC7_SRGB_BLOCK = TIF_DXGI_FORMAT_BC7_UNORM_SRGB,

	TDDS_AYUV = TIF_DXGI_FORMAT_AYUV,
	TDDS_Y410 = TIF_DXGI_FORMAT_Y410,
	TDDS_Y416 = TIF_DXGI_FORMAT_Y416,
	TDDS_NV12 = TIF_DXGI_FORMAT_NV12,
	TDDS_P010 = TIF_DXGI_FORMAT_P010,
	TDDS_P016 = TIF_DXGI_FORMAT_P016,
	TDDS_420_OPAQUE = TIF_DXGI_FORMAT_420_OPAQUE,
	TDDS_YUY2 = TIF_DXGI_FORMAT_YUY2,
	TDDS_Y210 = TIF_DXGI_FORMAT_Y210,
	TDDS_Y216 = TIF_DXGI_FORMAT_Y216,
	TDDS_NV11 = TIF_DXGI_FORMAT_NV11,
	TDDS_AI44 = TIF_DXGI_FORMAT_AI44,
	TDDS_IA44 = TIF_DXGI_FORMAT_IA44,
	TDDS_P8 = TIF_DXGI_FORMAT_P8,
	TDDS_A8P8 = TIF_DXGI_FORMAT_A8P8,
	TDDS_B4G4R4A4_UNORM = TIF_DXGI_FORMAT_B4G4R4A4_UNORM,
	TDDS_R10G10B10_7E3_A2_FLOAT = TIF_DXGI_FORMAT_R10G10B10_7E3_A2_FLOAT,
	TDDS_R10G10B10_6E4_A2_FLOAT = TIF_DXGI_FORMAT_R10G10B10_6E4_A2_FLOAT,
	TDDS_D16_UNORM_S8_UINT = TIF_DXGI_FORMAT_D16_UNORM_S8_UINT,
	TDDS_R16_UNORM_X8_TYPELESS = TIF_DXGI_FORMAT_R16_UNORM_X8_TYPELESS,
	TDDS_X16_TYPELESS_G8_UINT = TIF_DXGI_FORMAT_X16_TYPELESS_G8_UINT,
	TDDS_P208 = TIF_DXGI_FORMAT_P208,
	TDDS_V208 = TIF_DXGI_FORMAT_V208,
	TDDS_V408 = TIF_DXGI_FORMAT_V408,
	TDDS_R10G10B10_SNORM_A2_UNORM = TIF_DXGI_FORMAT_R10G10B10_SNORM_A2_UNORM,
	TDDS_R4G4_UNORM = TIF_DXGI_FORMAT_R4G4_UNORM,

	TDDS_SYNTHESISED_DXGIFORMATS = 0xFFFF,
	TDDS_G4R4_UNORM = TDDS_SYNTHESISED_DXGIFORMATS,

	TDDS_A4B4G4R4_UNORM,
	TDDS_X4B4G4R4_UNORM,

	TDDS_A4R4G4B4_UNORM,
	TDDS_X4R4G4B4_UNORM,

	TDDS_B4G4R4X4_UNORM,

	TDDS_R4G4B4A4_UNORM,
	TDDS_R4G4B4X4_UNORM,

	TDDS_B5G5R5X1_UNORM,

	TDDS_R5G5B5A1_UNORM,
	TDDS_R5G5B5X1_UNORM,

	TDDS_A1R5G5B5_UNORM,
	TDDS_X1R5G5B5_UNORM,

	TDDS_A1B5G5R5_UNORM,
	TDDS_X1B5G5R5_UNORM,

	TDDS_R5G6B5_UNORM,

	TDDS_B2G3R3_UNORM,
	TDDS_B2G3R3A8_UNORM,

	TDDS_G8R8_UNORM,
	TDDS_G8R8_SNORM,

	TDDS_R8G8B8_UNORM,
	TDDS_B8G8R8_UNORM,

	TDDS_A8B8G8R8_SNORM,
	TDDS_B8G8R8A8_SNORM,

	TDDS_R8G8B8X8_UNORM,
	TDDS_B8G8R8X8_UNORM,
	TDDS_A8B8G8R8_UNORM,
	TDDS_X8B8G8R8_UNORM,
	TDDS_A8R8G8B8_UNORM,
	TDDS_X8R8G8B8_UNORM,

	TDDS_R10G10B10A2_SNORM,
	TDDS_B10G10R10A2_UNORM,
	TDDS_B10G10R10A2_SNORM,
	TDDS_A2B10G10R10_UNORM,
	TDDS_A2B10G10R10_SNORM,
	TDDS_A2R10G10B10_UNORM,
	TDDS_A2R10G10B10_SNORM,

	TDDS_G16R16_UNORM,
	TDDS_G16R16_SNORM,

} TinyDDS_Format;

// tiny_imageformat/format needs included before tinydds.h for this functionality
#ifdef TINYIMAGEFORMAT_BASE_H_

static TinyImageFormat TinyImageFormat_FromTinyDDSFormat(TinyDDS_Format fmt) {
	switch (fmt) {
	case TDDS_UNDEFINED: return TinyImageFormat_UNDEFINED;

	case TDDS_R32G32B32A32_SFLOAT: return TinyImageFormat_R32G32B32A32_SFLOAT;
	case TDDS_R32G32B32A32_UINT: return TinyImageFormat_R32G32B32A32_UINT;
	case TDDS_R32G32B32A32_SINT: return TinyImageFormat_R32G32B32A32_SINT;
	case TDDS_R32G32B32_SFLOAT: return TinyImageFormat_R32G32B32_SFLOAT;
	case TDDS_R32G32B32_UINT: return TinyImageFormat_R32G32B32_UINT;
	case TDDS_R32G32B32_SINT: return TinyImageFormat_R32G32B32_SINT;
	case TDDS_R16G16B16A16_SFLOAT: return TinyImageFormat_R16G16B16A16_SFLOAT;
	case TDDS_R16G16B16A16_UNORM: return TinyImageFormat_R16G16B16A16_UNORM;
	case TDDS_R16G16B16A16_UINT: return TinyImageFormat_R16G16B16A16_UINT;
	case TDDS_R16G16B16A16_SNORM: return TinyImageFormat_R16G16B16A16_SNORM;
	case TDDS_R16G16B16A16_SINT: return TinyImageFormat_R16G16B16A16_SINT;
	case TDDS_R32G32_SFLOAT: return TinyImageFormat_R32G32_SFLOAT;
	case TDDS_R32G32_UINT: return TinyImageFormat_R32G32_UINT;
	case TDDS_R32G32_SINT: return TinyImageFormat_R32G32_SINT;
	case TDDS_R8G8B8A8_UNORM: return TinyImageFormat_R8G8B8A8_UNORM;
	case TDDS_R8G8B8A8_SRGB: return TinyImageFormat_R8G8B8A8_SRGB;
	case TDDS_R8G8B8A8_UINT: return TinyImageFormat_R8G8B8A8_UINT;
	case TDDS_R8G8B8A8_SNORM: return TinyImageFormat_R8G8B8A8_SNORM;
	case TDDS_R8G8B8A8_SINT: return TinyImageFormat_R8G8B8A8_SINT;
	case TDDS_R16G16_SFLOAT: return TinyImageFormat_R16G16_SFLOAT;
	case TDDS_R16G16_UNORM: return TinyImageFormat_R16G16_UNORM;
	case TDDS_R16G16_UINT: return TinyImageFormat_R16G16_UINT;
	case TDDS_R16G16_SNORM: return TinyImageFormat_R16G16_SNORM;
	case TDDS_R16G16_SINT: return TinyImageFormat_R16G16_SINT;
	case TDDS_R32_SFLOAT: return TinyImageFormat_R32_SFLOAT;
	case TDDS_R32_UINT: return TinyImageFormat_R32_UINT;
	case TDDS_R32_SINT: return TinyImageFormat_R32_SINT;

	case TDDS_R8G8_UNORM: return TinyImageFormat_R8G8_UNORM;
	case TDDS_R8G8_UINT: return TinyImageFormat_R8G8_UINT;
	case TDDS_R8G8_SNORM: return TinyImageFormat_R8G8_SNORM;
	case TDDS_R8G8_SINT: return TinyImageFormat_R8G8_SINT;
	case TDDS_G8R8_UNORM: return TinyImageFormat_G8R8_UNORM;
	case TDDS_G8R8_SNORM: return TinyImageFormat_G8R8_SNORM;

	case TDDS_R16_SFLOAT: return TinyImageFormat_R16_SFLOAT;
	case TDDS_R16_UNORM: return TinyImageFormat_R16_UNORM;
	case TDDS_R16_UINT: return TinyImageFormat_R16_UINT;
	case TDDS_R16_SNORM: return TinyImageFormat_R16_SNORM;
	case TDDS_R16_SINT: return TinyImageFormat_R16_SINT;
	case TDDS_R8_UNORM: return TinyImageFormat_R8_UNORM;
	case TDDS_R8_UINT: return TinyImageFormat_R8_UINT;
	case TDDS_R8_SNORM: return TinyImageFormat_R8_SNORM;
	case TDDS_R8_SINT: return TinyImageFormat_R8_SINT;
	case TDDS_A8_UNORM: return TinyImageFormat_A8_UNORM;
	case TDDS_BC1_RGBA_UNORM_BLOCK: return TinyImageFormat_DXBC1_RGBA_UNORM;
	case TDDS_BC1_RGBA_SRGB_BLOCK:			return TinyImageFormat_DXBC1_RGBA_SRGB;
	case TDDS_BC2_UNORM_BLOCK: return TinyImageFormat_DXBC2_UNORM;
	case TDDS_BC2_SRGB_BLOCK: return TinyImageFormat_DXBC2_SRGB;
	case TDDS_BC3_UNORM_BLOCK: return TinyImageFormat_DXBC3_UNORM;
	case TDDS_BC3_SRGB_BLOCK: return TinyImageFormat_DXBC3_SRGB;
	case TDDS_BC4_UNORM_BLOCK: return TinyImageFormat_DXBC4_UNORM;
	case TDDS_BC4_SNORM_BLOCK: return TinyImageFormat_DXBC4_SNORM;
	case TDDS_BC5_UNORM_BLOCK: return TinyImageFormat_DXBC5_UNORM;
	case TDDS_BC5_SNORM_BLOCK: return TinyImageFormat_DXBC5_SNORM;
	case TDDS_BC6H_UFLOAT_BLOCK: return TinyImageFormat_DXBC6H_UFLOAT;
	case TDDS_BC6H_SFLOAT_BLOCK: return TinyImageFormat_DXBC6H_SFLOAT;
	case TDDS_BC7_UNORM_BLOCK: return TinyImageFormat_DXBC7_UNORM;
	case TDDS_BC7_SRGB_BLOCK: return TinyImageFormat_DXBC7_SRGB;
	case TDDS_B8G8R8A8_UNORM: return TinyImageFormat_B8G8R8A8_UNORM;
	case TDDS_B8G8R8A8_SRGB: return TinyImageFormat_B8G8R8A8_SRGB;

	case TDDS_B2G3R3A8_UNORM: return TinyImageFormat_B2G3R3A8_UNORM;
	case TDDS_B2G3R3_UNORM: return TinyImageFormat_B2G3R3_UNORM;
	case TDDS_R4G4_UNORM: return TinyImageFormat_R4G4_UNORM;

	case TDDS_R8G8B8_UNORM: return TinyImageFormat_R8G8B8_UNORM;
	case TDDS_B8G8R8_UNORM:	return TinyImageFormat_B8G8R8_UNORM;
	case TDDS_B8G8R8A8_SNORM:					return TinyImageFormat_B8G8R8A8_SNORM;

	case TDDS_R9G9B9E5_UFLOAT: return TinyImageFormat_E5B9G9R9_UFLOAT;
	case TDDS_R11G11B10_UFLOAT: return TinyImageFormat_B10G11R11_UFLOAT;
	case TDDS_G4R4_UNORM: return TinyImageFormat_G4R4_UNORM;

	case TDDS_R5G6B5_UNORM: return TinyImageFormat_R5G6B5_UNORM;
	case TDDS_B5G6R5_UNORM: return TinyImageFormat_B5G6R5_UNORM;

	case TDDS_B5G5R5A1_UNORM: return TinyImageFormat_B5G5R5A1_UNORM;
	case TDDS_B5G5R5X1_UNORM: return TinyImageFormat_B5G5R5X1_UNORM;

	case TDDS_R5G5B5A1_UNORM: return TinyImageFormat_R5G5B5A1_UNORM;
	case TDDS_R5G5B5X1_UNORM: return TinyImageFormat_R5G5B5X1_UNORM;

	case TDDS_A1R5G5B5_UNORM: return TinyImageFormat_A1R5G5B5_UNORM;
	case TDDS_X1R5G5B5_UNORM: return TinyImageFormat_X1R5G5B5_UNORM;

	case TDDS_X1B5G5R5_UNORM: return TinyImageFormat_X1B5G5R5_UNORM;
	case TDDS_A1B5G5R5_UNORM: return TinyImageFormat_A1B5G5R5_UNORM;

	case TDDS_X4B4G4R4_UNORM: return TinyImageFormat_X4B4G4R4_UNORM;
	case TDDS_X4R4G4B4_UNORM: return TinyImageFormat_X4R4G4B4_UNORM;
	case TDDS_A4R4G4B4_UNORM:	return TinyImageFormat_A4R4G4B4_UNORM;
	case TDDS_B4G4R4A4_UNORM: return TinyImageFormat_B4G4R4A4_UNORM;
	case TDDS_A4B4G4R4_UNORM: return TinyImageFormat_A4B4G4R4_UNORM;
	case TDDS_B4G4R4X4_UNORM: return TinyImageFormat_B4G4R4X4_UNORM;
	case TDDS_R4G4B4A4_UNORM: return TinyImageFormat_R4G4B4A4_UNORM;
	case TDDS_R4G4B4X4_UNORM:	return TinyImageFormat_R4G4B4X4_UNORM;

	case TDDS_R8G8B8X8_UNORM: return TinyImageFormat_R8G8B8X8_UNORM;

	// DDS A2R10B10G10 support is basically broken historically so expect channels to need swapping
	case TDDS_A2B10G10R10_UNORM:	return TinyImageFormat_A2B10G10R10_UNORM;
	case TDDS_A2B10G10R10_SNORM:	return TinyImageFormat_A2B10G10R10_SNORM;
	case TDDS_A2R10G10B10_UNORM:	return TinyImageFormat_A2R10G10B10_UNORM;
	case TDDS_A2R10G10B10_SNORM:	return TinyImageFormat_A2R10G10B10_SNORM;
	case TDDS_B10G10R10A2_UNORM: 	return TinyImageFormat_R10G10B10A2_UNORM;
	case TDDS_B10G10R10A2_SNORM: 	return TinyImageFormat_R10G10B10A2_SNORM;
	case TDDS_R10G10B10A2_UNORM: 	return TinyImageFormat_B10G10R10A2_UNORM;
	case TDDS_R10G10B10A2_SNORM: 	return TinyImageFormat_B10G10R10A2_SNORM;
	case TDDS_R10G10B10A2_UINT: 	return TinyImageFormat_B10G10R10A2_UINT;

	case TDDS_B8G8R8X8_UNORM: return TinyImageFormat_B8G8R8X8_UNORM;

	case TDDS_G16R16_UNORM: return TinyImageFormat_G16R16_UNORM;
	case TDDS_G16R16_SNORM: return TinyImageFormat_G16R16_SNORM;
	case TDDS_X8B8G8R8_UNORM: return TinyImageFormat_R8G8B8X8_UNORM;
	case TDDS_X8R8G8B8_UNORM: return TinyImageFormat_B8G8R8X8_UNORM;
	case TDDS_A8B8G8R8_UNORM: return TinyImageFormat_R8G8B8A8_UNORM;
	case TDDS_A8R8G8B8_UNORM: return TinyImageFormat_B8G8R8A8_UNORM;
	case TDDS_A8B8G8R8_SNORM: return TinyImageFormat_R8G8B8X8_UNORM;
	case TDDS_P8: return TinyImageFormat_CLUT_P8;
	case TDDS_A8P8: return TinyImageFormat_CLUT_P8A8;
	case TDDS_R1_UNORM: return TinyImageFormat_R1_UNORM;

	case TDDS_AYUV:break;
	case TDDS_Y410:break;
	case TDDS_Y416:break;
	case TDDS_NV12:break;
	case TDDS_P010:break;
	case TDDS_P016:break;
	case TDDS_420_OPAQUE:break;
	case TDDS_YUY2:break;
	case TDDS_Y210:break;
	case TDDS_Y216:break;
	case TDDS_NV11:break;
	case TDDS_AI44:break;
	case TDDS_IA44:break;
	case TDDS_R10G10B10_7E3_A2_FLOAT:break;
	case TDDS_R10G10B10_6E4_A2_FLOAT:break;
	case TDDS_D16_UNORM_S8_UINT:break;
	case TDDS_R16_UNORM_X8_TYPELESS:break;
	case TDDS_X16_TYPELESS_G8_UINT:break;
	case TDDS_P208:break;
	case TDDS_V208:break;
	case TDDS_V408:break;
	case TDDS_R10G10B10_SNORM_A2_UNORM:break;
	}

	return TinyImageFormat_UNDEFINED;
}

static TinyDDS_Format TinyImageFormat_ToTinyDDSFormat(TinyImageFormat fmt) {
	switch (fmt) {
	case TinyImageFormat_R4G4_UNORM: return TDDS_R4G4_UNORM;
	case TinyImageFormat_G4R4_UNORM: return TDDS_G4R4_UNORM;

	case TinyImageFormat_A4R4G4B4_UNORM: return TDDS_A4R4G4B4_UNORM;
	case TinyImageFormat_B4G4R4A4_UNORM: return TDDS_B4G4R4A4_UNORM;
	case TinyImageFormat_A4B4G4R4_UNORM: return TDDS_A4B4G4R4_UNORM;
	case TinyImageFormat_X4R4G4B4_UNORM: return TDDS_X4R4G4B4_UNORM;
	case TinyImageFormat_X4B4G4R4_UNORM: return TDDS_X4B4G4R4_UNORM;
	case TinyImageFormat_R4G4B4A4_UNORM: return TDDS_R4G4B4A4_UNORM;
	case TinyImageFormat_R4G4B4X4_UNORM: return TDDS_R4G4B4X4_UNORM;

	case TinyImageFormat_A1B5G5R5_UNORM: return TDDS_A1B5G5R5_UNORM;
	case TinyImageFormat_X1B5G5R5_UNORM: return TDDS_X1B5G5R5_UNORM;

	case TinyImageFormat_A1R5G5B5_UNORM: return TDDS_A1R5G5B5_UNORM;
	case TinyImageFormat_X1R5G5B5_UNORM: return TDDS_X1R5G5B5_UNORM;

	case TinyImageFormat_B5G5R5A1_UNORM: return TDDS_B5G5R5A1_UNORM;
	case TinyImageFormat_B5G5R5X1_UNORM: return TDDS_B5G5R5X1_UNORM;

	case TinyImageFormat_R5G5B5A1_UNORM: return TDDS_R5G5B5A1_UNORM;
	case TinyImageFormat_R5G5B5X1_UNORM: return TDDS_R5G5B5X1_UNORM;

	case TinyImageFormat_R5G6B5_UNORM: return TDDS_R5G6B5_UNORM;
	case TinyImageFormat_B5G6R5_UNORM: return TDDS_B5G6R5_UNORM;

	case TinyImageFormat_A2B10G10R10_UNORM:	return TDDS_A2B10G10R10_UNORM;
	case TinyImageFormat_A2B10G10R10_SNORM:	return TDDS_A2B10G10R10_SNORM;
	case TinyImageFormat_A2R10G10B10_UNORM:	return TDDS_A2R10G10B10_UNORM;
	case TinyImageFormat_A2R10G10B10_SNORM:	return TDDS_A2R10G10B10_SNORM;
	case TinyImageFormat_R10G10B10A2_UNORM: return TDDS_B10G10R10A2_UNORM;
	case TinyImageFormat_R10G10B10A2_SNORM: return TDDS_B10G10R10A2_SNORM;
	case TinyImageFormat_B10G10R10A2_UNORM: return TDDS_R10G10B10A2_UNORM;
	case TinyImageFormat_B10G10R10A2_SNORM: return TDDS_R10G10B10A2_SNORM;
	case TinyImageFormat_B10G10R10A2_UINT: 	return TDDS_R10G10B10A2_UINT;

	case TinyImageFormat_E5B9G9R9_UFLOAT: return TDDS_R9G9B9E5_UFLOAT;
	case TinyImageFormat_B10G11R11_UFLOAT: return TDDS_R11G11B10_UFLOAT;

	case TinyImageFormat_R8_UNORM: 				return TDDS_R8_UNORM;
	case TinyImageFormat_R8_SNORM: 				return TDDS_R8_SNORM;
	case TinyImageFormat_R8_UINT: 				return TDDS_R8_UINT;
	case TinyImageFormat_R8_SINT: 				return TDDS_R8_SINT;
	case TinyImageFormat_A8_UNORM: 				return TDDS_A8_UNORM;
	case TinyImageFormat_B2G3R3_UNORM: 		return TDDS_B2G3R3_UNORM;

	case TinyImageFormat_B2G3R3A8_UNORM: 	return TDDS_B2G3R3A8_UNORM;
	case TinyImageFormat_R8G8_UNORM: 			return TDDS_R8G8_UNORM;
	case TinyImageFormat_R8G8_SNORM: 			return TDDS_R8G8_SNORM;
	case TinyImageFormat_R8G8_UINT: 			return TDDS_R8G8_UINT;
	case TinyImageFormat_R8G8_SINT: 			return TDDS_R8G8_SINT;
	case TinyImageFormat_G8R8_UNORM:			return TDDS_G8R8_UNORM;
	case TinyImageFormat_G8R8_SNORM:			return TDDS_G8R8_SNORM;

	case TinyImageFormat_R8G8B8_UNORM: 		return TDDS_R8G8B8_UNORM;
	case TinyImageFormat_B8G8R8_UNORM:		return TDDS_B8G8R8_UNORM;

	case TinyImageFormat_R8G8B8A8_UNORM: 	return TDDS_R8G8B8A8_UNORM;
	case TinyImageFormat_R8G8B8A8_SNORM: 	return TDDS_R8G8B8A8_SNORM;
	case TinyImageFormat_R8G8B8A8_UINT: 	return TDDS_R8G8B8A8_UINT;
	case TinyImageFormat_R8G8B8A8_SINT: 	return TDDS_R8G8B8A8_SINT;
	case TinyImageFormat_R8G8B8A8_SRGB: 	return TDDS_R8G8B8A8_SRGB;
	case TinyImageFormat_B8G8R8A8_UNORM: 	return TDDS_B8G8R8A8_UNORM;
	case TinyImageFormat_B8G8R8A8_SRGB:		return TDDS_B8G8R8A8_SRGB;

	case TinyImageFormat_R16_UNORM:				return TDDS_R16_UNORM;
	case TinyImageFormat_R16_SNORM:				return TDDS_R16_SNORM;
	case TinyImageFormat_R16_UINT:				return TDDS_R16_UINT;
	case TinyImageFormat_R16_SINT:				return TDDS_R16_SINT;
	case TinyImageFormat_R16_SFLOAT:			return TDDS_R16_SFLOAT;

	case TinyImageFormat_R16G16_UNORM:		return TDDS_R16G16_UNORM;
	case TinyImageFormat_R16G16_SNORM:		return TDDS_R16G16_SNORM;
	case TinyImageFormat_R16G16_UINT:			return TDDS_R16G16_UINT;
	case TinyImageFormat_R16G16_SINT:			return TDDS_R16G16_SINT;
	case TinyImageFormat_R16G16_SFLOAT:		return TDDS_R16G16_SFLOAT;

	case TinyImageFormat_G16R16_UNORM:					return TDDS_G16R16_UNORM;
	case TinyImageFormat_G16R16_SNORM:					return TDDS_G16R16_SNORM;

	case TinyImageFormat_R16G16B16A16_UNORM:	return TDDS_R16G16B16A16_UNORM;
	case TinyImageFormat_R16G16B16A16_SNORM:	return TDDS_R16G16B16A16_SNORM;
	case TinyImageFormat_R16G16B16A16_UINT:		return TDDS_R16G16B16A16_UINT;
	case TinyImageFormat_R16G16B16A16_SINT:		return TDDS_R16G16B16A16_SINT;
	case TinyImageFormat_R16G16B16A16_SFLOAT:	return TDDS_R16G16B16A16_SFLOAT;

	case TinyImageFormat_R32_UINT:				return TDDS_R32_UINT;
	case TinyImageFormat_R32_SINT:				return TDDS_R32_SINT;
	case TinyImageFormat_R32_SFLOAT:			return TDDS_R32_SFLOAT;

	case TinyImageFormat_R32G32_UINT:			return TDDS_R32G32_UINT;
	case TinyImageFormat_R32G32_SINT:			return TDDS_R32G32_SINT;
	case TinyImageFormat_R32G32_SFLOAT:		return TDDS_R32G32_SFLOAT;

	case TinyImageFormat_R32G32B32_UINT:	return TDDS_R32G32B32_UINT;
	case TinyImageFormat_R32G32B32_SINT:	return TDDS_R32G32B32_SINT;
	case TinyImageFormat_R32G32B32_SFLOAT:return TDDS_R32G32B32_SFLOAT;

	case TinyImageFormat_R32G32B32A32_UINT:		return TDDS_R32G32B32A32_UINT;
	case TinyImageFormat_R32G32B32A32_SINT:		return TDDS_R32G32B32A32_SINT;
	case TinyImageFormat_R32G32B32A32_SFLOAT:	return TDDS_R32G32B32A32_SFLOAT;

	case TinyImageFormat_D16_UNORM:				return TDDS_R16_UNORM;
	case TinyImageFormat_D32_SFLOAT:			return TDDS_R32_SFLOAT;
	case TinyImageFormat_S8_UINT:					return TDDS_R8_UINT;
	case TinyImageFormat_DXBC1_RGB_UNORM: 	return TDDS_BC1_RGBA_UNORM_BLOCK;
	case TinyImageFormat_DXBC1_RGB_SRGB:		return TDDS_BC1_RGBA_SRGB_BLOCK;
	case TinyImageFormat_DXBC1_RGBA_UNORM:	return TDDS_BC1_RGBA_UNORM_BLOCK;
	case TinyImageFormat_DXBC1_RGBA_SRGB:		return TDDS_BC1_RGBA_SRGB_BLOCK;
	case TinyImageFormat_DXBC2_UNORM:				return TDDS_BC2_UNORM_BLOCK;
	case TinyImageFormat_DXBC2_SRGB:				return TDDS_BC2_SRGB_BLOCK;
	case TinyImageFormat_DXBC3_UNORM:				return TDDS_BC3_UNORM_BLOCK;
	case TinyImageFormat_DXBC3_SRGB:				return TDDS_BC3_SRGB_BLOCK;
	case TinyImageFormat_DXBC4_UNORM:				return TDDS_BC4_UNORM_BLOCK;
	case TinyImageFormat_DXBC4_SNORM:				return TDDS_BC4_SNORM_BLOCK;
	case TinyImageFormat_DXBC5_UNORM:				return TDDS_BC5_UNORM_BLOCK;
	case TinyImageFormat_DXBC5_SNORM:				return TDDS_BC5_SNORM_BLOCK;
	case TinyImageFormat_DXBC6H_UFLOAT:			return TDDS_BC6H_UFLOAT_BLOCK;
	case TinyImageFormat_DXBC6H_SFLOAT:			return TDDS_BC6H_SFLOAT_BLOCK;
	case TinyImageFormat_DXBC7_UNORM:				return TDDS_BC7_UNORM_BLOCK;
	case TinyImageFormat_DXBC7_SRGB:				return TDDS_BC7_SRGB_BLOCK;

	case TinyImageFormat_CLUT_P8: return TDDS_P8;
	case TinyImageFormat_CLUT_P8A8: return TDDS_A8P8;
	case TinyImageFormat_R1_UNORM: return TDDS_R1_UNORM;

		// unsupported
	// TODO Some of these can be via Dx10/4CC codes I think
	default: return TDDS_UNDEFINED;
	}

	return TDDS_UNDEFINED;
}
#endif

TinyDDS_Format TinyDDS_GetFormat(TinyDDS_ContextHandle handle);

bool TinyDDS_WriteImage(TinyDDS_WriteCallbacks const *callbacks,
												void *user,
												uint32_t width,
												uint32_t height,
												uint32_t depth,
												uint32_t slices,
												uint32_t mipmaplevels,
												TinyDDS_Format format,
												bool cubemap,
												bool preferDx10Format,
												uint32_t const *mipmapsizes,
												void const **mipmaps);

#ifdef TINYDDS_IMPLEMENTATION

#define TINYDDS_DDSD_CAPS 0x00000001
#define TINYDDS_DDSD_HEIGHT 0x00000002
#define TINYDDS_DDSD_WIDTH 0x00000004
#define TINYDDS_DDSD_PITCH 0x00000008
#define TINYDDS_DDSD_PIXELFORMAT 0x00001000
#define TINYDDS_DDSD_MIPMAPCOUNT 0x00020000
#define TINYDDS_DDSD_LINEARSIZE 0x00080000
#define TINYDDS_DDSD_DEPTH 0x00800000
#define TINYDDS_DDSCAPS_COMPLEX 0x00000008
#define TINYDDS_DDSCAPS_TEXTURE 0x00001000
#define TINYDDS_DDSCAPS_MIPMAP 0x00400000
#define TINYDDS_DDSCAPS2_CUBEMAP 0x00000200
#define TINYDDS_DDSCAPS2_VOLUME 0x00200000
#define TINYDDS_DDSCAPS2_CUBEMAP_ALL 0x0000FC000
#define TINYDDS_D3D10_RESOURCE_MISC_TEXTURECUBE 0x4
#define TINYDDS_D3D10_RESOURCE_DIMENSION_BUFFER 1
#define TINYDDS_D3D10_RESOURCE_DIMENSION_TEXTURE1D 2
#define TINYDDS_D3D10_RESOURCE_DIMENSION_TEXTURE2D 3
#define TINYDDS_D3D10_RESOURCE_DIMENSION_TEXTURE3D 4
#define TINYDDS_DDPF_ALPHAPIXELS                        0x00000001l
#define TINYDDS_DDPF_ALPHA                              0x00000002l
#define TINYDDS_DDPF_FOURCC                             0x00000004l
#define TINYDDS_DDPF_PALETTEINDEXED4                    0x00000008l
#define TINYDDS_DDPF_PALETTEINDEXEDTO8                  0x00000010l
#define TINYDDS_DDPF_PALETTEINDEXED8                    0x00000020l
#define TINYDDS_DDPF_RGB                                0x00000040l
#define TINYDDS_DDPF_LUMINANCE                          0x00020000l
#define TINYDDS_DDPF_BUMPLUMINANCE                      0x00040000l
#define TINYDDS_DDPF_BUMPDUDV                           0x00080000l

// some of these get stuck in unofficial DDS v9 FourCC code
typedef enum TINYDDS_D3DFORMAT {
	TINYDDS_D3DFMT_UNKNOWN              =  0,
	TINYDDS_D3DFMT_R8G8B8               = 20,
	TINYDDS_D3DFMT_A8R8G8B8             = 21,
	TINYDDS_D3DFMT_X8R8G8B8             = 22,
	TINYDDS_D3DFMT_R5G6B5               = 23,
	TINYDDS_D3DFMT_X1R5G5B5             = 24,
	TINYDDS_D3DFMT_A1R5G5B5             = 25,
	TINYDDS_D3DFMT_A4R4G4B4             = 26,
	TINYDDS_D3DFMT_R3G3B2               = 27,
	TINYDDS_D3DFMT_A8                   = 28,
	TINYDDS_D3DFMT_A8R3G3B2             = 29,
	TINYDDS_D3DFMT_X4R4G4B4             = 30,
	TINYDDS_D3DFMT_A2B10G10R10          = 31,
	TINYDDS_D3DFMT_A8B8G8R8             = 32,
	TINYDDS_D3DFMT_X8B8G8R8             = 33,
	TINYDDS_D3DFMT_G16R16               = 34,
	TINYDDS_D3DFMT_A2R10G10B10          = 35,
	TINYDDS_D3DFMT_A16B16G16R16         = 36,
	TINYDDS_D3DFMT_A8P8                 = 40,
	TINYDDS_D3DFMT_P8                   = 41,
	TINYDDS_D3DFMT_L8                   = 50,
	TINYDDS_D3DFMT_A8L8                 = 51,
	TINYDDS_D3DFMT_A4L4                 = 52,
	TINYDDS_D3DFMT_V8U8                 = 60,
	TINYDDS_D3DFMT_L6V5U5               = 61,
	TINYDDS_D3DFMT_X8L8V8U8             = 62,
	TINYDDS_D3DFMT_Q8W8V8U8             = 63,
	TINYDDS_D3DFMT_V16U16               = 64,
	TINYDDS_D3DFMT_A2W10V10U10          = 67,
	TINYDDS_D3DFMT_L16                  = 81,
	TINYDDS_D3DFMT_Q16W16V16U16         = 110,
	TINYDDS_D3DFMT_R16F                 = 111,
	TINYDDS_D3DFMT_G16R16F              = 112,
	TINYDDS_D3DFMT_A16B16G16R16F        = 113,
	TINYDDS_D3DFMT_R32F                 = 114,
	TINYDDS_D3DFMT_G32R32F              = 115,
	TINYDDS_D3DFMT_A32B32G32R32F        = 116,
	TINYDDS_D3DFMT_CxV8U8               = 117,
	TINYDDS_D3DFMT_A1                   = 118,
	TINYDDS_D3DFMT_A2B10G10R10_XR_BIAS  = 119,
} TINYDDS_D3DFORMAT;

typedef struct TinyDDS_Header {
	uint32_t magic;
	uint32_t size;
	uint32_t flags;
	uint32_t height;
	uint32_t width;
	uint32_t pitchOrLinearSize;
	uint32_t depth;
	uint32_t mipMapCount;
	uint32_t reserved0[11];

	uint32_t formatSize;
	uint32_t formatFlags;
	uint32_t formatFourCC;
	uint32_t formatRGBBitCount;
	uint32_t formatRBitMask;
	uint32_t formatGBitMask;
	uint32_t formatBBitMask;
	uint32_t formatABitMask;

	uint32_t caps1;
	uint32_t caps2;
	uint32_t caps3; // not used?
	uint32_t caps4; // not used?

	uint32_t reserved1;
} TinyDDS_Header;

typedef struct TinyDDS_HeaderDX10 {
	uint32_t DXGIFormat;
	uint32_t resourceDimension;
	uint32_t miscFlag;
	uint32_t arraySize;
	uint32_t reserved;
} TinyDDS_HeaderDX10;

typedef struct TinyDDS_Context {
	TinyDDS_Callbacks callbacks;
	void *user;
	uint64_t headerPos;
	uint64_t firstImagePos;

	TinyDDS_Header header;
	TinyDDS_HeaderDX10 headerDx10;
	TinyDDS_Format format;

	bool headerValid;
	uint8_t const *mipmaps[TINYDDS_MAX_MIPMAPLEVELS];
	uint32_t const *clut;

} TinyDDS_Context;

#define TINYDDS_MAKE_RIFFCODE(a, b, c, d) (a | (b << 8) | (c << 16) | (d << 24))

static uint32_t TinyDDS_fileIdentifier = TINYDDS_MAKE_RIFFCODE('D', 'D', 'S', ' ');

static void TinyDDS_NullErrorFunc(void *user, char const *msg) { BASISU_NOTE_UNUSED(user); BASISU_NOTE_UNUSED(msg); }

TinyDDS_ContextHandle TinyDDS_CreateContext(TinyDDS_Callbacks const *callbacks, void *user) {
	TinyDDS_Context *ctx = (TinyDDS_Context *) callbacks->allocFn(user, sizeof(TinyDDS_Context));
	if (ctx == NULL)
		return NULL;

	memset(ctx, 0, sizeof(TinyDDS_Context));
	memcpy(&ctx->callbacks, callbacks, sizeof(TinyDDS_Callbacks));
	ctx->user = user;
	if (ctx->callbacks.errorFn == NULL) {
		ctx->callbacks.errorFn = &TinyDDS_NullErrorFunc;
	}

	if (ctx->callbacks.readFn == NULL) {
		ctx->callbacks.errorFn(user, "TinyDDS must have read callback");
		return NULL;
	}
	if (ctx->callbacks.allocFn == NULL) {
		ctx->callbacks.errorFn(user, "TinyDDS must have alloc callback");
		return NULL;
	}
	if (ctx->callbacks.freeFn == NULL) {
		ctx->callbacks.errorFn(user, "TinyDDS must have free callback");
		return NULL;
	}
	if (ctx->callbacks.seekFn == NULL) {
		ctx->callbacks.errorFn(user, "TinyDDS must have seek callback");
		return NULL;
	}
	if (ctx->callbacks.tellFn == NULL) {
		ctx->callbacks.errorFn(user, "TinyDDS must have tell callback");
		return NULL;
	}

	TinyDDS_Reset(ctx);

	return ctx;
}

void TinyDDS_DestroyContext(TinyDDS_ContextHandle handle) {
	TinyDDS_Context *ctx = (TinyDDS_Context *) handle;
	if (ctx == NULL)
		return;
	TinyDDS_Reset(handle);

	ctx->callbacks.freeFn(ctx->user, ctx);
}

void TinyDDS_Reset(TinyDDS_ContextHandle handle) {
	TinyDDS_Context *ctx = (TinyDDS_Context *) handle;
	if (ctx == NULL)
		return;

	// backup user provided callbacks and data
	TinyDDS_Callbacks callbacks;
	memcpy(&callbacks, &ctx->callbacks, sizeof(TinyDDS_Callbacks));
	void *user = ctx->user;

	for (int i = 0; i < TINYDDS_MAX_MIPMAPLEVELS; ++i) {
		if (ctx->mipmaps[i] != NULL) {
			callbacks.freeFn(user, (void *) ctx->mipmaps[i]);
		}
	}

	if(ctx->clut) {
		callbacks.freeFn(user, (void *) ctx->clut);
		ctx->clut = NULL;
	}

	// reset to default state
	memset(ctx, 0, sizeof(TinyDDS_Context));
	memcpy(&ctx->callbacks, &callbacks, sizeof(TinyDDS_Callbacks));
	ctx->user = user;

}

static bool TinyDDS_IsCLUT(TinyDDS_Format fmt) {
	switch (fmt) {
	case TDDS_P8:
	case TDDS_A8P8:
		return true;
	default: return false;
	}
}

static bool TinyDDS_IsCompressed(TinyDDS_Format fmt) {
	switch (fmt) {
	case TDDS_BC1_RGBA_UNORM_BLOCK:
	case TDDS_BC1_RGBA_SRGB_BLOCK:
	case TDDS_BC2_UNORM_BLOCK:
	case TDDS_BC2_SRGB_BLOCK:
	case TDDS_BC3_UNORM_BLOCK:
	case TDDS_BC3_SRGB_BLOCK:
	case TDDS_BC4_UNORM_BLOCK:
	case TDDS_BC4_SNORM_BLOCK:
	case TDDS_BC5_UNORM_BLOCK:
	case TDDS_BC5_SNORM_BLOCK:
	case TDDS_BC6H_UFLOAT_BLOCK:
	case TDDS_BC6H_SFLOAT_BLOCK:
	case TDDS_BC7_UNORM_BLOCK:
	case TDDS_BC7_SRGB_BLOCK: return true;
	default: return false;
	}
}

// the size is per pixel (except R1) for uncompressed and per block of 16 pixels for compressed
static uint32_t TinyDDS_FormatSize(TinyDDS_Format fmt) {
	switch(fmt) {
	// 8 pixels at 1 bits each
	case TDDS_R1_UNORM:
		return 1;
		// 2 * 4 bits
	case TDDS_R4G4_UNORM:
	case TDDS_G4R4_UNORM:
		// 1 * 8 bits
	case TDDS_P8:;
	case TDDS_R8_UNORM:
	case TDDS_R8_SNORM:
	case TDDS_R8_UINT:
	case TDDS_R8_SINT:
	case TDDS_A8_UNORM:
	// 2 + 2 * 3 bits
	case TDDS_B2G3R3_UNORM:
		return 1;

		// 2 + 2 * 3 +8 bits
	case TDDS_B2G3R3A8_UNORM:
	// 4 * 4 bits
	case TDDS_B4G4R4A4_UNORM:
	case TDDS_A4B4G4R4_UNORM:
	case TDDS_X4B4G4R4_UNORM:
	case TDDS_A4R4G4B4_UNORM:
	case TDDS_X4R4G4B4_UNORM:
	case TDDS_B4G4R4X4_UNORM:
	case TDDS_R4G4B4A4_UNORM:
	case TDDS_R4G4B4X4_UNORM:

		// 3 * 5 bits + 1 bit
	case TDDS_B5G5R5A1_UNORM:
	case TDDS_B5G5R5X1_UNORM:
	case TDDS_R5G5B5A1_UNORM:
	case TDDS_R5G5B5X1_UNORM:
	case TDDS_A1R5G5B5_UNORM:
	case TDDS_X1R5G5B5_UNORM:
	case TDDS_A1B5G5R5_UNORM:
	case TDDS_X1B5G5R5_UNORM:

		// 1 * 6 bit + 2 * 5 bits
	case TDDS_R5G6B5_UNORM:
	case TDDS_B5G6R5_UNORM:
		// 2 x 8 bits
	case TDDS_A8P8:
	case TDDS_R8G8_UNORM:
	case TDDS_R8G8_SNORM:
	case TDDS_G8R8_UNORM:
	case TDDS_G8R8_SNORM:
	case TDDS_R8G8_UINT:
	case TDDS_R8G8_SINT:
		// 1 * 16 bits
	case TDDS_R16_UNORM:
	case TDDS_R16_SNORM:
	case TDDS_R16_UINT:
	case TDDS_R16_SINT:
	case TDDS_R16_SFLOAT:
		return 2;

		// 3 * 8 bits
	case TDDS_R8G8B8_UNORM:
	case TDDS_B8G8R8_UNORM:
		return 3;
	// 4 * 8 bits
	case TDDS_A8B8G8R8_SNORM:
	case TDDS_R8G8B8A8_SNORM:
	case TDDS_R8G8B8A8_UINT:
	case TDDS_R8G8B8A8_SINT:
	case TDDS_R8G8B8A8_SRGB:
	case TDDS_B8G8R8A8_SRGB:
	case TDDS_B8G8R8A8_SNORM:

	case TDDS_R8G8B8A8_UNORM:
	case TDDS_R8G8B8X8_UNORM:
	case TDDS_B8G8R8A8_UNORM:
	case TDDS_B8G8R8X8_UNORM:
	case TDDS_A8B8G8R8_UNORM:
	case TDDS_X8B8G8R8_UNORM:
	case TDDS_A8R8G8B8_UNORM:
	case TDDS_X8R8G8B8_UNORM:

		// 3 * 9 bits + 5 bits
	case TDDS_R9G9B9E5_UFLOAT:
		// 3 * 10 bits + 2 bits
	case TDDS_R10G10B10_7E3_A2_FLOAT:
	case TDDS_R10G10B10_6E4_A2_FLOAT:
	case TDDS_R10G10B10_SNORM_A2_UNORM:

	case TDDS_B10G10R10A2_UNORM:
	case TDDS_B10G10R10A2_SNORM:
	case TDDS_A2B10G10R10_UNORM:
	case TDDS_A2B10G10R10_SNORM:
	case TDDS_A2R10G10B10_UNORM:
	case TDDS_A2R10G10B10_SNORM:
	case TDDS_R10G10B10A2_UNORM:
	case TDDS_R10G10B10A2_SNORM:
	case TDDS_R10G10B10A2_UINT:

		// 2 * 11 bits + 10 bits
	case TDDS_R11G11B10_UFLOAT:
		// 2 * 16 bits
	case TDDS_R16G16_UNORM:
	case TDDS_R16G16_SNORM:
	case TDDS_R16G16_UINT:
	case TDDS_R16G16_SINT:
	case TDDS_R16G16_SFLOAT:
	case TDDS_G16R16_UNORM:
	case TDDS_G16R16_SNORM:
		// 1 * 32 bits
	case TDDS_R32_UINT:
	case TDDS_R32_SINT:
	case TDDS_R32_SFLOAT:
		return 4;
		// 4 * 16 bits
	case TDDS_R16G16B16A16_UNORM:
	case TDDS_R16G16B16A16_SNORM:
	case TDDS_R16G16B16A16_UINT:
	case TDDS_R16G16B16A16_SINT:
	case TDDS_R16G16B16A16_SFLOAT:
		// 2 * 32 bits
	case TDDS_R32G32_UINT:
	case TDDS_R32G32_SINT:
	case TDDS_R32G32_SFLOAT:
		return 8;
		// 3 * 32 bits
	case TDDS_R32G32B32_UINT:
	case TDDS_R32G32B32_SINT:
	case TDDS_R32G32B32_SFLOAT:
		return 12;
		// 4 * 32 bits
	case TDDS_R32G32B32A32_UINT:
	case TDDS_R32G32B32A32_SINT:
	case TDDS_R32G32B32A32_SFLOAT:
		return 16;
		// block formats
	case TDDS_BC1_RGBA_UNORM_BLOCK:
	case TDDS_BC1_RGBA_SRGB_BLOCK:
	case TDDS_BC4_UNORM_BLOCK:
	case TDDS_BC4_SNORM_BLOCK:
		return 8;

 	case TDDS_BC2_UNORM_BLOCK:
	case TDDS_BC2_SRGB_BLOCK:
	case TDDS_BC3_UNORM_BLOCK:
	case TDDS_BC3_SRGB_BLOCK:
	case TDDS_BC5_UNORM_BLOCK:
	case TDDS_BC5_SNORM_BLOCK:
	case TDDS_BC6H_UFLOAT_BLOCK:
	case TDDS_BC6H_SFLOAT_BLOCK:
	case TDDS_BC7_UNORM_BLOCK:
	case TDDS_BC7_SRGB_BLOCK:
		return 16;

	case TDDS_UNDEFINED: return 0;
		//	default: return 0;
	case TDDS_AYUV:break;
	case TDDS_Y410:break;
	case TDDS_Y416:break;
	case TDDS_NV12:break;
	case TDDS_P010:break;
	case TDDS_P016:break;
	case TDDS_420_OPAQUE:break;
	case TDDS_YUY2:break;
	case TDDS_Y210:break;
	case TDDS_Y216:break;
	case TDDS_NV11:break;
	case TDDS_AI44:break;
	case TDDS_IA44:break;
	case TDDS_D16_UNORM_S8_UINT:break;
	case TDDS_R16_UNORM_X8_TYPELESS:break;
	case TDDS_X16_TYPELESS_G8_UINT:break;
	case TDDS_P208:break;
	case TDDS_V208:break;
	case TDDS_V408:break;
	}
	return 0;
}

#define TINYDDS_CHK_DDSFORMAT(bits, rm, gm, bm, am, fmt) \
			if ((ctx->header.formatRGBBitCount == bits) && \
					(ctx->header.formatRBitMask == rm) && \
					(ctx->header.formatGBitMask == gm) && \
					(ctx->header.formatBBitMask == bm) && \
					(ctx->header.formatABitMask == am)) { return fmt; }

static TinyDDS_Format TinyDDS_DecodeFormat(TinyDDS_Context *ctx) {
	if (ctx->header.formatFlags & TINYDDS_DDPF_FOURCC) {
		if (ctx->headerDx10.DXGIFormat != TIF_DXGI_FORMAT_UNKNOWN) {
			return (TinyDDS_Format) ctx->headerDx10.DXGIFormat;
		}

		// check fourCC and some special numbers..
		// unofficially during the dx9 timeline, D3D_FORMAT were stuck directly into
		// formatFourCC field we handle FourCC and these < 119 codes here
		// its unclear if this was only for formats that couldn't be exposed via
		// Direct Draw Surfaces (like floats etc.) so I decode most of them anyway
		switch (ctx->header.formatFourCC) {
		case TINYDDS_D3DFMT_R8G8B8: return TDDS_R8G8B8_UNORM;
		case TINYDDS_D3DFMT_A8R8G8B8: return TDDS_A8R8G8B8_UNORM;
		case TINYDDS_D3DFMT_X8R8G8B8: return TDDS_X8R8G8B8_UNORM;
		case TINYDDS_D3DFMT_R5G6B5: return TDDS_R5G6B5_UNORM;
		case TINYDDS_D3DFMT_X1R5G5B5: return TDDS_X1R5G5B5_UNORM;
		case TINYDDS_D3DFMT_A1R5G5B5: return TDDS_A1R5G5B5_UNORM;
		case TINYDDS_D3DFMT_A4R4G4B4: return TDDS_A4R4G4B4_UNORM;
		case TINYDDS_D3DFMT_R3G3B2: return TDDS_B2G3R3_UNORM;
		case TINYDDS_D3DFMT_A8: return TDDS_A8_UNORM;
		case TINYDDS_D3DFMT_A8R3G3B2: return TDDS_B2G3R3A8_UNORM;
		case TINYDDS_D3DFMT_X4R4G4B4: return TDDS_A4R4G4B4_UNORM;
		case TINYDDS_D3DFMT_A2B10G10R10: return TDDS_A2B10G10R10_UNORM;
		case TINYDDS_D3DFMT_A8B8G8R8: return TDDS_A8B8G8R8_UNORM;
		case TINYDDS_D3DFMT_X8B8G8R8: return TDDS_A8B8G8R8_UNORM;
		case TINYDDS_D3DFMT_A2R10G10B10: return TDDS_A2R10G10B10_UNORM;
		case TINYDDS_D3DFMT_G16R16: return TDDS_R16G16_UNORM;
		case TINYDDS_D3DFMT_A16B16G16R16: return TDDS_R16G16B16A16_UNORM;
		case TINYDDS_D3DFMT_R16F: return TDDS_R16_SFLOAT;
		case TINYDDS_D3DFMT_G16R16F: return TDDS_R16G16_SFLOAT;
		case TINYDDS_D3DFMT_A16B16G16R16F: return TDDS_R16G16B16A16_SFLOAT;
		case TINYDDS_D3DFMT_A8P8: return TDDS_A8P8;
		case TINYDDS_D3DFMT_P8: return TDDS_P8;
		case TINYDDS_D3DFMT_L8: return TDDS_R8_UNORM;
		case TINYDDS_D3DFMT_A8L8: return TDDS_R8G8_UNORM;
		case TINYDDS_D3DFMT_A4L4: return TDDS_R4G4_UNORM;
		case TINYDDS_D3DFMT_V8U8: return TDDS_G8R8_SNORM;
		case TINYDDS_D3DFMT_L6V5U5: return TDDS_UNDEFINED; // TODO TDDS_R5G6B5_SNORM_PACK16;
		case TINYDDS_D3DFMT_X8L8V8U8: return TDDS_R8G8B8A8_SNORM;
		case TINYDDS_D3DFMT_Q8W8V8U8: return TDDS_R8G8B8A8_SNORM;
		case TINYDDS_D3DFMT_V16U16: return TDDS_R16G16_SNORM;
		case TINYDDS_D3DFMT_A2W10V10U10: return TDDS_A2B10G10R10_SNORM;
		case TINYDDS_D3DFMT_L16: return TDDS_R16_UNORM;
		case TINYDDS_D3DFMT_Q16W16V16U16: return TDDS_R16G16B16A16_SNORM;
		case TINYDDS_D3DFMT_R32F: return TDDS_R32_SFLOAT;
		case TINYDDS_D3DFMT_G32R32F: return TDDS_R32G32_SFLOAT;
		case TINYDDS_D3DFMT_A32B32G32R32F: return TDDS_R32G32B32A32_SFLOAT;
		case TINYDDS_D3DFMT_CxV8U8: return TDDS_UNDEFINED;
		case TINYDDS_D3DFMT_A1: return TDDS_R1_UNORM;
		case TINYDDS_D3DFMT_A2B10G10R10_XR_BIAS: return TDDS_UNDEFINED;

			// real 4CC no exotics yet just the block compression ones
		case TINYDDS_MAKE_RIFFCODE('D', 'X', 'T', '1'): return TDDS_BC1_RGBA_UNORM_BLOCK;
		case TINYDDS_MAKE_RIFFCODE('D', 'X', 'T', '2'): return TDDS_BC2_UNORM_BLOCK;
		case TINYDDS_MAKE_RIFFCODE('D', 'X', 'T', '3'): return TDDS_BC2_UNORM_BLOCK;
		case TINYDDS_MAKE_RIFFCODE('D', 'X', 'T', '4'): return TDDS_BC3_UNORM_BLOCK;
		case TINYDDS_MAKE_RIFFCODE('D', 'X', 'T', '5'): return TDDS_BC3_UNORM_BLOCK;
		case TINYDDS_MAKE_RIFFCODE('A', 'T', 'I', '1'): return TDDS_BC4_UNORM_BLOCK;
		case TINYDDS_MAKE_RIFFCODE('A', 'T', 'I', '2'): return TDDS_BC5_UNORM_BLOCK;
		case TINYDDS_MAKE_RIFFCODE('B', 'C', '4', 'U'): return TDDS_BC4_UNORM_BLOCK;
		case TINYDDS_MAKE_RIFFCODE('B', 'C', '4', 'S'): return TDDS_BC4_SNORM_BLOCK;
		case TINYDDS_MAKE_RIFFCODE('B', 'C', '5', 'U'): return TDDS_BC5_UNORM_BLOCK;
		case TINYDDS_MAKE_RIFFCODE('B', 'C', '5', 'S'): return TDDS_BC5_SNORM_BLOCK;
		}
	}

	// okay back to direct draw surface bit fields to try and work format out.
	// TODO this could be better i'm sure

	if ((ctx->header.formatFlags & TINYDDS_DDPF_PALETTEINDEXED4)) {
		return TDDS_UNDEFINED; // TODO 4 bit CLUTs
	}

	if ((ctx->header.formatFlags & TINYDDS_DDPF_PALETTEINDEXED8)) {
		if(ctx->header.formatRGBBitCount != 8) return TDDS_UNDEFINED;
		if(ctx->header.formatFlags & TINYDDS_DDPF_ALPHA) {
			return TDDS_A8P8;
		} else {
			return TDDS_P8;
		}
	}
	// what is this? TINYDDS_DDPF_PALETTEINDEXEDTO8

	// most have RGB data and/or alpha
	if ((ctx->header.formatFlags & TINYDDS_DDPF_RGB) ||
			(ctx->header.formatFlags & TINYDDS_DDPF_ALPHA)) {

		TINYDDS_CHK_DDSFORMAT(1, 0x1, 0x0, 0, 0, TDDS_R1_UNORM);

		TINYDDS_CHK_DDSFORMAT(8, 0xF0, 0x0F, 0, 0, TDDS_G4R4_UNORM);
		TINYDDS_CHK_DDSFORMAT(8, 0x0F, 0xF0, 0, 0, TDDS_R4G4_UNORM);
		TINYDDS_CHK_DDSFORMAT(8, 0xFF, 0, 0, 0, TDDS_R8_UNORM);
		TINYDDS_CHK_DDSFORMAT(8, 0, 0, 0, 0xFF, TDDS_A8_UNORM);
		TINYDDS_CHK_DDSFORMAT(8, 0xE0, 0x1C, 0x3, 0, TDDS_B2G3R3_UNORM);

		TINYDDS_CHK_DDSFORMAT(16, 0xF000, 0x0F00, 0x00F0, 0x000F, TDDS_A4B4G4R4_UNORM);
		TINYDDS_CHK_DDSFORMAT(16, 0xF000, 0x0F00, 0x00F0, 0x0000, TDDS_X4B4G4R4_UNORM);

		TINYDDS_CHK_DDSFORMAT(16, 0x00F0, 0x0F00, 0xF000, 0x000F, TDDS_A4R4G4B4_UNORM);
		TINYDDS_CHK_DDSFORMAT(16, 0x00F0, 0x0F00, 0xF000, 0x0000, TDDS_X4R4G4B4_UNORM);

		TINYDDS_CHK_DDSFORMAT(16, 0x0F00, 0x00F0, 0x000F, 0xF000, TDDS_B4G4R4A4_UNORM);
		TINYDDS_CHK_DDSFORMAT(16, 0x0F00, 0x00F0, 0x000F, 0x0000, TDDS_B4G4R4X4_UNORM);

		TINYDDS_CHK_DDSFORMAT(16, 0x000F, 0x00F0, 0x0F00, 0xF000, TDDS_R4G4B4A4_UNORM);
		TINYDDS_CHK_DDSFORMAT(16, 0x000F, 0x00F0, 0x0F00, 0x0000, TDDS_R4G4B4X4_UNORM);

		TINYDDS_CHK_DDSFORMAT(16, 0x7C00, 0x03E0, 0x001F, 0x8000, TDDS_B5G5R5A1_UNORM);
		TINYDDS_CHK_DDSFORMAT(16, 0x7C00, 0x03E0, 0x001F, 0x0000, TDDS_B5G5R5X1_UNORM);

		TINYDDS_CHK_DDSFORMAT(16, 0x001F, 0x03E0, 0x7C00, 0x8000, TDDS_R5G5B5A1_UNORM);
		TINYDDS_CHK_DDSFORMAT(16, 0x001F, 0x03E0, 0x7C00, 0x0000, TDDS_R5G5B5X1_UNORM);

		TINYDDS_CHK_DDSFORMAT(16, 0x003E, 0x07C0, 0xF800, 0x0001, TDDS_A1R5G5B5_UNORM);
		TINYDDS_CHK_DDSFORMAT(16, 0x003E, 0x07C0, 0xF800, 0x0000, TDDS_X1R5G5B5_UNORM);

		TINYDDS_CHK_DDSFORMAT(16, 0xF800, 0x07C0, 0x003E, 0x0001, TDDS_A1B5G5R5_UNORM);
		TINYDDS_CHK_DDSFORMAT(16, 0xF800, 0x07C0, 0x003E, 0x0000, TDDS_X1B5G5R5_UNORM);

		TINYDDS_CHK_DDSFORMAT(16, 0xF800, 0x07E0, 0x001F, 0x0000, TDDS_B5G6R5_UNORM);
		TINYDDS_CHK_DDSFORMAT(16, 0x001F, 0x07E0, 0xF800, 0x0000, TDDS_R5G6B5_UNORM);

		TINYDDS_CHK_DDSFORMAT(16, 0x00FF, 0xFF00, 0x0000, 0x0000, TDDS_R8G8_UNORM);
		TINYDDS_CHK_DDSFORMAT(16, 0xFF00, 0x00FF, 0x0000, 0x0000, TDDS_G8R8_UNORM);

		TINYDDS_CHK_DDSFORMAT(16, 0xFFFF, 0x0000, 0x0000, 0x0000, TDDS_R16_UNORM);

		TINYDDS_CHK_DDSFORMAT(16, 0xE0, 0x1C, 0x3, 0xFF00, TDDS_B2G3R3A8_UNORM);

		TINYDDS_CHK_DDSFORMAT(24, 0xFF0000, 0x00FF00, 0x0000FF, 0x0, TDDS_B8G8R8_UNORM);
		TINYDDS_CHK_DDSFORMAT(24, 0x0000FF, 0x00FF00, 0xFF0000, 0x0, TDDS_R8G8B8_UNORM);

		TINYDDS_CHK_DDSFORMAT(32, 0x000000FF, 0x0000FF00, 0x00FF0000, 0xFF000000, TDDS_R8G8B8A8_UNORM);
		TINYDDS_CHK_DDSFORMAT(32, 0x000000FF, 0x0000FF00, 0x00FF0000, 0x00000000, TDDS_R8G8B8X8_UNORM);

		TINYDDS_CHK_DDSFORMAT(32, 0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000, TDDS_B8G8R8A8_UNORM);
		TINYDDS_CHK_DDSFORMAT(32, 0x00FF0000, 0x0000FF00, 0x000000FF, 0x00000000, TDDS_B8G8R8X8_UNORM);

		TINYDDS_CHK_DDSFORMAT(32, 0xFF000000, 0x00FF0000, 0x0000FF00, 0x000000FF, TDDS_A8B8G8R8_UNORM);
		TINYDDS_CHK_DDSFORMAT(32, 0xFF000000, 0x00FF0000, 0x0000FF00, 0x00000000, TDDS_X8B8G8R8_UNORM);

		TINYDDS_CHK_DDSFORMAT(32, 0x0000FF00, 0x00FF0000, 0xFF000000, 0x000000FF, TDDS_A8R8G8B8_UNORM);
		TINYDDS_CHK_DDSFORMAT(32, 0x0000FF00, 0x00FF0000, 0xFF000000, 0x00000000, TDDS_X8R8G8B8_UNORM);

		TINYDDS_CHK_DDSFORMAT(32, 0x000003FF, 0x000FFC00, 0x3FF00000, 0xC0000000, TDDS_R10G10B10A2_UNORM);
		TINYDDS_CHK_DDSFORMAT(32, 0xFFC00000, 0x003FF000, 0x00000FFC, 0x00000003, TDDS_A2B10G10R10_UNORM);
		TINYDDS_CHK_DDSFORMAT(32, 0x00000FFC, 0x003FF000, 0xFFC00000, 0x00000003, TDDS_A2R10G10B10_UNORM);

		// this is often written incorrectly so we use the most 'common' version
		TINYDDS_CHK_DDSFORMAT(32, 0x3FF00000, 0x000FFC00, 0x000003FF, 0xC0000000, TDDS_B10G10R10A2_UNORM);


		TINYDDS_CHK_DDSFORMAT(32, 0xFFFF0000, 0x0000FFFF, 0x00000000, 0x00000000, TDDS_G16R16_UNORM);
		TINYDDS_CHK_DDSFORMAT(32, 0x0000FFFF, 0xFFFF0000, 0x00000000, 0x00000000, TDDS_R16G16_UNORM);
		TINYDDS_CHK_DDSFORMAT(32, 0xFFFFFFFF, 0x00000000, 0x00000000, 0x00000000, TDDS_R32_UINT);

		if (ctx->header.formatRGBBitCount == 8) return TDDS_R8_UINT;
		if (ctx->header.formatRGBBitCount == 16) return TDDS_R16_UINT;
		if (ctx->header.formatRGBBitCount == 32) return TDDS_R32_UINT;
	}

	if ((ctx->header.formatFlags & TINYDDS_DDPF_BUMPDUDV) ||
			(ctx->header.formatFlags & TINYDDS_DDPF_BUMPLUMINANCE)) {
		TINYDDS_CHK_DDSFORMAT(16, 0xFF00, 0x00FF, 0x0000, 0x0000, TDDS_G8R8_SNORM);
		TINYDDS_CHK_DDSFORMAT(16, 0x00FF, 0xFF00, 0x0000, 0x0000, TDDS_R8G8_SNORM);

		TINYDDS_CHK_DDSFORMAT(32, 0xFFFF0000, 0x0000FFFF, 0x0000, 0x0, TDDS_G16R16_SNORM);
		TINYDDS_CHK_DDSFORMAT(32, 0x0000FFFF, 0xFFFF0000, 0x0000, 0x0, TDDS_R16G16_SNORM);
		TINYDDS_CHK_DDSFORMAT(32, 0x000000FF, 0x0000FF00, 0x00FF0000, 0xFF000000, TDDS_R8G8B8A8_SNORM);
		TINYDDS_CHK_DDSFORMAT(32, 0x000003FF, 0x000FFC00, 0x3FF00000, 0xC0000000, TDDS_R10G10B10A2_SNORM);
		TINYDDS_CHK_DDSFORMAT(32, 0x3FF00000, 0x000FFC00, 0x000003FF, 0xC0000000, TDDS_B10G10R10A2_SNORM);
		TINYDDS_CHK_DDSFORMAT(32, 0x00000FFC, 0x003FF000, 0xFFC00000, 0x00000003, TDDS_A2R10G10B10_SNORM);
		TINYDDS_CHK_DDSFORMAT(32, 0xFFC00000, 0x003FF000, 0x00000FFC, 0x00000003, TDDS_A2B10G10R10_SNORM);

		if (ctx->header.formatRGBBitCount == 8) return TDDS_R8_SINT;
		if (ctx->header.formatRGBBitCount == 16) return TDDS_R16_SINT;
		if (ctx->header.formatRGBBitCount == 32) return TDDS_R32_SINT;
	}

	if (ctx->header.formatFlags & TINYDDS_DDPF_LUMINANCE) {
		TINYDDS_CHK_DDSFORMAT(8, 0x0F, 0x00, 0x00, 0xF0, TDDS_R4G4_UNORM); // this is A4L4 aka A4R4 we decode this as R4G4
		TINYDDS_CHK_DDSFORMAT(16, 0x00FF, 0x0000, 0x0000, 0xFF00, TDDS_R8G8_UNORM); // this is A8L8 aka A4R8 we decode this as R8G8

		if (ctx->header.formatRGBBitCount == 8) return TDDS_R8_UNORM;
		if (ctx->header.formatRGBBitCount == 16) return TDDS_R16_UNORM;
		if (ctx->header.formatRGBBitCount == 32) return TDDS_R32_UINT;

	}

	return TDDS_UNDEFINED;
}
#undef TINYDDS_CHK_DDSFORMAT

static uint32_t TinyDDS_MipMapReduce(uint32_t value, uint32_t mipmaplevel) {

	// handle 0 being passed in
	if (value <= 1)
		return 1;

	// there are better ways of doing this (log2 etc.) but this doesn't require any
	// dependecies and isn't used enough to matter imho
	for (uint32_t i = 0u; i < mipmaplevel; ++i) {
		if (value <= 1)
			return 1;
		value = value / 2;
	}
	return value;
}

bool TinyDDS_ReadHeader(TinyDDS_ContextHandle handle) {
	TinyDDS_Context *ctx = (TinyDDS_Context *) handle;
	if (ctx == NULL)
		return false;

	ctx->headerPos = ctx->callbacks.tellFn(ctx->user);
	if( ctx->callbacks.readFn(ctx->user, &ctx->header, sizeof(TinyDDS_Header)) != sizeof(TinyDDS_Header)) {
		ctx->callbacks.errorFn(ctx->user, "Could not read DDS header");
		return false;
	}

	// try the easy case of a modern dx10 DDS file
	if ((ctx->header.formatFlags & TINYDDS_DDPF_FOURCC) &&
			(ctx->header.formatFourCC == TINYDDS_MAKE_RIFFCODE('D', 'X', '1', '0'))) {
		ctx->callbacks.readFn(ctx->user, &ctx->headerDx10, sizeof(TinyDDS_HeaderDX10));

		if (ctx->headerDx10.DXGIFormat >= TDDS_SYNTHESISED_DXGIFORMATS) {
			ctx->callbacks.errorFn(ctx->user, "DX10 Header has an invalid DXGI_FORMAT code");
			return false;
		}
	}

	ctx->format = TinyDDS_DecodeFormat(ctx);
	if (ctx->format == TDDS_UNDEFINED) {
		ctx->callbacks.errorFn(ctx->user, "Could not decode DDS format");
		return false;
	}

	if(	(ctx->header.formatFourCC == 0) &&
			(ctx->header.formatRGBBitCount != 0) &&
			((ctx->header.formatRGBBitCount/8) != TinyDDS_FormatSize(ctx->format))) {
		ctx->callbacks.errorFn(ctx->user, "Format size mismatch");
		return false;
	}

	// correct for dodgy mipmap levels counts
	if(ctx->header.mipMapCount > 1) {
		uint32_t w = ctx->header.width;
		uint32_t h = ctx->header.height;

		for(uint32_t i = 0; i < ctx->header.mipMapCount;++i) {
			if (TinyDDS_IsCompressed(ctx->format)) {
				if (w <= 4 || h <= 4) {
					ctx->header.mipMapCount = i + 1;
					break;
				}
			} else if (w <= 1 || h <= 1) {
				ctx->header.mipMapCount = i + 1;
				break;
			}


			w = w / 2;
			h = h / 2;
		}

	}

	if (TinyDDS_IsCompressed(ctx->format)) {
		// compressed images never get asked to make mip maps which is good as
		// requires decompress/compress cycle
		if(ctx->header.mipMapCount == 0) ctx->header.mipMapCount = 1;
	}

	if(TinyDDS_IsCLUT(ctx->format)) {
		// for now don't ask to generate mipmaps for cluts
		if(ctx->header.mipMapCount == 0) ctx->header.mipMapCount = 1;

		size_t const clutSize = 256 * sizeof(uint32_t);

		ctx->clut = (uint32_t*) ctx->callbacks.allocFn(ctx->user, clutSize);

		if( ctx->callbacks.readFn(ctx->user, (void*)ctx->clut, clutSize) != clutSize) {
			ctx->callbacks.errorFn(ctx->user, "Could not read DDS CLUT");
			return false;
		}
	}

	ctx->firstImagePos = ctx->callbacks.tellFn(ctx->user);
	ctx->headerValid = true;
	return true;
}

bool TinyDDS_IsCubemap(TinyDDS_ContextHandle handle) {
	TinyDDS_Context *ctx = (TinyDDS_Context *) handle;
	if (ctx == NULL)
		return false;
	if (!ctx->headerValid) {
		ctx->callbacks.errorFn(ctx->user, "Header data hasn't been read yet or its invalid");
		return false;
	}

	return (ctx->header.caps2 & TINYDDS_DDSCAPS2_CUBEMAP);
}

bool TinyDDS_Dimensions(TinyDDS_ContextHandle handle,
												uint32_t *width,
												uint32_t *height,
												uint32_t *depth,
												uint32_t *slices) {
	TinyDDS_Context *ctx = (TinyDDS_Context *) handle;
	if (ctx == NULL)
		return false;
	if (!ctx->headerValid) {
		ctx->callbacks.errorFn(ctx->user, "Header data hasn't been read yet or its invalid");
		return false;
	}

	if (width)
		*width = ctx->header.width;
	if (height)
		*height = ctx->header.height;
	if (depth)
		*depth = ctx->header.depth;
	if (slices)
		*slices = ctx->headerDx10.arraySize;
	return true;
}

uint32_t TinyDDS_Width(TinyDDS_ContextHandle handle) {
	TinyDDS_Context *ctx = (TinyDDS_Context *) handle;
	if (ctx == NULL)
		return 0;
	if (!ctx->headerValid) {
		ctx->callbacks.errorFn(ctx->user, "Header data hasn't been read yet or its invalid");
		return 0;
	}
	return ctx->header.width;
}

uint32_t TinyDDS_Height(TinyDDS_ContextHandle handle) {
	TinyDDS_Context *ctx = (TinyDDS_Context *) handle;
	if (ctx == NULL)
		return 0;
	if (!ctx->headerValid) {
		ctx->callbacks.errorFn(ctx->user, "Header data hasn't been read yet or its invalid");
		return 0;
	}
	return ctx->header.height;
}

uint32_t TinyDDS_Depth(TinyDDS_ContextHandle handle) {
	TinyDDS_Context *ctx = (TinyDDS_Context *) handle;
	if (ctx == NULL)
		return 0;
	if (!ctx->headerValid) {
		ctx->callbacks.errorFn(ctx->user, "Header data hasn't been read yet or its invalid");
		return 0;
	}

	return ctx->header.depth;
}

uint32_t TinyDDS_ArraySlices(TinyDDS_ContextHandle handle) {
	TinyDDS_Context *ctx = (TinyDDS_Context *) handle;
	if (ctx == NULL)
		return 0;
	if (!ctx->headerValid) {
		ctx->callbacks.errorFn(ctx->user, "Header data hasn't been read yet or its invalid");
		return 0;
	}

	return ctx->headerDx10.arraySize;
}

bool TinyDDS_Is1D(TinyDDS_ContextHandle handle) {
	TinyDDS_Context *ctx = (TinyDDS_Context *) handle;
	if (ctx == NULL)
		return false;
	if (!ctx->headerValid) {
		ctx->callbacks.errorFn(ctx->user, "Header data hasn't been read yet or its invalid");
		return false;
	}
	return (ctx->header.height <= 1 && ctx->header.depth <= 1);
}
bool TinyDDS_Is2D(TinyDDS_ContextHandle handle) {
	TinyDDS_Context *ctx = (TinyDDS_Context *) handle;
	if (ctx == NULL)
		return false;
	if (!ctx->headerValid) {
		ctx->callbacks.errorFn(ctx->user, "Header data hasn't been read yet or its invalid");
		return false;
	}
	return (ctx->header.height > 1 && ctx->header.depth <= 1);
}
bool TinyDDS_Is3D(TinyDDS_ContextHandle handle) {
	TinyDDS_Context *ctx = (TinyDDS_Context *) handle;
	if (ctx == NULL)
		return false;
	if (!ctx->headerValid) {
		ctx->callbacks.errorFn(ctx->user, "Header data hasn't been read yet or its invalid");
		return false;
	}

	return (ctx->header.height > 1 && ctx->header.depth > 1);
}

bool TinyDDS_IsArray(TinyDDS_ContextHandle handle) {
	TinyDDS_Context *ctx = (TinyDDS_Context *) handle;
	if (ctx == NULL)
		return false;
	if (!ctx->headerValid) {
		ctx->callbacks.errorFn(ctx->user, "Header data hasn't been read yet or its invalid");
		return false;
	}

	return (ctx->headerDx10.arraySize >= 1);
}

uint32_t TinyDDS_NumberOfMipmaps(TinyDDS_ContextHandle handle) {
	TinyDDS_Context *ctx = (TinyDDS_Context *) handle;
	if (ctx == NULL)
		return 0;
	if (!ctx->headerValid) {
		ctx->callbacks.errorFn(ctx->user, "Header data hasn't been read yet or its invalid");
		return 0;
	}

	return ctx->header.mipMapCount ? ctx->header.mipMapCount : 1;
}

bool TinyDDS_NeedsGenerationOfMipmaps(TinyDDS_ContextHandle handle) {
	TinyDDS_Context *ctx = (TinyDDS_Context *) handle;
	if (ctx == NULL)
		return false;
	if (!ctx->headerValid) {
		ctx->callbacks.errorFn(ctx->user, "Header data hasn't been read yet or its invalid");
		return false;
	}

	return ctx->header.mipMapCount == 0;
}

bool TinyDDS_NeedsEndianCorrecting(TinyDDS_ContextHandle handle) {
	// TODO should return true if this file is compiled on big endian machines
	BASISU_NOTE_UNUSED(handle);
	return false;
}

uint32_t TinyDDS_FaceSize(TinyDDS_ContextHandle handle, uint32_t mipmaplevel) {
	TinyDDS_Context *ctx = (TinyDDS_Context *) handle;
	if (ctx == NULL)
		return 0;

	if (!ctx->headerValid) {
		ctx->callbacks.errorFn(ctx->user, "Header data hasn't been read yet or its invalid");
		return 0;
	}
	uint32_t w = TinyDDS_MipMapReduce(ctx->header.width, mipmaplevel);
	uint32_t h = TinyDDS_MipMapReduce(ctx->header.height, mipmaplevel);
	uint32_t d = TinyDDS_MipMapReduce(ctx->header.depth, mipmaplevel);
	uint32_t s = ctx->headerDx10.arraySize ? ctx->headerDx10.arraySize : 1;

	if(d > 1 && s > 1) {
		ctx->callbacks.errorFn(ctx->user, "Volume textures can't have array slices or be cubemap");
		return 0;
	}

	if (TinyDDS_IsCompressed(ctx->format)) {
		// padd to block boundaries
		w = (w + 3) / 4;
		h = (h + 3) / 4;
	}
	// 1 bit special case
	if(ctx->format == TDDS_R1_UNORM) {
		w = (w + 7) / 8;
	}

	uint32_t const formatSize = TinyDDS_FormatSize(ctx->format);
	return w * h * d * s * formatSize;
}

uint32_t TinyDDS_ImageSize(TinyDDS_ContextHandle handle, uint32_t mipmaplevel) {
	TinyDDS_Context *ctx = (TinyDDS_Context *) handle;
	if (ctx == NULL)
		return 0;

	if (!ctx->headerValid) {
		ctx->callbacks.errorFn(ctx->user, "Header data hasn't been read yet or its invalid");
		return 0;
	}

	if(	ctx->header.caps2 & TINYDDS_DDSCAPS2_CUBEMAP ||
			ctx->headerDx10.miscFlag & TINYDDS_D3D10_RESOURCE_MISC_TEXTURECUBE ) {
		return TinyDDS_FaceSize(handle, mipmaplevel) * 6;
	} else {
		return TinyDDS_FaceSize(handle, mipmaplevel);
	}
}

void const *TinyDDS_ImageRawData(TinyDDS_ContextHandle handle, uint32_t mipmaplevel) {
	TinyDDS_Context *ctx = (TinyDDS_Context *) handle;
	if (ctx == NULL)
		return NULL;

	if (!ctx->headerValid) {
		ctx->callbacks.errorFn(ctx->user, "Header data hasn't been read yet or its invalid");
		return NULL;
	}

	if (mipmaplevel >= (ctx->header.mipMapCount ? ctx->header.mipMapCount : 1) ) {
		ctx->callbacks.errorFn(ctx->user, "Invalid mipmap level");
		return NULL;
	}

	if (mipmaplevel >= TINYDDS_MAX_MIPMAPLEVELS) {
		ctx->callbacks.errorFn(ctx->user, "Invalid mipmap level");
		return NULL;
	}

	if (ctx->mipmaps[mipmaplevel] != NULL)
		return ctx->mipmaps[mipmaplevel];

	if(	ctx->header.caps2 & TINYDDS_DDSCAPS2_CUBEMAP ||
			ctx->headerDx10.miscFlag & TINYDDS_D3D10_RESOURCE_MISC_TEXTURECUBE ) {

		uint64_t offset = 0;
		for(uint32_t i=0;i < mipmaplevel;++i) {
			offset += TinyDDS_FaceSize(handle, i);
		}

		uint32_t mipMapCount = ctx->header.mipMapCount;
		if(mipMapCount == 0) mipMapCount = 1;

		// at least one cubemap generater has mipMapCount wrong which causes
		// image artifacts :(
		uint64_t nextFaceOffset = 0;
		for(uint32_t i = 0;i < mipMapCount;++i) {
			nextFaceOffset += TinyDDS_FaceSize(handle, i);
		}

		size_t const faceSize = TinyDDS_FaceSize(handle, mipmaplevel);
		ctx->mipmaps[mipmaplevel] = (uint8_t const *) ctx->callbacks.allocFn(ctx->user, faceSize * 6);
		if(!ctx->mipmaps[mipmaplevel]) return NULL;

		uint8_t *dstPtr = (uint8_t*)ctx->mipmaps[mipmaplevel];
		for (uint32_t i = 0u;i < 6;++i) {
			ctx->callbacks.seekFn(ctx->user, offset + ctx->firstImagePos);
			size_t read = ctx->callbacks.readFn(ctx->user, (void *) dstPtr, faceSize);
			if(read != faceSize) {
				ctx->callbacks.freeFn(ctx->user, (void*)&ctx->mipmaps[mipmaplevel]);
				return NULL;
			}
			dstPtr += faceSize;
			offset += nextFaceOffset;
		}
		return ctx->mipmaps[mipmaplevel];
	}

	uint64_t offset = 0;
	for(uint32_t i=0;i < mipmaplevel;++i) {
		offset += TinyDDS_ImageSize(handle, i);
	}

	uint32_t size = TinyDDS_ImageSize(handle, mipmaplevel);
	if (size == 0)
		return NULL;

	ctx->callbacks.seekFn(ctx->user, offset + ctx->firstImagePos);

	ctx->mipmaps[mipmaplevel] = (uint8_t const *) ctx->callbacks.allocFn(ctx->user, size);
	if (!ctx->mipmaps[mipmaplevel]) return NULL;
	size_t read = ctx->callbacks.readFn(ctx->user, (void *) ctx->mipmaps[mipmaplevel], size);
	if(read != size) {
		ctx->callbacks.freeFn(ctx->user, (void*)&ctx->mipmaps[mipmaplevel]);
		return NULL;
	}

	return ctx->mipmaps[mipmaplevel];
}

TinyDDS_Format TinyDDS_GetFormat(TinyDDS_ContextHandle handle) {
	TinyDDS_Context *ctx = (TinyDDS_Context *) handle;
	if (ctx == NULL)
		return TDDS_UNDEFINED;

	if (!ctx->headerValid) {
		ctx->callbacks.errorFn(ctx->user, "Header data hasn't been read yet or its invalid");
		return TDDS_UNDEFINED;
	}
	return ctx->format;
}

#define TDDS_EF(bits, rm, gm, bm, am, fl) \
		header->formatRGBBitCount = bits; \
		header->formatRBitMask = rm; \
		header->formatGBitMask = gm; \
		header->formatBBitMask = bm; \
		header->formatABitMask = am; \
		header->formatFlags = fl; \
		header->formatFourCC = 0; \
		return true;

#define TDDS_EF_RGB(bits, rm, gm, bm) TDDS_EF(bits, rm, gm, bm, 0, TINYDDS_DDPF_RGB )
#define TDDS_EF_RGBA(bits, rm, gm, bm, am) TDDS_EF(bits, rm, gm, bm, am, TINYDDS_DDPF_RGB | TINYDDS_DDPF_ALPHAPIXELS)
#define TDDS_EF_ALPHA(bits, am) TDDS_EF(bits, 0, 0, 0, am, TINYDDS_DDPF_ALPHA)

#define TDDS_EF_BUMP_RG(bits, rm, gm) TDDS_EF(bits, rm, gm, 0, 0, TINYDDS_DDPF_BUMPDUDV)
#define TDDS_EF_BUMP_RGB(bits, rm, gm, bm) TDDS_EF(bits, rm, gm, bm, 0, TINYDDS_DDPF_BUMPLUMINANCE)
#define TDDS_EF_BUMP_RGBA(bits, rm, gm, bm, am) TDDS_EF(bits, rm, gm, bm, am, TINYDDS_DDPF_BUMPLUMINANCE | TINYDDS_DDPF_ALPHAPIXELS)

static bool TinyDDS_EncodeFormat(TinyDDS_Format fmt, TinyDDS_Header* header, TinyDDS_HeaderDX10* headerDx10) {
	// lets start with the easy part. if its real DXGI_FORMAT we can just fill in the Dx10 part
	if(fmt < TDDS_SYNTHESISED_DXGIFORMATS) {
		headerDx10->DXGIFormat = (TinyImageFormat_DXGI_FORMAT)fmt;
		header->formatFourCC = TINYDDS_MAKE_RIFFCODE('D','X','1','0');
		header->formatFlags = TINYDDS_DDPF_FOURCC;
	} else {
		headerDx10->DXGIFormat = TIF_DXGI_FORMAT_UNKNOWN;
	}
	// now lets try synthesising if possible
	// if we can reset the DX10 fourCC but leave the format in place
	// that way if we have slices which can only be DXGI_FORMAT we can use it
	switch(fmt) {
	case TDDS_UNDEFINED: break;

	case TDDS_R1_UNORM: TDDS_EF_RGB(1, 0x1, 0, 0)
	case TDDS_R4G4_UNORM: TDDS_EF_RGB(8, 0x0F, 0xF0, 0)
	case TDDS_G4R4_UNORM: TDDS_EF_RGB(8, 0xF0, 0x0F, 0)
	case TDDS_B2G3R3_UNORM: TDDS_EF_RGB(8, 0x3, 0x7, 0x7 )
	case TDDS_R8_UNORM: TDDS_EF_RGB(8, 0xFF, 0, 0 );
	case TDDS_A8_UNORM: TDDS_EF_ALPHA( 8, 0xFF);

	case TDDS_R16_UNORM:TDDS_EF_RGB( 16,0x0000FFFF, 0, 0)
	case TDDS_A4B4G4R4_UNORM:
		TDDS_EF_RGBA(16, 0xF000, 0x0F00, 0x00F0, 0x000F);
	case TDDS_X4B4G4R4_UNORM:
		TDDS_EF_RGBA(16, 0xF000, 0x0F00, 0x00F0, 0x0000);
	case TDDS_B4G4R4A4_UNORM:
		TDDS_EF_RGBA(16, 0x0F00, 0x00F0, 0x000F, 0xF000);
	case TDDS_B4G4R4X4_UNORM:
		TDDS_EF_RGBA(16, 0x0F00, 0x00F0, 0x000F, 0x0000);
	case TDDS_A4R4G4B4_UNORM:
		TDDS_EF_RGBA(16, 0x00F0, 0x0F00, 0xF000, 0x000F);
	case TDDS_X4R4G4B4_UNORM:
		TDDS_EF_RGBA(16, 0x00F0, 0x0F00, 0xF000, 0x0000);
	case TDDS_R4G4B4A4_UNORM:
		TDDS_EF_RGBA(16, 0x000F, 0x00F0, 0x0F00, 0xF000);
	case TDDS_R4G4B4X4_UNORM:
		TDDS_EF_RGBA(16, 0x000F, 0x00F0, 0x0F00, 0x0000);

	case TDDS_B5G5R5A1_UNORM:
		TDDS_EF_RGBA(16, 0x7C00, 0x03E0, 0x001F, 0x8000);
	case TDDS_B5G5R5X1_UNORM:
		TDDS_EF_RGBA(16, 0x7C00, 0x03E0, 0x001F, 0x0000);

	case TDDS_R5G5B5A1_UNORM:
	TDDS_EF_RGBA(16, 0x001F, 0x03E0, 0x7C00, 0x8000);
	case TDDS_R5G5B5X1_UNORM:
	TDDS_EF_RGBA(16, 0x001F, 0x03E0, 0x7C00, 0x0000);

	case TDDS_A1R5G5B5_UNORM:
		TDDS_EF_RGBA(16, 0x003E, 0x07C0, 0xF800, 0x0001);
	case TDDS_X1R5G5B5_UNORM:
		TDDS_EF_RGBA(16, 0x003E, 0x07C0, 0xF800, 0x0000);
	case TDDS_A1B5G5R5_UNORM:
		TDDS_EF_RGBA(16, 0xF800, 0x07C0, 0x003E, 0x0001);
	case TDDS_X1B5G5R5_UNORM:
		TDDS_EF_RGBA(16, 0xF800, 0x07C0, 0x003E, 0x0000);

	case TDDS_B5G6R5_UNORM:
		TDDS_EF_RGB(16, 0xF800, 0x07E0, 0x001F);
	case TDDS_R5G6B5_UNORM:
		TDDS_EF_RGB(16, 0x001F, 0x07E0, 0xF800);

	case TDDS_R8G8_UNORM:
		TDDS_EF_RGB(16, 0x00FF, 0xFF00, 0);
	case TDDS_G8R8_UNORM:
		TDDS_EF_RGB(16, 0xFF00, 0x00FF, 0);
	case TDDS_G8R8_SNORM:
		TDDS_EF_BUMP_RG(16, 0xFF00, 0x00FF);

	case TDDS_B2G3R3A8_UNORM: TDDS_EF_RGBA(8, 0x3, 0x7, 0x7, 0xFF00 )

	case TDDS_R8G8B8_UNORM:
	TDDS_EF_RGB( 24,0x000000FF, 0x0000FF00, 0x00FF0000)
	case TDDS_B8G8R8_UNORM:
	TDDS_EF_RGB( 24,0x00FF0000, 0x0000FF00, 0x000000FF)

	case TDDS_R8G8B8A8_UNORM:
	TDDS_EF_RGBA( 32,0x000000FF, 0x0000FF00, 0x00FF0000, 0xFF000000)
	case TDDS_R8G8B8X8_UNORM:
	TDDS_EF_RGBA( 32,0x000000FF, 0x0000FF00, 0x00FF0000, 0x00000000)
	case TDDS_B8G8R8A8_UNORM:
	TDDS_EF_RGBA( 32,0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000)
	case TDDS_B8G8R8X8_UNORM:
	TDDS_EF_RGBA( 32,0x00FF0000, 0x0000FF00, 0x000000FF, 0x00000000)
	case TDDS_A8B8G8R8_UNORM:
	TDDS_EF_RGBA( 32,0xFF000000, 0x00FF0000, 0x0000FF00, 0x000000FF)
	case TDDS_X8B8G8R8_UNORM:
	TDDS_EF_RGBA( 32,0xFF000000, 0x00FF0000, 0x0000FF00, 0x00000000)
	case TDDS_A8R8G8B8_UNORM:
	TDDS_EF_RGBA( 32,0x0000FF00, 0x00FF0000, 0xFF000000, 0x000000FF)
	case TDDS_X8R8G8B8_UNORM:
	TDDS_EF_RGBA( 32,0x0000FF00, 0x00FF0000, 0xFF000000, 0x00000000)

	/* A2R10G10B10 is broken via the traditional DDS descriptions, so we
	 * always use the Dx10 header for those
	case TDDS_R10G10B10A2_UNORM:
	TDDS_EF_RGBA( 32,0x3FF00000, 0x000FFC00, 0x000003FF, 0xC0000000)
	case TDDS_A2B10G10R10_UNORM:
	TDDS_EF_RGBA( 32,0xFFC00000, 0x003FF000, 0x00000FFC, 0x00000003)
	case TDDS_A2R10G10B10_UNORM:
	TDDS_EF_RGBA( 32,0x00000FFC, 0x003FF000, 0xFFC00000, 0x00000003)
	case TDDS_B10G10R10A2_UNORM:
	TDDS_EF_RGBA( 32,0x3FF00000, 0x000FFC00, 0x000003FF, 0xC0000000)
	*/
	case TDDS_R10G10B10A2_UNORM:
	case TDDS_B10G10R10A2_UNORM:
	case TDDS_A2B10G10R10_UNORM:
	case TDDS_A2R10G10B10_UNORM:
	case TDDS_R10G10B10A2_SNORM:
	case TDDS_B10G10R10A2_SNORM:
	case TDDS_A2B10G10R10_SNORM:
	case TDDS_A2R10G10B10_SNORM:
		break;

	case TDDS_R16G16_UNORM: TDDS_EF_RGB( 32,0x0000FFFF, 0xFFFF0000, 0)
	case TDDS_G16R16_UNORM: TDDS_EF_RGB( 32,0xFFFF0000, 0x0000FFFF, 0)

	case TDDS_BC1_RGBA_UNORM_BLOCK:
		header->formatFourCC = TINYDDS_MAKE_RIFFCODE('D','X','T','1');
		header->formatFlags = TINYDDS_DDPF_FOURCC;
		return true;
	case TDDS_BC2_UNORM_BLOCK:
		header->formatFourCC = TINYDDS_MAKE_RIFFCODE('D','X','T','3');
		header->formatFlags = TINYDDS_DDPF_FOURCC;
		return true;
	case TDDS_BC3_UNORM_BLOCK:
		header->formatFourCC = TINYDDS_MAKE_RIFFCODE('D','X','T','5');
		header->formatFlags = TINYDDS_DDPF_FOURCC;
		return true;
	case TDDS_BC4_UNORM_BLOCK:
		header->formatFourCC = TINYDDS_MAKE_RIFFCODE('A','T','I','1');
		header->formatFlags = TINYDDS_DDPF_FOURCC;
		return true;
	case TDDS_BC5_UNORM_BLOCK:
		header->formatFourCC = TINYDDS_MAKE_RIFFCODE('A','T','I','2');
		header->formatFlags = TINYDDS_DDPF_FOURCC;
		return true;


	case TDDS_R8_SNORM:
	case TDDS_R8G8_SNORM:
	case TDDS_R8G8B8A8_SNORM:
	case TDDS_R16_SNORM:
	case TDDS_R16G16_SNORM:
	case TDDS_A8B8G8R8_SNORM:
	case TDDS_B8G8R8A8_SNORM:
	case TDDS_G16R16_SNORM:

	case TDDS_R8_UINT:
	case TDDS_R8_SINT:
	case TDDS_R8G8_UINT:
	case TDDS_R8G8_SINT:
	case TDDS_R8G8B8A8_UINT:
	case TDDS_R8G8B8A8_SINT:
	case TDDS_R8G8B8A8_SRGB:
	case TDDS_B8G8R8A8_SRGB:
	case TDDS_R9G9B9E5_UFLOAT:
	case TDDS_R10G10B10A2_UINT:
	case TDDS_R11G11B10_UFLOAT:
	case TDDS_R16_UINT:
	case TDDS_R16_SINT:
	case TDDS_R16_SFLOAT:
	case TDDS_R16G16_UINT:
	case TDDS_R16G16_SINT:
	case TDDS_R16G16_SFLOAT:
	case TDDS_R16G16B16A16_UNORM:
	case TDDS_R16G16B16A16_SNORM:
	case TDDS_R16G16B16A16_UINT:
	case TDDS_R16G16B16A16_SINT:
	case TDDS_R16G16B16A16_SFLOAT:
	case TDDS_R32_UINT:
	case TDDS_R32_SINT:
	case TDDS_R32_SFLOAT:
	case TDDS_R32G32_UINT:
	case TDDS_R32G32_SINT:
	case TDDS_R32G32_SFLOAT:
	case TDDS_R32G32B32_UINT:
	case TDDS_R32G32B32_SINT:
	case TDDS_R32G32B32_SFLOAT:
	case TDDS_R32G32B32A32_UINT:
	case TDDS_R32G32B32A32_SINT:
	case TDDS_R32G32B32A32_SFLOAT:
	case TDDS_BC1_RGBA_SRGB_BLOCK:
	case TDDS_BC2_SRGB_BLOCK:
	case TDDS_BC3_SRGB_BLOCK:
	case TDDS_BC4_SNORM_BLOCK:
	case TDDS_BC5_SNORM_BLOCK:
	case TDDS_BC6H_UFLOAT_BLOCK:
	case TDDS_BC6H_SFLOAT_BLOCK:
	case TDDS_BC7_UNORM_BLOCK:
	case TDDS_BC7_SRGB_BLOCK:
	case TDDS_AYUV:
	case TDDS_Y410:
	case TDDS_Y416:
	case TDDS_NV12:
	case TDDS_P010:
	case TDDS_P016:
	case TDDS_420_OPAQUE:
	case TDDS_YUY2:
	case TDDS_Y210:
	case TDDS_Y216:
	case TDDS_NV11:
	case TDDS_AI44:
	case TDDS_IA44:
	case TDDS_P8:
	case TDDS_A8P8:
	case TDDS_R10G10B10_7E3_A2_FLOAT:
	case TDDS_R10G10B10_6E4_A2_FLOAT:
	case TDDS_D16_UNORM_S8_UINT:
	case TDDS_R16_UNORM_X8_TYPELESS:
	case TDDS_X16_TYPELESS_G8_UINT:
	case TDDS_P208:
	case TDDS_V208:
	case TDDS_V408:
	case TDDS_R10G10B10_SNORM_A2_UNORM:
		break;

	}
	// these formats can probably be done via dx10 header so check
	if(headerDx10->DXGIFormat == TIF_DXGI_FORMAT_UNKNOWN) return false;
	else return true;
}

#undef TDDS_EF
#undef TDDS_EF_RGB
#undef TDDS_EF_RGBA
#undef TDDS_EF_ALPHA

bool TinyDDS_WriteImage(TinyDDS_WriteCallbacks const *callbacks,
												void *user,
												uint32_t width,
												uint32_t height,
												uint32_t depth,	 // 3D texture depth
												uint32_t slices, // Array slices
												uint32_t mipmaplevels,
												TinyDDS_Format format,
												bool cubemap,
												bool preferDx10Format,
												uint32_t const *mipmapsizes,
												void const **mipmaps) {
	TinyDDS_Header header;
	TinyDDS_HeaderDX10 headerDX10;
	memset(&header, 0, sizeof(header));
	memset(&headerDX10, 0, sizeof(headerDX10));

	header.magic = TINYDDS_MAKE_RIFFCODE('D', 'D', 'S', ' ');
	header.size = 124;
	header.formatSize = 32;

	header.width = width;
	header.height = height;
	header.depth = (depth > 1) ? depth : 0;
	header.mipMapCount = mipmaplevels;

	if(!TinyDDS_EncodeFormat(format, &header, &headerDX10)) return false;

	// do we have to force dx10 (for slices)
	if (slices > 1) {
		if(headerDX10.DXGIFormat == TIF_DXGI_FORMAT_UNKNOWN) {
			// DDS doesn't support slices for formats that aren't DXGI compatible
			return false;
		}
		header.formatFlags = TINYDDS_DDPF_FOURCC;
		header.formatFourCC = TINYDDS_MAKE_RIFFCODE('D','X','1','0');
		headerDX10.arraySize = slices;
	}
	header.flags = TINYDDS_DDSD_CAPS | TINYDDS_DDSD_PIXELFORMAT | TINYDDS_DDSD_MIPMAPCOUNT;
	header.caps1 = TINYDDS_DDSCAPS_TEXTURE | TINYDDS_DDSCAPS_COMPLEX | TINYDDS_DDSCAPS_MIPMAP;

	if(depth > 1) {
		headerDX10.resourceDimension = TINYDDS_D3D10_RESOURCE_DIMENSION_TEXTURE3D;
		header.flags |= TINYDDS_DDSD_DEPTH;
		header.caps2 |= TINYDDS_DDSCAPS2_VOLUME;
	}
	else if(height > 1) {
		headerDX10.resourceDimension = TINYDDS_D3D10_RESOURCE_DIMENSION_TEXTURE2D;
		header.flags |= TINYDDS_DDSD_HEIGHT;
	}
	else if(width > 1) {
		headerDX10.resourceDimension = TINYDDS_D3D10_RESOURCE_DIMENSION_TEXTURE1D;
		header.flags |= TINYDDS_DDSD_WIDTH;
	}
	if(cubemap) {
		headerDX10.miscFlag |= TINYDDS_D3D10_RESOURCE_MISC_TEXTURECUBE;
		header.caps2 |= TINYDDS_DDSCAPS2_CUBEMAP | TINYDDS_DDSCAPS2_CUBEMAP_ALL;
	}

	// unclear whether we need to save this or exactly what it should be...
	header.pitchOrLinearSize = 0;
	if(preferDx10Format && headerDX10.DXGIFormat != TIF_DXGI_FORMAT_UNKNOWN) {
		header.formatFlags = TINYDDS_DDPF_FOURCC;
		header.formatFourCC = TINYDDS_MAKE_RIFFCODE('D','X','1','0');
	}

	// now write
	callbacks->write(user, &header, sizeof(TinyDDS_Header));
	if(header.formatFlags & TINYDDS_DDPF_FOURCC &&
			header.formatFourCC == TINYDDS_MAKE_RIFFCODE('D','X','1','0')) {
		callbacks->write(user, &headerDX10, sizeof(TinyDDS_HeaderDX10));
	}

	// rg 8/27/2024: The original tinydds.h code is wrong for mipmapped cubemaps.
	// I'm going to work around this by having the caller compose the top mip data correctly.
	// https://learn.microsoft.com/en-us/windows/win32/direct3ddds/dds-file-layout-for-cubic-environment-maps
	for (uint32_t mipMapLevel = 0; mipMapLevel < header.mipMapCount; mipMapLevel++) 
	{
		// rg: Adding this check, in case the caller wants to compose all the data themselves.
		if (mipmapsizes[mipMapLevel])
		{
			callbacks->write(user, mipmaps[mipMapLevel], mipmapsizes[mipMapLevel]);
		}
	}
	return true;
}

#endif

#ifdef __cplusplus
};
#endif

#endif // end header
/*
MIT License

Copyright (c) 2019 DeanoC

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
