Replace file accesses by memory accesses

Several functions used to load external files have been reviewed to just load the full file with LoadFileData() and load data from memory from there. This way all file access functionality is centralized in utils module.

Functions reviewed: LoadDDS(), LoadPKM(), LoadKTX(), LoadPVR(), LoadASTC(), SaveKTX()
This commit is contained in:
raysan5 2020-05-22 01:47:30 +02:00
parent a2955bc5b3
commit 0e56bc2929

View file

@ -3258,6 +3258,10 @@ static Image LoadAnimatedGIF(const char *fileName, int *frames, int **delays)
// Loading DDS image data (compressed or uncompressed) // Loading DDS image data (compressed or uncompressed)
static Image LoadDDS(const char *fileName) static Image LoadDDS(const char *fileName)
{ {
unsigned int fileSize = 0;
unsigned char *fileData = LoadFileData(fileName, &fileSize);
unsigned char *fileDataPtr = fileData;
// Required extension: // Required extension:
// GL_EXT_texture_compression_s3tc // GL_EXT_texture_compression_s3tc
@ -3303,18 +3307,11 @@ static Image LoadDDS(const char *fileName)
Image image = { 0 }; Image image = { 0 };
FILE *ddsFile = fopen(fileName, "rb"); if (fileDataPtr != NULL)
if (ddsFile == NULL)
{
TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to open DDS file", fileName);
}
else
{ {
// Verify the type of file // Verify the type of file
char ddsHeaderId[4] = { 0 }; unsigned char *ddsHeaderId = fileDataPtr;
fileDataPtr += 4;
fread(ddsHeaderId, 4, 1, ddsFile);
if ((ddsHeaderId[0] != 'D') || (ddsHeaderId[1] != 'D') || (ddsHeaderId[2] != 'S') || (ddsHeaderId[3] != ' ')) if ((ddsHeaderId[0] != 'D') || (ddsHeaderId[1] != 'D') || (ddsHeaderId[2] != 'S') || (ddsHeaderId[3] != ' '))
{ {
@ -3322,39 +3319,42 @@ static Image LoadDDS(const char *fileName)
} }
else else
{ {
DDSHeader ddsHeader = { 0 }; DDSHeader *ddsHeader = (DDSHeader *)fileDataPtr;
// Get the image header
fread(&ddsHeader, sizeof(DDSHeader), 1, ddsFile);
TRACELOGD("IMAGE: [%s] DDS file info:", fileName); TRACELOGD("IMAGE: [%s] DDS file info:", fileName);
TRACELOGD(" > Header size: %i", fileName, sizeof(DDSHeader)); TRACELOGD(" > Header size: %i", fileName, sizeof(DDSHeader));
TRACELOGD(" > Pixel format size: %i", fileName, ddsHeader.ddspf.size); TRACELOGD(" > Pixel format size: %i", fileName, ddsHeader->ddspf.size);
TRACELOGD(" > Pixel format flags: 0x%x", fileName, ddsHeader.ddspf.flags); TRACELOGD(" > Pixel format flags: 0x%x", fileName, ddsHeader->ddspf.flags);
TRACELOGD(" > File format: 0x%x", fileName, ddsHeader.ddspf.fourCC); TRACELOGD(" > File format: 0x%x", fileName, ddsHeader->ddspf.fourCC);
TRACELOGD(" > File bit count: 0x%x", fileName, ddsHeader.ddspf.rgbBitCount); TRACELOGD(" > File bit count: 0x%x", fileName, ddsHeader->ddspf.rgbBitCount);
fileDataPtr += sizeof(DDSHeader); // Skip header
image.width = ddsHeader.width; image.width = ddsHeader->width;
image.height = ddsHeader.height; image.height = ddsHeader->height;
if (ddsHeader.mipmapCount == 0) image.mipmaps = 1; // Parameter not used if (ddsHeader->mipmapCount == 0) image.mipmaps = 1; // Parameter not used
else image.mipmaps = ddsHeader.mipmapCount; else image.mipmaps = ddsHeader->mipmapCount;
if (ddsHeader.ddspf.rgbBitCount == 16) // 16bit mode, no compressed if (ddsHeader->ddspf.rgbBitCount == 16) // 16bit mode, no compressed
{ {
if (ddsHeader.ddspf.flags == 0x40) // no alpha channel if (ddsHeader->ddspf.flags == 0x40) // no alpha channel
{ {
image.data = (unsigned short *)RL_MALLOC(image.width*image.height*sizeof(unsigned short)); int dataSize = image.width*image.height*sizeof(unsigned short);
fread(image.data, image.width*image.height*sizeof(unsigned short), 1, ddsFile); image.data = (unsigned short *)RL_MALLOC(dataSize);
memcpy(image.data, fileDataPtr, dataSize);
image.format = UNCOMPRESSED_R5G6B5; image.format = UNCOMPRESSED_R5G6B5;
} }
else if (ddsHeader.ddspf.flags == 0x41) // with alpha channel else if (ddsHeader->ddspf.flags == 0x41) // with alpha channel
{ {
if (ddsHeader.ddspf.aBitMask == 0x8000) // 1bit alpha if (ddsHeader->ddspf.aBitMask == 0x8000) // 1bit alpha
{ {
image.data = (unsigned short *)RL_MALLOC(image.width*image.height*sizeof(unsigned short)); int dataSize = image.width*image.height*sizeof(unsigned short);
fread(image.data, image.width*image.height*sizeof(unsigned short), 1, ddsFile); image.data = (unsigned short *)RL_MALLOC(dataSize);
memcpy(image.data, fileDataPtr, dataSize);
unsigned char alpha = 0; unsigned char alpha = 0;
@ -3368,10 +3368,12 @@ static Image LoadDDS(const char *fileName)
image.format = UNCOMPRESSED_R5G5B5A1; image.format = UNCOMPRESSED_R5G5B5A1;
} }
else if (ddsHeader.ddspf.aBitMask == 0xf000) // 4bit alpha else if (ddsHeader->ddspf.aBitMask == 0xf000) // 4bit alpha
{ {
image.data = (unsigned short *)RL_MALLOC(image.width*image.height*sizeof(unsigned short)); int dataSize = image.width*image.height*sizeof(unsigned short);
fread(image.data, image.width*image.height*sizeof(unsigned short), 1, ddsFile); image.data = (unsigned short *)RL_MALLOC(dataSize);
memcpy(image.data, fileDataPtr, dataSize);
unsigned char alpha = 0; unsigned char alpha = 0;
@ -3387,18 +3389,21 @@ static Image LoadDDS(const char *fileName)
} }
} }
} }
else if (ddsHeader.ddspf.flags == 0x40 && ddsHeader.ddspf.rgbBitCount == 24) // DDS_RGB, no compressed else if (ddsHeader->ddspf.flags == 0x40 && ddsHeader->ddspf.rgbBitCount == 24) // DDS_RGB, no compressed
{ {
// NOTE: not sure if this case exists... int dataSize = image.width*image.height*3*sizeof(unsigned char);
image.data = (unsigned char *)RL_MALLOC(image.width*image.height*3*sizeof(unsigned char)); image.data = (unsigned short *)RL_MALLOC(dataSize);
fread(image.data, image.width*image.height*3, 1, ddsFile);
memcpy(image.data, fileDataPtr, dataSize);
image.format = UNCOMPRESSED_R8G8B8; image.format = UNCOMPRESSED_R8G8B8;
} }
else if (ddsHeader.ddspf.flags == 0x41 && ddsHeader.ddspf.rgbBitCount == 32) // DDS_RGBA, no compressed else if (ddsHeader->ddspf.flags == 0x41 && ddsHeader->ddspf.rgbBitCount == 32) // DDS_RGBA, no compressed
{ {
image.data = (unsigned char *)RL_MALLOC(image.width*image.height*4*sizeof(unsigned char)); int dataSize = image.width*image.height*4*sizeof(unsigned char);
fread(image.data, image.width*image.height*4, 1, ddsFile); image.data = (unsigned short *)RL_MALLOC(dataSize);
memcpy(image.data, fileDataPtr, dataSize);
unsigned char blue = 0; unsigned char blue = 0;
@ -3414,23 +3419,23 @@ static Image LoadDDS(const char *fileName)
image.format = UNCOMPRESSED_R8G8B8A8; image.format = UNCOMPRESSED_R8G8B8A8;
} }
else if (((ddsHeader.ddspf.flags == 0x04) || (ddsHeader.ddspf.flags == 0x05)) && (ddsHeader.ddspf.fourCC > 0)) // Compressed else if (((ddsHeader->ddspf.flags == 0x04) || (ddsHeader->ddspf.flags == 0x05)) && (ddsHeader->ddspf.fourCC > 0)) // Compressed
{ {
int size; // DDS image data size int dataSize = 0;
// Calculate data size, including all mipmaps // Calculate data size, including all mipmaps
if (ddsHeader.mipmapCount > 1) size = ddsHeader.pitchOrLinearSize*2; if (ddsHeader->mipmapCount > 1) dataSize = ddsHeader->pitchOrLinearSize*2;
else size = ddsHeader.pitchOrLinearSize; else dataSize = ddsHeader->pitchOrLinearSize;
image.data = (unsigned char *)RL_MALLOC(size*sizeof(unsigned char)); image.data = (unsigned char *)RL_MALLOC(dataSize*sizeof(unsigned char));
fread(image.data, size, 1, ddsFile); memcpy(image.data, fileDataPtr, dataSize);
switch (ddsHeader.ddspf.fourCC) switch (ddsHeader->ddspf.fourCC)
{ {
case FOURCC_DXT1: case FOURCC_DXT1:
{ {
if (ddsHeader.ddspf.flags == 0x04) image.format = COMPRESSED_DXT1_RGB; if (ddsHeader->ddspf.flags == 0x04) image.format = COMPRESSED_DXT1_RGB;
else image.format = COMPRESSED_DXT1_RGBA; else image.format = COMPRESSED_DXT1_RGBA;
} break; } break;
case FOURCC_DXT3: image.format = COMPRESSED_DXT3_RGBA; break; case FOURCC_DXT3: image.format = COMPRESSED_DXT3_RGBA; break;
@ -3440,7 +3445,7 @@ static Image LoadDDS(const char *fileName)
} }
} }
fclose(ddsFile); // Close file pointer free(fileData); // Free file data buffer
} }
return image; return image;
@ -3453,6 +3458,10 @@ static Image LoadDDS(const char *fileName)
// PKM is a much simpler file format used mainly to contain a single ETC1/ETC2 compressed image (no mipmaps) // PKM is a much simpler file format used mainly to contain a single ETC1/ETC2 compressed image (no mipmaps)
static Image LoadPKM(const char *fileName) static Image LoadPKM(const char *fileName)
{ {
unsigned int fileSize = 0;
unsigned char *fileData = LoadFileData(fileName, &fileSize);
unsigned char *fileDataPtr = fileData;
// Required extensions: // Required extensions:
// GL_OES_compressed_ETC1_RGB8_texture (ETC1) (OpenGL ES 2.0) // GL_OES_compressed_ETC1_RGB8_texture (ETC1) (OpenGL ES 2.0)
// GL_ARB_ES3_compatibility (ETC2/EAC) (OpenGL ES 3.0) // GL_ARB_ES3_compatibility (ETC2/EAC) (OpenGL ES 3.0)
@ -3482,54 +3491,47 @@ static Image LoadPKM(const char *fileName)
Image image = { 0 }; Image image = { 0 };
FILE *pkmFile = fopen(fileName, "rb"); if (fileDataPtr != NULL)
if (pkmFile == NULL)
{ {
TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to open PKM file", fileName); PKMHeader *pkmHeader = (PKMHeader *)fileDataPtr;
}
else
{
PKMHeader pkmHeader = { 0 };
// Get the image header if ((pkmHeader->id[0] != 'P') || (pkmHeader->id[1] != 'K') || (pkmHeader->id[2] != 'M') || (pkmHeader->id[3] != ' '))
fread(&pkmHeader, sizeof(PKMHeader), 1, pkmFile);
if ((pkmHeader.id[0] != 'P') || (pkmHeader.id[1] != 'K') || (pkmHeader.id[2] != 'M') || (pkmHeader.id[3] != ' '))
{ {
TRACELOG(LOG_WARNING, "IMAGE: [%s] PKM file not a valid image", fileName); TRACELOG(LOG_WARNING, "IMAGE: [%s] PKM file not a valid image", fileName);
} }
else else
{ {
fileDataPtr += sizeof(PKMHeader); // Skip header
// NOTE: format, width and height come as big-endian, data must be swapped to little-endian // NOTE: format, width and height come as big-endian, data must be swapped to little-endian
pkmHeader.format = ((pkmHeader.format & 0x00FF) << 8) | ((pkmHeader.format & 0xFF00) >> 8); pkmHeader->format = ((pkmHeader->format & 0x00FF) << 8) | ((pkmHeader->format & 0xFF00) >> 8);
pkmHeader.width = ((pkmHeader.width & 0x00FF) << 8) | ((pkmHeader.width & 0xFF00) >> 8); pkmHeader->width = ((pkmHeader->width & 0x00FF) << 8) | ((pkmHeader->width & 0xFF00) >> 8);
pkmHeader.height = ((pkmHeader.height & 0x00FF) << 8) | ((pkmHeader.height & 0xFF00) >> 8); pkmHeader->height = ((pkmHeader->height & 0x00FF) << 8) | ((pkmHeader->height & 0xFF00) >> 8);
TRACELOGD("IMAGE: [%s] PKM file info:", fileName); TRACELOGD("IMAGE: [%s] PKM file info:", fileName);
TRACELOGD(" > Image width: %i", pkmHeader.width); TRACELOGD(" > Image width: %i", pkmHeader->width);
TRACELOGD(" > Image height: %i", pkmHeader.height); TRACELOGD(" > Image height: %i", pkmHeader->height);
TRACELOGD(" > Image format: %i", pkmHeader.format); TRACELOGD(" > Image format: %i", pkmHeader->format);
image.width = pkmHeader.width; image.width = pkmHeader->width;
image.height = pkmHeader.height; image.height = pkmHeader->height;
image.mipmaps = 1; image.mipmaps = 1;
int bpp = 4; int bpp = 4;
if (pkmHeader.format == 3) bpp = 8; if (pkmHeader->format == 3) bpp = 8;
int size = image.width*image.height*bpp/8; // Total data size in bytes int dataSize = image.width*image.height*bpp/8; // Total data size in bytes
image.data = (unsigned char *)RL_MALLOC(size*sizeof(unsigned char)); image.data = (unsigned char *)RL_MALLOC(size*sizeof(unsigned char));
fread(image.data, size, 1, pkmFile); memcpy(image.data, fileDataPtr, dataSize);
if (pkmHeader.format == 0) image.format = COMPRESSED_ETC1_RGB; if (pkmHeader->format == 0) image.format = COMPRESSED_ETC1_RGB;
else if (pkmHeader.format == 1) image.format = COMPRESSED_ETC2_RGB; else if (pkmHeader->format == 1) image.format = COMPRESSED_ETC2_RGB;
else if (pkmHeader.format == 3) image.format = COMPRESSED_ETC2_EAC_RGBA; else if (pkmHeader->format == 3) image.format = COMPRESSED_ETC2_EAC_RGBA;
} }
fclose(pkmFile); // Close file pointer free(fileData); // Free file data buffer
} }
return image; return image;
@ -3540,6 +3542,10 @@ static Image LoadPKM(const char *fileName)
// Load KTX compressed image data (ETC1/ETC2 compression) // Load KTX compressed image data (ETC1/ETC2 compression)
static Image LoadKTX(const char *fileName) static Image LoadKTX(const char *fileName)
{ {
unsigned int fileSize = 0;
unsigned char *fileData = LoadFileData(fileName, &fileSize);
unsigned char *fileDataPtr = fileData;
// Required extensions: // Required extensions:
// GL_OES_compressed_ETC1_RGB8_texture (ETC1) // GL_OES_compressed_ETC1_RGB8_texture (ETC1)
// GL_ARB_ES3_compatibility (ETC2/EAC) // GL_ARB_ES3_compatibility (ETC2/EAC)
@ -3576,55 +3582,43 @@ static Image LoadKTX(const char *fileName)
Image image = { 0 }; Image image = { 0 };
FILE *ktxFile = fopen(fileName, "rb"); if (fileDataPtr != NULL)
if (ktxFile == NULL)
{ {
TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to load KTX file", fileName); KTXHeader *ktxHeader = (KTXHeader *)fileDataPtr;
}
else
{
KTXHeader ktxHeader = { 0 };
// Get the image header if ((ktxHeader->id[1] != 'K') || (ktxHeader->id[2] != 'T') || (ktxHeader->id[3] != 'X') ||
fread(&ktxHeader, sizeof(KTXHeader), 1, ktxFile); (ktxHeader->id[4] != ' ') || (ktxHeader->id[5] != '1') || (ktxHeader->id[6] != '1'))
if ((ktxHeader.id[1] != 'K') || (ktxHeader.id[2] != 'T') || (ktxHeader.id[3] != 'X') ||
(ktxHeader.id[4] != ' ') || (ktxHeader.id[5] != '1') || (ktxHeader.id[6] != '1'))
{ {
TRACELOG(LOG_WARNING, "IMAGE: [%s] KTX file not a valid image", fileName); TRACELOG(LOG_WARNING, "IMAGE: [%s] KTX file not a valid image", fileName);
} }
else else
{ {
image.width = ktxHeader.width; fileDataPtr += sizeof(KTXHeader); // Move file data pointer
image.height = ktxHeader.height;
image.mipmaps = ktxHeader.mipmapLevels; image.width = ktxHeader->width;
image.height = ktxHeader->height;
image.mipmaps = ktxHeader->mipmapLevels;
TRACELOGD("IMAGE: [%s] KTX file info:", fileName); TRACELOGD("IMAGE: [%s] KTX file info:", fileName);
TRACELOGD(" > Image width: %i", ktxHeader.width); TRACELOGD(" > Image width: %i", ktxHeader->width);
TRACELOGD(" > Image height: %i", ktxHeader.height); TRACELOGD(" > Image height: %i", ktxHeader->height);
TRACELOGD(" > Image format: 0x%x", ktxHeader.glInternalFormat); TRACELOGD(" > Image format: 0x%x", ktxHeader->glInternalFormat);
unsigned char unused; fileDataPtr += ktxHeader->keyValueDataSize; // Skip value data size
if (ktxHeader.keyValueDataSize > 0)
{
for (unsigned int i = 0; i < ktxHeader.keyValueDataSize; i++) fread(&unused, sizeof(unsigned char), 1U, ktxFile);
}
int dataSize;
fread(&dataSize, sizeof(unsigned int), 1, ktxFile);
int dataSize = ((int *)fileDataPtr)[0];
fileDataPtr += sizeof(int);
image.data = (unsigned char *)RL_MALLOC(dataSize*sizeof(unsigned char)); image.data = (unsigned char *)RL_MALLOC(dataSize*sizeof(unsigned char));
fread(image.data, dataSize, 1, ktxFile); memcpy(image.data, fileDataPtr, dataSize);
if (ktxHeader.glInternalFormat == 0x8D64) image.format = COMPRESSED_ETC1_RGB; if (ktxHeader->glInternalFormat == 0x8D64) image.format = COMPRESSED_ETC1_RGB;
else if (ktxHeader.glInternalFormat == 0x9274) image.format = COMPRESSED_ETC2_RGB; else if (ktxHeader->glInternalFormat == 0x9274) image.format = COMPRESSED_ETC2_RGB;
else if (ktxHeader.glInternalFormat == 0x9278) image.format = COMPRESSED_ETC2_EAC_RGBA; else if (ktxHeader->glInternalFormat == 0x9278) image.format = COMPRESSED_ETC2_EAC_RGBA;
} }
fclose(ktxFile); // Close file pointer free(fileData); // Free file data buffer
} }
return image; return image;
@ -3634,12 +3628,9 @@ static Image LoadKTX(const char *fileName)
// NOTE: By default KTX 1.1 spec is used, 2.0 is still on draft (01Oct2018) // NOTE: By default KTX 1.1 spec is used, 2.0 is still on draft (01Oct2018)
static int SaveKTX(Image image, const char *fileName) static int SaveKTX(Image image, const char *fileName)
{ {
int success = 0;
// KTX file Header (64 bytes) // KTX file Header (64 bytes)
// v1.1 - https://www.khronos.org/opengles/sdk/tools/KTX/file_format_spec/ // v1.1 - https://www.khronos.org/opengles/sdk/tools/KTX/file_format_spec/
// v2.0 - http://github.khronos.org/KTX-Specification/ - still on draft, not ready for implementation // v2.0 - http://github.khronos.org/KTX-Specification/ - still on draft, not ready for implementation
typedef struct { typedef struct {
char id[12]; // Identifier: "«KTX 11»\r\n\x1A\n" // KTX 2.0: "«KTX 22»\r\n\x1A\n" char id[12]; // Identifier: "«KTX 11»\r\n\x1A\n" // KTX 2.0: "«KTX 22»\r\n\x1A\n"
unsigned int endianness; // Little endian: 0x01 0x02 0x03 0x04 unsigned int endianness; // Little endian: 0x01 0x02 0x03 0x04
@ -3659,69 +3650,77 @@ static int SaveKTX(Image image, const char *fileName)
// KTX 2.0 defines additional header elements... // KTX 2.0 defines additional header elements...
} KTXHeader; } KTXHeader;
// NOTE: Before start of every mipmap data block, we have: unsigned int dataSize // Calculate file dataSize required
int dataSize = sizeof(KTXHeader);
FILE *ktxFile = fopen(fileName, "wb");
for (int i = 0, width = image.width, height = image.height; i < image.mipmaps; i++)
if (ktxFile == NULL) TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to open KTX file", fileName);
else
{ {
KTXHeader ktxHeader = { 0 }; dataSize += GetPixelDataSize(width, height, image.format);
width /= 2; height /= 2;
// KTX identifier (v1.1)
//unsigned char id[12] = { '«', 'K', 'T', 'X', ' ', '1', '1', '»', '\r', '\n', '\x1A', '\n' };
//unsigned char id[12] = { 0xAB, 0x4B, 0x54, 0x58, 0x20, 0x31, 0x31, 0xBB, 0x0D, 0x0A, 0x1A, 0x0A };
const char ktxIdentifier[12] = { 0xAB, 'K', 'T', 'X', ' ', '1', '1', 0xBB, '\r', '\n', 0x1A, '\n' };
// Get the image header
strncpy(ktxHeader.id, ktxIdentifier, 12); // KTX 1.1 signature
ktxHeader.endianness = 0;
ktxHeader.glType = 0; // Obtained from image.format
ktxHeader.glTypeSize = 1;
ktxHeader.glFormat = 0; // Obtained from image.format
ktxHeader.glInternalFormat = 0; // Obtained from image.format
ktxHeader.glBaseInternalFormat = 0;
ktxHeader.width = image.width;
ktxHeader.height = image.height;
ktxHeader.depth = 0;
ktxHeader.elements = 0;
ktxHeader.faces = 1;
ktxHeader.mipmapLevels = image.mipmaps; // If it was 0, it means mipmaps should be generated on loading (not for compressed formats)
ktxHeader.keyValueDataSize = 0; // No extra data after the header
rlGetGlTextureFormats(image.format, &ktxHeader.glInternalFormat, &ktxHeader.glFormat, &ktxHeader.glType); // rlgl module function
ktxHeader.glBaseInternalFormat = ktxHeader.glFormat; // KTX 1.1 only
// NOTE: We can save into a .ktx all PixelFormats supported by raylib, including compressed formats like DXT, ETC or ASTC
if (ktxHeader.glFormat == -1) TRACELOG(LOG_WARNING, "IMAGE: GL format not supported for KTX export (%i)", ktxHeader.glFormat);
else
{
success = (int)fwrite(&ktxHeader, sizeof(KTXHeader), 1, ktxFile);
int width = image.width;
int height = image.height;
int dataOffset = 0;
// Save all mipmaps data
for (int i = 0; i < image.mipmaps; i++)
{
unsigned int dataSize = GetPixelDataSize(width, height, image.format);
success = (int)fwrite(&dataSize, sizeof(unsigned int), 1, ktxFile);
success = (int)fwrite((unsigned char *)image.data + dataOffset, dataSize, 1, ktxFile);
width /= 2;
height /= 2;
dataOffset += dataSize;
}
}
fclose(ktxFile); // Close file pointer
} }
unsigned char *fileData = RL_CALLOC(dataSize, 1);
unsigned char *fileDataPtr = fileData;
KTXHeader ktxHeader = { 0 };
// KTX identifier (v1.1)
//unsigned char id[12] = { '«', 'K', 'T', 'X', ' ', '1', '1', '»', '\r', '\n', '\x1A', '\n' };
//unsigned char id[12] = { 0xAB, 0x4B, 0x54, 0x58, 0x20, 0x31, 0x31, 0xBB, 0x0D, 0x0A, 0x1A, 0x0A };
const char ktxIdentifier[12] = { 0xAB, 'K', 'T', 'X', ' ', '1', '1', 0xBB, '\r', '\n', 0x1A, '\n' };
// Get the image header
memcpy(ktxHeader.id, ktxIdentifier, 12); // KTX 1.1 signature
ktxHeader.endianness = 0;
ktxHeader.glType = 0; // Obtained from image.format
ktxHeader.glTypeSize = 1;
ktxHeader.glFormat = 0; // Obtained from image.format
ktxHeader.glInternalFormat = 0; // Obtained from image.format
ktxHeader.glBaseInternalFormat = 0;
ktxHeader.width = image.width;
ktxHeader.height = image.height;
ktxHeader.depth = 0;
ktxHeader.elements = 0;
ktxHeader.faces = 1;
ktxHeader.mipmapLevels = image.mipmaps; // If it was 0, it means mipmaps should be generated on loading (not for compressed formats)
ktxHeader.keyValueDataSize = 0; // No extra data after the header
rlGetGlTextureFormats(image.format, &ktxHeader.glInternalFormat, &ktxHeader.glFormat, &ktxHeader.glType); // rlgl module function
ktxHeader.glBaseInternalFormat = ktxHeader.glFormat; // KTX 1.1 only
// NOTE: We can save into a .ktx all PixelFormats supported by raylib, including compressed formats like DXT, ETC or ASTC
if (ktxHeader.glFormat == -1) TRACELOG(LOG_WARNING, "IMAGE: GL format not supported for KTX export (%i)", ktxHeader.glFormat);
else
{
memcpy(fileDataPtr, &ktxHeader, sizeof(KTXHeader));
fileDataPtr += sizeof(KTXHeader);
int width = image.width;
int height = image.height;
int dataOffset = 0;
// Save all mipmaps data
for (int i = 0; i < image.mipmaps; i++)
{
unsigned int dataSize = GetPixelDataSize(width, height, image.format);
memcpy(fileDataPtr, &dataSize, sizeof(unsigned int));
memcpy(fileDataPtr + 4, (unsigned char *)image.data + dataOffset, dataSize);
width /= 2;
height /= 2;
dataOffset += dataSize;
fileDataPtr += (4 + dataSize);
}
}
SaveFileData(fileName, fileData, dataSize);
free(fileData); // Free file data buffer
// If all data has been written correctly to file, success = 1 // If all data has been written correctly to file, success = 1
return success; return true;
} }
#endif #endif
@ -3730,6 +3729,10 @@ static int SaveKTX(Image image, const char *fileName)
// NOTE: PVR v2 not supported, use PVR v3 instead // NOTE: PVR v2 not supported, use PVR v3 instead
static Image LoadPVR(const char *fileName) static Image LoadPVR(const char *fileName)
{ {
unsigned int fileSize = 0;
unsigned char *fileData = LoadFileData(fileName, &fileSize);
unsigned char *fileDataPtr = fileData;
// Required extension: // Required extension:
// GL_IMG_texture_compression_pvrtc // GL_IMG_texture_compression_pvrtc
@ -3786,69 +3789,52 @@ static Image LoadPVR(const char *fileName)
Image image = { 0 }; Image image = { 0 };
FILE *pvrFile = fopen(fileName, "rb"); if (fileDataPtr != NULL)
if (pvrFile == NULL)
{
TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to load PVR file", fileName);
}
else
{ {
// Check PVR image version // Check PVR image version
unsigned char pvrVersion = 0; unsigned char pvrVersion = fileDataPtr[0];
fread(&pvrVersion, sizeof(unsigned char), 1, pvrFile);
fseek(pvrFile, 0, SEEK_SET);
// Load different PVR data formats // Load different PVR data formats
if (pvrVersion == 0x50) if (pvrVersion == 0x50)
{ {
PVRHeaderV3 pvrHeader = { 0 }; PVRHeaderV3 *pvrHeader = (PVRHeaderV3 *)fileDataPtr;
// Get PVR image header if ((pvrHeader->id[0] != 'P') || (pvrHeader->id[1] != 'V') || (pvrHeader->id[2] != 'R') || (pvrHeader->id[3] != 3))
fread(&pvrHeader, sizeof(PVRHeaderV3), 1, pvrFile);
if ((pvrHeader.id[0] != 'P') || (pvrHeader.id[1] != 'V') || (pvrHeader.id[2] != 'R') || (pvrHeader.id[3] != 3))
{ {
TRACELOG(LOG_WARNING, "IMAGE: [%s] PVR file not a valid image", fileName); TRACELOG(LOG_WARNING, "IMAGE: [%s] PVR file not a valid image", fileName);
} }
else else
{ {
image.width = pvrHeader.width; fileDataPtr += sizeof(PVRHeaderV3); // Skip header
image.height = pvrHeader.height;
image.mipmaps = pvrHeader.numMipmaps; image.width = pvrHeader->width;
image.height = pvrHeader->height;
image.mipmaps = pvrHeader->numMipmaps;
// Check data format // Check data format
if (((pvrHeader.channels[0] == 'l') && (pvrHeader.channels[1] == 0)) && (pvrHeader.channelDepth[0] == 8)) if (((pvrHeader->channels[0] == 'l') && (pvrHeader->channels[1] == 0)) && (pvrHeader->channelDepth[0] == 8)) image.format = UNCOMPRESSED_GRAYSCALE;
image.format = UNCOMPRESSED_GRAYSCALE; else if (((pvrHeader->channels[0] == 'l') && (pvrHeader->channels[1] == 'a')) && ((pvrHeader->channelDepth[0] == 8) && (pvrHeader->channelDepth[1] == 8))) image.format = UNCOMPRESSED_GRAY_ALPHA;
else if (((pvrHeader.channels[0] == 'l') && (pvrHeader.channels[1] == 'a')) && ((pvrHeader.channelDepth[0] == 8) && (pvrHeader.channelDepth[1] == 8))) else if ((pvrHeader->channels[0] == 'r') && (pvrHeader->channels[1] == 'g') && (pvrHeader->channels[2] == 'b'))
image.format = UNCOMPRESSED_GRAY_ALPHA;
else if ((pvrHeader.channels[0] == 'r') && (pvrHeader.channels[1] == 'g') && (pvrHeader.channels[2] == 'b'))
{ {
if (pvrHeader.channels[3] == 'a') if (pvrHeader->channels[3] == 'a')
{ {
if ((pvrHeader.channelDepth[0] == 5) && (pvrHeader.channelDepth[1] == 5) && (pvrHeader.channelDepth[2] == 5) && (pvrHeader.channelDepth[3] == 1)) if ((pvrHeader->channelDepth[0] == 5) && (pvrHeader->channelDepth[1] == 5) && (pvrHeader->channelDepth[2] == 5) && (pvrHeader->channelDepth[3] == 1)) image.format = UNCOMPRESSED_R5G5B5A1;
image.format = UNCOMPRESSED_R5G5B5A1; else if ((pvrHeader->channelDepth[0] == 4) && (pvrHeader->channelDepth[1] == 4) && (pvrHeader->channelDepth[2] == 4) && (pvrHeader->channelDepth[3] == 4)) image.format = UNCOMPRESSED_R4G4B4A4;
else if ((pvrHeader.channelDepth[0] == 4) && (pvrHeader.channelDepth[1] == 4) && (pvrHeader.channelDepth[2] == 4) && (pvrHeader.channelDepth[3] == 4)) else if ((pvrHeader->channelDepth[0] == 8) && (pvrHeader->channelDepth[1] == 8) && (pvrHeader->channelDepth[2] == 8) && (pvrHeader->channelDepth[3] == 8)) image.format = UNCOMPRESSED_R8G8B8A8;
image.format = UNCOMPRESSED_R4G4B4A4;
else if ((pvrHeader.channelDepth[0] == 8) && (pvrHeader.channelDepth[1] == 8) && (pvrHeader.channelDepth[2] == 8) && (pvrHeader.channelDepth[3] == 8))
image.format = UNCOMPRESSED_R8G8B8A8;
} }
else if (pvrHeader.channels[3] == 0) else if (pvrHeader->channels[3] == 0)
{ {
if ((pvrHeader.channelDepth[0] == 5) && (pvrHeader.channelDepth[1] == 6) && (pvrHeader.channelDepth[2] == 5)) image.format = UNCOMPRESSED_R5G6B5; if ((pvrHeader->channelDepth[0] == 5) && (pvrHeader->channelDepth[1] == 6) && (pvrHeader->channelDepth[2] == 5)) image.format = UNCOMPRESSED_R5G6B5;
else if ((pvrHeader.channelDepth[0] == 8) && (pvrHeader.channelDepth[1] == 8) && (pvrHeader.channelDepth[2] == 8)) image.format = UNCOMPRESSED_R8G8B8; else if ((pvrHeader->channelDepth[0] == 8) && (pvrHeader->channelDepth[1] == 8) && (pvrHeader->channelDepth[2] == 8)) image.format = UNCOMPRESSED_R8G8B8;
} }
} }
else if (pvrHeader.channels[0] == 2) image.format = COMPRESSED_PVRT_RGB; else if (pvrHeader->channels[0] == 2) image.format = COMPRESSED_PVRT_RGB;
else if (pvrHeader.channels[0] == 3) image.format = COMPRESSED_PVRT_RGBA; else if (pvrHeader->channels[0] == 3) image.format = COMPRESSED_PVRT_RGBA;
// Skip meta data header fileDataPtr += pvrHeader->metaDataSize); // Skip meta data header
unsigned char unused = 0;
for (unsigned int i = 0; i < pvrHeader.metaDataSize; i++) fread(&unused, sizeof(unsigned char), 1, pvrFile);
// Calculate data size (depends on format) // Calculate data size (depends on format)
int bpp = 0; int bpp = 0;
switch (image.format) switch (image.format)
{ {
case UNCOMPRESSED_GRAYSCALE: bpp = 8; break; case UNCOMPRESSED_GRAYSCALE: bpp = 8; break;
@ -3866,13 +3852,12 @@ static Image LoadPVR(const char *fileName)
int dataSize = image.width*image.height*bpp/8; // Total data size in bytes int dataSize = image.width*image.height*bpp/8; // Total data size in bytes
image.data = (unsigned char *)RL_MALLOC(dataSize*sizeof(unsigned char)); image.data = (unsigned char *)RL_MALLOC(dataSize*sizeof(unsigned char));
// Read data from file memcpy(image.data, fileDataPtr, dataSize);
fread(image.data, dataSize, 1, pvrFile);
} }
} }
else if (pvrVersion == 52) TRACELOG(LOG_INFO, "IMAGE: [%s] PVRv2 format not supported, update your files to PVRv3", fileName); else if (pvrVersion == 52) TRACELOG(LOG_INFO, "IMAGE: [%s] PVRv2 format not supported, update your files to PVRv3", fileName);
fclose(pvrFile); // Close file pointer free(fileData); // Free file data buffer
} }
return image; return image;
@ -3883,6 +3868,10 @@ static Image LoadPVR(const char *fileName)
// Load ASTC compressed image data (ASTC compression) // Load ASTC compressed image data (ASTC compression)
static Image LoadASTC(const char *fileName) static Image LoadASTC(const char *fileName)
{ {
unsigned int fileSize = 0;
unsigned char *fileData = LoadFileData(fileName, &fileSize);
unsigned char *fileDataPtr = fileData;
// Required extensions: // Required extensions:
// GL_KHR_texture_compression_astc_hdr // GL_KHR_texture_compression_astc_hdr
// GL_KHR_texture_compression_astc_ldr // GL_KHR_texture_compression_astc_ldr
@ -3904,38 +3893,31 @@ static Image LoadASTC(const char *fileName)
Image image = { 0 }; Image image = { 0 };
FILE *astcFile = fopen(fileName, "rb"); if (fileDataPtr != NULL)
if (astcFile == NULL)
{ {
TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to load ASTC file", fileName); ASTCHeader *astcHeader = (ASTCHeader *)fileDataPtr;
}
else
{
ASTCHeader astcHeader = { 0 };
// Get ASTC image header if ((astcHeader->id[3] != 0x5c) || (astcHeader->id[2] != 0xa1) || (astcHeader->id[1] != 0xab) || (astcHeader->id[0] != 0x13))
fread(&astcHeader, sizeof(ASTCHeader), 1, astcFile);
if ((astcHeader.id[3] != 0x5c) || (astcHeader.id[2] != 0xa1) || (astcHeader.id[1] != 0xab) || (astcHeader.id[0] != 0x13))
{ {
TRACELOG(LOG_WARNING, "IMAGE: [%s] ASTC file not a valid image", fileName); TRACELOG(LOG_WARNING, "IMAGE: [%s] ASTC file not a valid image", fileName);
} }
else else
{ {
fileDataPtr += sizeof(ASTCHeader); // Skip header
// NOTE: Assuming Little Endian (could it be wrong?) // NOTE: Assuming Little Endian (could it be wrong?)
image.width = 0x00000000 | ((int)astcHeader.width[2] << 16) | ((int)astcHeader.width[1] << 8) | ((int)astcHeader.width[0]); image.width = 0x00000000 | ((int)astcHeader->width[2] << 16) | ((int)astcHeader->width[1] << 8) | ((int)astcHeader->width[0]);
image.height = 0x00000000 | ((int)astcHeader.height[2] << 16) | ((int)astcHeader.height[1] << 8) | ((int)astcHeader.height[0]); image.height = 0x00000000 | ((int)astcHeader->height[2] << 16) | ((int)astcHeader->height[1] << 8) | ((int)astcHeader->height[0]);
TRACELOGD("IMAGE: [%s] ASTC file info:", fileName); TRACELOGD("IMAGE: [%s] ASTC file info:", fileName);
TRACELOGD(" > Image width: %i", image.width); TRACELOGD(" > Image width: %i", image.width);
TRACELOGD(" > Image height: %i", image.height); TRACELOGD(" > Image height: %i", image.height);
TRACELOGD(" > Image blocks: %ix%i", astcHeader.blockX, astcHeader.blockY); TRACELOGD(" > Image blocks: %ix%i", astcHeader->blockX, astcHeader->blockY);
image.mipmaps = 1; // NOTE: ASTC format only contains one mipmap level image.mipmaps = 1; // NOTE: ASTC format only contains one mipmap level
// NOTE: Each block is always stored in 128bit so we can calculate the bpp // NOTE: Each block is always stored in 128bit so we can calculate the bpp
int bpp = 128/(astcHeader.blockX*astcHeader.blockY); int bpp = 128/(astcHeader->blockX*astcHeader->blockY);
// NOTE: Currently we only support 2 blocks configurations: 4x4 and 8x8 // NOTE: Currently we only support 2 blocks configurations: 4x4 and 8x8
if ((bpp == 8) || (bpp == 2)) if ((bpp == 8) || (bpp == 2))
@ -3943,7 +3925,8 @@ static Image LoadASTC(const char *fileName)
int dataSize = image.width*image.height*bpp/8; // Data size in bytes int dataSize = image.width*image.height*bpp/8; // Data size in bytes
image.data = (unsigned char *)RL_MALLOC(dataSize*sizeof(unsigned char)); image.data = (unsigned char *)RL_MALLOC(dataSize*sizeof(unsigned char));
fread(image.data, dataSize, 1, astcFile);
memcpy(image.data, fileDataPtr, dataSize);
if (bpp == 8) image.format = COMPRESSED_ASTC_4x4_RGBA; if (bpp == 8) image.format = COMPRESSED_ASTC_4x4_RGBA;
else if (bpp == 2) image.format = COMPRESSED_ASTC_8x8_RGBA; else if (bpp == 2) image.format = COMPRESSED_ASTC_8x8_RGBA;
@ -3951,7 +3934,7 @@ static Image LoadASTC(const char *fileName)
else TRACELOG(LOG_WARNING, "IMAGE: [%s] ASTC block size configuration not supported", fileName); else TRACELOG(LOG_WARNING, "IMAGE: [%s] ASTC block size configuration not supported", fileName);
} }
fclose(astcFile); free(fileData); // Free file data buffer
} }
return image; return image;