//----------------------------------------------------------------------------- // // ImageLib Sources // Copyright (C) 2000-2016 by Denton Woods // Last modified: 05/15/2016 // // Filename: src-IL/src/il_ktx.cpp // // Description: Reads a Khronos Texture .ktx file // //----------------------------------------------------------------------------- #include "il_internal.h" #ifndef IL_NO_KTX #include "il_bits.h" #include "rg_etc1.h" ILboolean iIsValidKtx(void); ILboolean iLoadKtxInternal(); ILboolean iKtxReadMipmaps(ILboolean Compressed, ILuint NumMips, ILenum Origin); ILboolean iKtxKeyValueData(ILuint bytesOfKeyValueData, ILenum &Origin); #ifdef _MSC_VER #pragma pack(push, packed_struct, 1) #endif typedef struct KTX_HEAD { ILubyte identifier[12]; ILuint endianness; ILuint glType; ILuint glTypeSize; ILuint glFormat; ILuint glInternalFormat; ILuint glBaseInternalFormat; ILuint pixelWidth; ILuint pixelHeight; ILuint pixelDepth; ILuint numberOfArrayElements; ILuint numberOfFaces; ILuint numberOfMipmapLevels; ILuint bytesOfKeyValueData; } IL_PACKSTRUCT KTX_HEAD; #ifdef _MSC_VER #pragma pack(pop, packed_struct) #endif // From GL/GL.h #define I_GL_BYTE 0x1400 #define I_GL_UNSIGNED_BYTE 0x1401 #define I_GL_SHORT 0x1402 #define I_GL_UNSIGNED_SHORT 0x1403 #define I_GL_INT 0x1404 #define I_GL_UNSIGNED_INT 0x1405 #define I_GL_FLOAT 0x1406 //#define I_GL_2_BYTES 0x1407 //#define I_GL_3_BYTES 0x1408 //#define I_GL_4_BYTES 0x1409 #define I_GL_DOUBLE 0x140A #define I_GL_HALF 0x140B #define I_GL_ALPHA 0x1906 #define I_GL_RGB 0x1907 #define I_GL_RGBA 0x1908 #define I_GL_LUMINANCE 0x1909 #define I_GL_LUMINANCE_ALPHA 0x190A // From gl2ext.h #define I_GL_ETC1_RGB8_OES 0x8D64 // From https://www.opengl.org/registry/specs/ARB/ES3_compatibility.txt #define I_GL_COMPRESSED_RGB8_ETC2 0x9274 #define I_GL_COMPRESSED_SRGB8_ETC2 0x9275 #define I_GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2 0x9276 #define I_GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2 0x9277 #define I_GL_COMPRESSED_RGBA8_ETC2_EAC 0x9278 #define I_GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC 0x9279 #define I_GL_COMPRESSED_R11_EAC 0x9270 #define I_GL_COMPRESSED_SIGNED_R11_EAC 0x9271 #define I_GL_COMPRESSED_RG11_EAC 0x9272 #define I_GL_COMPRESSED_SIGNED_RG11_EAC 0x9273 ILboolean ilIsValidKtx(ILconst_string FileName) { ILHANDLE KtxFile; ILboolean bKtx = IL_FALSE; if (!iCheckExtension(FileName, IL_TEXT("ktx"))) { ilSetError(IL_INVALID_EXTENSION); return bKtx; } KtxFile = iopenr(FileName); if (KtxFile == NULL) { ilSetError(IL_COULD_NOT_OPEN_FILE); return bKtx; } bKtx = ilIsValidKtxF(KtxFile); icloser(KtxFile); return bKtx; } ILboolean ilIsValidKtxF(ILHANDLE File) { ILuint FirstPos; ILboolean bRet; iSetInputFile(File); FirstPos = itell(); bRet = iIsValidKtx(); iseek(FirstPos, IL_SEEK_SET); return bRet; } ILboolean ilIsValidKtxL(const void *Lump, ILuint Size) { iSetInputLump(Lump, Size); return iIsValidKtx(); } ILboolean iIsValidKtx() { ILubyte Signature[8]; ILint Read; /*Read = iread(Signature, 1, 8); iseek(-Read, IL_SEEK_CUR);*/ return IL_FALSE; } //! Reads a .ktx file ILboolean ilLoadKtx(ILconst_string FileName) { ILHANDLE KtxFile; ILboolean bKtx = IL_FALSE; KtxFile = iopenr(FileName); if (KtxFile == NULL) { ilSetError(IL_COULD_NOT_OPEN_FILE); return bKtx; } bKtx = ilLoadKtxF(KtxFile); icloser(KtxFile); return bKtx; } //! Reads an already-opened .ktx file ILboolean ilLoadKtxF(ILHANDLE File) { ILuint FirstPos; ILboolean bRet; iSetInputFile(File); FirstPos = itell(); bRet = iLoadKtxInternal(); iseek(FirstPos, IL_SEEK_SET); return bRet; } //! Reads from a memory "lump" that contains a .ktx ILboolean ilLoadKtxL(const void *Lump, ILuint Size) { iSetInputLump(Lump, Size); return iLoadKtxInternal(); } // Note: .Ktx support has not been tested yet! // https://www.khronos.org/opengles/sdk/tools/KTX/file_format_spec/ ILboolean iLoadKtxInternal() { KTX_HEAD Header; ILuint imageSize; ILenum Format, Type, Origin; ILubyte Bpp, Bpc; ILboolean Compressed; char FileIdentifier[12] = { //0xAB, 0x4B, 0x54, 0x58, 0x20, 0x31, 0x31, 0xBB, 0x0D, 0x0A, 0x1A, 0x0A '«', 'K', 'T', 'X', ' ', '1', '1', '»', '\r', '\n', '\x1A', '\n' }; if (iCurImage == NULL) { ilSetError(IL_ILLEGAL_OPERATION); return IL_FALSE; } /*Header.Width = GetLittleShort(); Header.Height = GetLittleShort(); Header.Dummy = GetLittleInt();*/ if (iread(Header.identifier, 1, 12) != 12) return IL_FALSE; Header.endianness = GetLittleUInt(); Header.glType = GetLittleUInt(); Header.glTypeSize = GetLittleUInt(); Header.glFormat = GetLittleUInt(); Header.glInternalFormat = GetLittleUInt(); Header.glBaseInternalFormat = GetLittleUInt(); Header.pixelWidth = GetLittleUInt(); Header.pixelHeight = GetLittleUInt(); Header.pixelDepth = GetLittleUInt(); Header.numberOfArrayElements = GetLittleUInt(); Header.numberOfFaces = GetLittleUInt(); Header.numberOfMipmapLevels = GetLittleUInt(); Header.bytesOfKeyValueData = GetLittleUInt(); if (memcmp(Header.identifier, FileIdentifier, 12) || Header.endianness != 0x04030201) { ilSetError(IL_ILLEGAL_FILE_VALUE); return IL_FALSE; } if (Header.glTypeSize != 1) //@TODO: Cases when this is different? { ilSetError(IL_ILLEGAL_FILE_VALUE); return IL_FALSE; } //@TODO: Really needed? /*if (Header.glInternalFormat != Header.glFormat || Header.glBaseInternalFormat != Header.glFormat) { ilSetError(IL_ILLEGAL_FILE_VALUE); return IL_FALSE; }*/ //@TODO: Mipmaps, etc. if (Header.numberOfArrayElements != 0 || Header.numberOfFaces != 1 /*|| Header.numberOfMipmapLevels != 1*/) { ilSetError(IL_ILLEGAL_FILE_VALUE); return IL_FALSE; } if (!iKtxKeyValueData(Header.bytesOfKeyValueData, Origin)) return IL_FALSE; //@TODO: Additional formats if (Header.glFormat != 0) // Uncompressed formats { switch (Header.glFormat) { case I_GL_LUMINANCE: Bpp = 1; Format = IL_LUMINANCE; Compressed = IL_FALSE; break; case I_GL_LUMINANCE_ALPHA: Bpp = 2; Format = IL_LUMINANCE_ALPHA; Compressed = IL_FALSE; break; case I_GL_RGB: Bpp = 3; Format = IL_RGB; Compressed = IL_FALSE; break; case I_GL_RGBA: Bpp = 4; Format = IL_RGBA; Compressed = IL_FALSE; break; default: ilSetError(IL_ILLEGAL_FILE_VALUE); return IL_FALSE; } } else // Compressed formats - Note that type must be set here! { switch (Header.glInternalFormat) { case I_GL_ETC1_RGB8_OES: Bpp = 4; Format = IL_RGBA; Compressed = IL_TRUE; Type = IL_UNSIGNED_BYTE; break; default: ilSetError(IL_ILLEGAL_FILE_VALUE); return IL_FALSE; } } if (!Compressed) { //@TODO: Additional types switch (Header.glType) { case I_GL_UNSIGNED_BYTE: Bpc = 1; Type = IL_UNSIGNED_BYTE; break; case I_GL_HALF: Bpc = 2; Type = IL_HALF; break; //case I_GL_SHORT: default: ilSetError(IL_ILLEGAL_FILE_VALUE); return IL_FALSE; } if (!ilTexImage(Header.pixelWidth, Header.pixelHeight, 1, Bpp, Format, Type, NULL)) return IL_FALSE; if (!iKtxReadMipmaps(Compressed, Header.numberOfMipmapLevels, Origin)) return IL_FALSE; } else // Compressed formats require different handling { //@TODO: Add support for compressed mipmaps if (Header.numberOfMipmapLevels != 1) { ilSetError(IL_ILLEGAL_FILE_VALUE); return IL_FALSE; } imageSize = GetLittleUInt(); if (!ilTexImage(Header.pixelWidth, Header.pixelHeight, 1, Bpp, Format, Type, NULL)) return IL_FALSE; switch (Header.glInternalFormat) { case I_GL_ETC1_RGB8_OES: ILubyte *FullBuffer = (ILubyte*)ialloc(Header.pixelWidth * Header.pixelHeight / 2); ILuint UnpackBuff[16*4]; ILuint *Data = (ILuint*)iCurImage->Data; // Easier copies if (FullBuffer == NULL) return IL_FALSE; if (imageSize != Header.pixelWidth * Header.pixelHeight / 2) { ilSetError(IL_ILLEGAL_FILE_VALUE); return IL_FALSE; } if (iread(FullBuffer, 1, imageSize) != imageSize) return IL_FALSE; ILuint BlockPos = 0; for (ILuint y = 0; y < iCurImage->Height; y+=4) { for (ILuint x = 0; x < iCurImage->Width; x+=4) { ILuint ImagePos = y * iCurImage->Width + x; rg_etc1::unpack_etc1_block(&FullBuffer[BlockPos], (ILuint*)UnpackBuff, false); ILuint c = 0; for (ILuint y1 = 0; y1 < 4; y1++) { for (ILuint x1 = 0; x1 < 4; x1++) { Data[ImagePos + y1*iCurImage->Width + x1] = UnpackBuff[c++]; } } BlockPos += 8; } } ifree(FullBuffer); break; } iCurImage->Origin = Origin; //@TODO: Correct for all? } return ilFixImage(); } // There are multiple items that can be in the key values, but all we care about // right now is the orientation/origin data. ILboolean iKtxKeyValueData(ILuint bytesOfKeyValueData, ILenum &Origin) { ILubyte *KeyValueData; ILuint Pos = 0, Length; Origin = IL_ORIGIN_UPPER_LEFT; // KTX specs declare that this is the default if (bytesOfKeyValueData == 0) return IL_TRUE; KeyValueData = (ILubyte*)ialloc(bytesOfKeyValueData); if (KeyValueData == NULL) return IL_FALSE; if (iread(KeyValueData, 1, bytesOfKeyValueData) != bytesOfKeyValueData) return IL_FALSE; // Just a very rudimentary check on the origin values while (Pos < bytesOfKeyValueData) { Length = *((ILuint*)&KeyValueData[Pos]); if (Length >= 22) // Size needed for the KTXorientation key { if (!strncmp((const char*)&KeyValueData[Pos+4], "KTXorientation", 14)) { // Found the orientation key we need ILubyte TopDownOrient = KeyValueData[Pos+25]; if (TopDownOrient == 'd') Origin = IL_ORIGIN_UPPER_LEFT; else if (TopDownOrient == 'u') Origin = IL_ORIGIN_LOWER_LEFT; } } Pos += Length; if (Pos >= bytesOfKeyValueData) break; } ifree(KeyValueData); return IL_TRUE; } ILboolean iKtxReadMipmaps(ILboolean Compressed, ILuint NumMips, ILenum Origin) { ILimage *Image = iCurImage; ILuint Width = iCurImage->Width, Height = iCurImage->Height; ILuint imageSize, Padding, Pos; for (ILuint Mip = 0; Mip < NumMips; Mip++) { imageSize = GetLittleUInt(); Padding = 3 - ((Image->Bps + 3) % 4); if (imageSize != Image->SizeOfData + Padding*Image->Height) { ilSetError(IL_ILLEGAL_FILE_VALUE); goto mip_fail; } // Note: The KTX spec at https://www.khronos.org/opengles/sdk/tools/KTX/file_format_spec/ // seems to imply dword-alignment of the entire image, but this is per scanline. if (Image->Bps % 4 == 0) // Required to be dword-aligned { if (iread(Image->Data, 1, Image->SizeOfData) != Image->SizeOfData) return IL_FALSE; } else // Since not dword-aligned, have to read each line separately. { Pos = 0; for (ILuint h = 0; h < Height; h++) { if (iread(&Image->Data[Pos], 1, Image->Bps) != Image->Bps) return IL_FALSE; iseek(Padding, IL_SEEK_CUR); Pos += Image->Bps; } } Image->Origin = Origin; Width = Width / 2; Height = Height / 2; if (Mip < NumMips - 1) { Image->Mipmaps = ilNewImage(Width, Height, 1, Image->Bpp, Image->Bpc); if (Image->Mipmaps == NULL) goto mip_fail; Image->Mipmaps->Format = Image->Format; Image->Mipmaps->Type = Image->Type; Image = Image->Mipmaps; } } return IL_TRUE; mip_fail: Image = iCurImage; ILimage *StartImage = Image->Mipmaps, *TempImage; while (StartImage) { TempImage = StartImage; StartImage = StartImage->Mipmaps; ifree(TempImage); } Image->Mipmaps = NULL; return IL_FALSE; } #endif//IL_NO_KTX