2014-09-17 00:51:31 +04:00
|
|
|
/**********************************************************************************************
|
2013-11-19 02:38:44 +04:00
|
|
|
*
|
2013-11-23 16:30:54 +04:00
|
|
|
* raylib.textures
|
2013-11-19 02:38:44 +04:00
|
|
|
*
|
2013-11-23 16:30:54 +04:00
|
|
|
* Basic functions to load and draw Textures (2d)
|
2014-09-03 18:51:28 +04:00
|
|
|
*
|
2013-11-23 16:30:54 +04:00
|
|
|
* Uses external lib:
|
|
|
|
* stb_image - Multiple formats image loading (JPEG, PNG, BMP, TGA, PSD, GIF, PIC)
|
2014-09-17 00:51:31 +04:00
|
|
|
* NOTE: stb_image has been slightly modified, original library: https://github.com/nothings/stb
|
2013-11-19 02:38:44 +04:00
|
|
|
*
|
2014-09-17 00:51:31 +04:00
|
|
|
* Copyright (c) 2014 Ramon Santamaria (Ray San - raysan@raysanweb.com)
|
2014-09-03 18:51:28 +04:00
|
|
|
*
|
|
|
|
* This software is provided "as-is", without any express or implied warranty. In no event
|
2013-11-23 16:30:54 +04:00
|
|
|
* will the authors be held liable for any damages arising from the use of this software.
|
2013-11-19 02:38:44 +04:00
|
|
|
*
|
2014-09-03 18:51:28 +04:00
|
|
|
* Permission is granted to anyone to use this software for any purpose, including commercial
|
2013-11-23 16:30:54 +04:00
|
|
|
* applications, and to alter it and redistribute it freely, subject to the following restrictions:
|
2013-11-19 02:38:44 +04:00
|
|
|
*
|
2014-09-03 18:51:28 +04:00
|
|
|
* 1. The origin of this software must not be misrepresented; you must not claim that you
|
|
|
|
* wrote the original software. If you use this software in a product, an acknowledgment
|
2013-11-23 16:30:54 +04:00
|
|
|
* in the product documentation would be appreciated but is not required.
|
2013-11-19 02:38:44 +04:00
|
|
|
*
|
2013-11-23 16:30:54 +04:00
|
|
|
* 2. Altered source versions must be plainly marked as such, and must not be misrepresented
|
|
|
|
* as being the original software.
|
2013-11-19 02:38:44 +04:00
|
|
|
*
|
2013-11-23 16:30:54 +04:00
|
|
|
* 3. This notice may not be removed or altered from any source distribution.
|
2013-11-19 02:38:44 +04:00
|
|
|
*
|
|
|
|
**********************************************************************************************/
|
|
|
|
|
|
|
|
#include "raylib.h"
|
|
|
|
|
2013-11-23 16:30:54 +04:00
|
|
|
#include <stdlib.h> // Declares malloc() and free() for memory management
|
2014-04-09 22:25:26 +04:00
|
|
|
#include <string.h> // Required for strcmp(), strrchr(), strncmp()
|
2014-01-23 15:36:18 +04:00
|
|
|
|
2014-03-25 15:40:35 +04:00
|
|
|
#include "rlgl.h" // raylib OpenGL abstraction layer to OpenGL 1.1, 3.3+ or ES2
|
2014-09-17 00:51:31 +04:00
|
|
|
#include "utils.h" // rRES data decompression utility function
|
|
|
|
// NOTE: Includes Android fopen function map
|
2014-03-25 15:40:35 +04:00
|
|
|
|
2014-11-23 23:58:17 +03:00
|
|
|
#define STB_IMAGE_IMPLEMENTATION
|
2014-09-17 00:51:31 +04:00
|
|
|
#include "stb_image.h" // Used to read image data (multiple formats support)
|
2014-07-23 21:50:06 +04:00
|
|
|
|
2013-11-19 02:38:44 +04:00
|
|
|
//----------------------------------------------------------------------------------
|
|
|
|
// Defines and Macros
|
|
|
|
//----------------------------------------------------------------------------------
|
|
|
|
// Nop...
|
|
|
|
|
|
|
|
//----------------------------------------------------------------------------------
|
|
|
|
// Types and Structures Definition
|
|
|
|
//----------------------------------------------------------------------------------
|
2015-05-05 00:46:31 +03:00
|
|
|
// ...
|
2014-04-09 22:25:26 +04:00
|
|
|
|
2013-11-19 02:38:44 +04:00
|
|
|
//----------------------------------------------------------------------------------
|
|
|
|
// Global Variables Definition
|
|
|
|
//----------------------------------------------------------------------------------
|
|
|
|
// It's lonely here...
|
|
|
|
|
2014-03-25 15:40:35 +04:00
|
|
|
//----------------------------------------------------------------------------------
|
|
|
|
// Other Modules Functions Declaration (required by text)
|
|
|
|
//----------------------------------------------------------------------------------
|
2015-05-05 00:46:31 +03:00
|
|
|
// ...
|
2014-03-25 15:40:35 +04:00
|
|
|
|
2013-11-19 02:38:44 +04:00
|
|
|
//----------------------------------------------------------------------------------
|
|
|
|
// Module specific Functions Declaration
|
|
|
|
//----------------------------------------------------------------------------------
|
2015-05-05 00:46:31 +03:00
|
|
|
static Image LoadDDS(const char *fileName); // Load DDS file
|
|
|
|
static Image LoadPKM(const char *fileName); // Load PKM file
|
|
|
|
static Image LoadKTX(const char *fileName); // Load KTX file
|
|
|
|
static Image LoadPVR(const char *fileName); // Load PVR file
|
|
|
|
static Image LoadASTC(const char *fileName); // Load ASTC file
|
2013-11-19 02:38:44 +04:00
|
|
|
|
|
|
|
//----------------------------------------------------------------------------------
|
|
|
|
// Module Functions Definition
|
|
|
|
//----------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
// Load an image into CPU memory (RAM)
|
|
|
|
Image LoadImage(const char *fileName)
|
|
|
|
{
|
2013-11-23 16:30:54 +04:00
|
|
|
Image image;
|
2014-09-03 18:51:28 +04:00
|
|
|
|
2015-05-05 00:46:31 +03:00
|
|
|
// Initialize image default values
|
|
|
|
image.data = NULL;
|
2014-04-19 18:36:49 +04:00
|
|
|
image.width = 0;
|
|
|
|
image.height = 0;
|
2015-05-05 00:46:31 +03:00
|
|
|
image.mipmaps = 0;
|
|
|
|
image.format = 0;
|
2014-09-03 18:51:28 +04:00
|
|
|
|
2014-04-09 22:25:26 +04:00
|
|
|
if ((strcmp(GetExtension(fileName),"png") == 0) ||
|
|
|
|
(strcmp(GetExtension(fileName),"bmp") == 0) ||
|
|
|
|
(strcmp(GetExtension(fileName),"tga") == 0) ||
|
|
|
|
(strcmp(GetExtension(fileName),"jpg") == 0) ||
|
|
|
|
(strcmp(GetExtension(fileName),"gif") == 0) ||
|
|
|
|
(strcmp(GetExtension(fileName),"psd") == 0) ||
|
|
|
|
(strcmp(GetExtension(fileName),"pic") == 0))
|
2014-09-03 18:51:28 +04:00
|
|
|
{
|
2015-05-05 00:46:31 +03:00
|
|
|
int imgWidth = 0;
|
|
|
|
int imgHeight = 0;
|
|
|
|
int imgBpp = 0;
|
|
|
|
|
2014-04-09 22:25:26 +04:00
|
|
|
// NOTE: Using stb_image to load images (Supports: BMP, TGA, PNG, JPG, ...)
|
2015-05-05 00:46:31 +03:00
|
|
|
image.data = stbi_load(fileName, &imgWidth, &imgHeight, &imgBpp, 0);
|
2014-09-03 18:51:28 +04:00
|
|
|
|
2015-05-05 00:46:31 +03:00
|
|
|
image.width = imgWidth;
|
|
|
|
image.height = imgHeight;
|
|
|
|
image.mipmaps = 1;
|
|
|
|
|
|
|
|
if (imgBpp == 1) image.format = UNCOMPRESSED_GRAYSCALE;
|
|
|
|
else if (imgBpp == 2) image.format = UNCOMPRESSED_GRAY_ALPHA;
|
|
|
|
else if (imgBpp == 3) image.format = UNCOMPRESSED_R8G8B8;
|
|
|
|
else if (imgBpp == 4) image.format = UNCOMPRESSED_R8G8B8A8;
|
2014-09-17 00:51:31 +04:00
|
|
|
}
|
2015-05-05 00:46:31 +03:00
|
|
|
else if (strcmp(GetExtension(fileName),"dds") == 0) image = LoadDDS(fileName);
|
|
|
|
else if (strcmp(GetExtension(fileName),"pkm") == 0) image = LoadPKM(fileName);
|
|
|
|
else if (strcmp(GetExtension(fileName),"ktx") == 0) image = LoadKTX(fileName);
|
|
|
|
else if (strcmp(GetExtension(fileName),"pvr") == 0) image = LoadPVR(fileName);
|
|
|
|
else if (strcmp(GetExtension(fileName),"astc") == 0) image = LoadASTC(fileName);
|
|
|
|
|
|
|
|
if (image.data != NULL)
|
|
|
|
{
|
|
|
|
TraceLog(INFO, "[%s] Image loaded successfully (%ix%i)", fileName, image.width, image.height);
|
2014-04-19 18:36:49 +04:00
|
|
|
}
|
2015-05-05 00:46:31 +03:00
|
|
|
else TraceLog(WARNING, "[%s] Image could not be loaded, file not recognized", fileName);
|
2013-11-19 02:38:44 +04:00
|
|
|
|
2013-11-23 16:30:54 +04:00
|
|
|
return image;
|
2013-11-19 02:38:44 +04:00
|
|
|
}
|
|
|
|
|
2014-01-23 15:36:18 +04:00
|
|
|
// Load an image from rRES file (raylib Resource)
|
2015-05-05 00:46:31 +03:00
|
|
|
// TODO: Review function to support multiple color modes
|
2014-01-23 15:36:18 +04:00
|
|
|
Image LoadImageFromRES(const char *rresName, int resId)
|
2013-11-19 02:38:44 +04:00
|
|
|
{
|
2014-01-23 15:36:18 +04:00
|
|
|
Image image;
|
|
|
|
bool found = false;
|
|
|
|
|
|
|
|
char id[4]; // rRES file identifier
|
|
|
|
unsigned char version; // rRES file version and subversion
|
|
|
|
char useless; // rRES header reserved data
|
|
|
|
short numRes;
|
2014-09-03 18:51:28 +04:00
|
|
|
|
2014-01-23 15:36:18 +04:00
|
|
|
ResInfoHeader infoHeader;
|
2014-09-03 18:51:28 +04:00
|
|
|
|
2014-01-23 15:36:18 +04:00
|
|
|
FILE *rresFile = fopen(rresName, "rb");
|
|
|
|
|
2015-02-02 02:53:49 +03:00
|
|
|
if (rresFile == NULL)
|
2014-12-31 20:03:32 +03:00
|
|
|
{
|
|
|
|
TraceLog(WARNING, "[%s] rRES raylib resource file could not be opened", rresName);
|
|
|
|
}
|
2014-04-09 22:25:26 +04:00
|
|
|
else
|
2014-01-23 15:36:18 +04:00
|
|
|
{
|
2014-04-09 22:25:26 +04:00
|
|
|
// Read rres file (basic file check - id)
|
|
|
|
fread(&id[0], sizeof(char), 1, rresFile);
|
|
|
|
fread(&id[1], sizeof(char), 1, rresFile);
|
|
|
|
fread(&id[2], sizeof(char), 1, rresFile);
|
|
|
|
fread(&id[3], sizeof(char), 1, rresFile);
|
|
|
|
fread(&version, sizeof(char), 1, rresFile);
|
|
|
|
fread(&useless, sizeof(char), 1, rresFile);
|
2014-09-03 18:51:28 +04:00
|
|
|
|
2014-04-09 22:25:26 +04:00
|
|
|
if ((id[0] != 'r') && (id[1] != 'R') && (id[2] != 'E') &&(id[3] != 'S'))
|
|
|
|
{
|
|
|
|
TraceLog(WARNING, "[%s] This is not a valid raylib resource file", rresName);
|
|
|
|
}
|
|
|
|
else
|
2014-01-23 15:36:18 +04:00
|
|
|
{
|
2014-04-09 22:25:26 +04:00
|
|
|
// Read number of resources embedded
|
|
|
|
fread(&numRes, sizeof(short), 1, rresFile);
|
2014-09-03 18:51:28 +04:00
|
|
|
|
2014-04-09 22:25:26 +04:00
|
|
|
for (int i = 0; i < numRes; i++)
|
2014-01-23 15:36:18 +04:00
|
|
|
{
|
2014-04-09 22:25:26 +04:00
|
|
|
fread(&infoHeader, sizeof(ResInfoHeader), 1, rresFile);
|
2014-09-03 18:51:28 +04:00
|
|
|
|
2014-04-09 22:25:26 +04:00
|
|
|
if (infoHeader.id == resId)
|
|
|
|
{
|
|
|
|
found = true;
|
2014-09-03 18:51:28 +04:00
|
|
|
|
2014-04-09 22:25:26 +04:00
|
|
|
// Check data is of valid IMAGE type
|
|
|
|
if (infoHeader.type == 0) // IMAGE data type
|
|
|
|
{
|
|
|
|
// TODO: Check data compression type
|
|
|
|
// NOTE: We suppose compression type 2 (DEFLATE - default)
|
2014-04-19 18:36:49 +04:00
|
|
|
|
2014-04-09 22:25:26 +04:00
|
|
|
short imgWidth, imgHeight;
|
|
|
|
char colorFormat, mipmaps;
|
2014-09-03 18:51:28 +04:00
|
|
|
|
2014-04-09 22:25:26 +04:00
|
|
|
fread(&imgWidth, sizeof(short), 1, rresFile); // Image width
|
|
|
|
fread(&imgHeight, sizeof(short), 1, rresFile); // Image height
|
|
|
|
fread(&colorFormat, 1, 1, rresFile); // Image data color format (default: RGBA 32 bit)
|
|
|
|
fread(&mipmaps, 1, 1, rresFile); // Mipmap images included (default: 0)
|
2014-09-03 18:51:28 +04:00
|
|
|
|
2014-04-09 22:25:26 +04:00
|
|
|
image.width = (int)imgWidth;
|
|
|
|
image.height = (int)imgHeight;
|
2014-09-03 18:51:28 +04:00
|
|
|
|
2015-05-05 00:46:31 +03:00
|
|
|
unsigned char *compData = malloc(infoHeader.size);
|
2014-01-23 15:36:18 +04:00
|
|
|
|
2015-05-05 00:46:31 +03:00
|
|
|
fread(compData, infoHeader.size, 1, rresFile);
|
2014-09-03 18:51:28 +04:00
|
|
|
|
2015-05-05 00:46:31 +03:00
|
|
|
unsigned char *imgData = DecompressData(compData, infoHeader.size, infoHeader.srcSize);
|
2014-09-03 18:51:28 +04:00
|
|
|
|
2015-05-05 00:46:31 +03:00
|
|
|
// TODO: Review color mode
|
|
|
|
//image.data = (unsigned char *)malloc(sizeof(unsigned char)*imgWidth*imgHeight*4);
|
|
|
|
image.data = imgData;
|
2014-09-03 18:51:28 +04:00
|
|
|
|
2015-05-05 00:46:31 +03:00
|
|
|
//free(imgData);
|
2014-09-03 18:51:28 +04:00
|
|
|
|
2015-05-05 00:46:31 +03:00
|
|
|
free(compData);
|
2014-09-03 18:51:28 +04:00
|
|
|
|
2014-04-09 22:25:26 +04:00
|
|
|
TraceLog(INFO, "[%s] Image loaded successfully from resource, size: %ix%i", rresName, image.width, image.height);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
TraceLog(WARNING, "[%s] Required resource do not seem to be a valid IMAGE resource", rresName);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
2014-01-23 15:36:18 +04:00
|
|
|
{
|
2014-04-09 22:25:26 +04:00
|
|
|
// Depending on type, skip the right amount of parameters
|
|
|
|
switch (infoHeader.type)
|
|
|
|
{
|
2014-04-19 18:36:49 +04:00
|
|
|
case 0: fseek(rresFile, 6, SEEK_CUR); break; // IMAGE: Jump 6 bytes of parameters
|
|
|
|
case 1: fseek(rresFile, 6, SEEK_CUR); break; // SOUND: Jump 6 bytes of parameters
|
|
|
|
case 2: fseek(rresFile, 5, SEEK_CUR); break; // MODEL: Jump 5 bytes of parameters (TODO: Review)
|
|
|
|
case 3: break; // TEXT: No parameters
|
|
|
|
case 4: break; // RAW: No parameters
|
2014-04-09 22:25:26 +04:00
|
|
|
default: break;
|
|
|
|
}
|
2014-09-03 18:51:28 +04:00
|
|
|
|
2014-04-09 22:25:26 +04:00
|
|
|
// Jump DATA to read next infoHeader
|
|
|
|
fseek(rresFile, infoHeader.size, SEEK_CUR);
|
2014-01-23 15:36:18 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2014-09-03 18:51:28 +04:00
|
|
|
|
2014-04-09 22:25:26 +04:00
|
|
|
fclose(rresFile);
|
2014-01-23 15:36:18 +04:00
|
|
|
}
|
2014-09-03 18:51:28 +04:00
|
|
|
|
2014-04-09 22:25:26 +04:00
|
|
|
if (!found) TraceLog(WARNING, "[%s] Required resource id [%i] could not be found in the raylib resource file", rresName, resId);
|
2014-09-03 18:51:28 +04:00
|
|
|
|
2014-01-23 15:36:18 +04:00
|
|
|
return image;
|
2013-11-19 02:38:44 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
// Load an image as texture into GPU memory
|
|
|
|
Texture2D LoadTexture(const char *fileName)
|
|
|
|
{
|
2013-11-23 16:30:54 +04:00
|
|
|
Texture2D texture;
|
2015-02-02 02:53:49 +03:00
|
|
|
|
2015-05-05 00:46:31 +03:00
|
|
|
Image image = LoadImage(fileName);
|
|
|
|
|
2014-12-31 20:03:32 +03:00
|
|
|
#if defined(PLATFORM_RPI) || defined(PLATFORM_WEB)
|
2015-05-05 00:46:31 +03:00
|
|
|
ConvertToPOT(&image, BLANK);
|
2014-12-31 20:03:32 +03:00
|
|
|
#endif
|
2015-05-05 00:46:31 +03:00
|
|
|
|
|
|
|
texture = LoadTextureFromImage(image, false);
|
|
|
|
UnloadImage(image);
|
2014-09-03 18:51:28 +04:00
|
|
|
|
2013-11-23 16:30:54 +04:00
|
|
|
return texture;
|
2013-11-19 02:38:44 +04:00
|
|
|
}
|
|
|
|
|
2015-04-06 15:02:29 +03:00
|
|
|
Texture2D LoadTextureEx(void *data, int width, int height, int textureFormat, int mipmapCount, bool genMipmaps)
|
|
|
|
{
|
|
|
|
Texture2D texture;
|
|
|
|
|
|
|
|
texture.width = width;
|
|
|
|
texture.height = height;
|
2015-05-05 00:46:31 +03:00
|
|
|
texture.mipmaps = mipmapCount;
|
|
|
|
texture.format = textureFormat;
|
2015-04-06 15:02:29 +03:00
|
|
|
|
|
|
|
texture.id = rlglLoadTexture(data, width, height, textureFormat, mipmapCount, genMipmaps);
|
|
|
|
|
|
|
|
return texture;
|
|
|
|
}
|
2015-03-01 18:00:52 +03:00
|
|
|
|
2014-12-15 03:08:30 +03:00
|
|
|
// Load a texture from image data
|
|
|
|
// NOTE: image is not unloaded, it must be done manually
|
|
|
|
Texture2D LoadTextureFromImage(Image image, bool genMipmaps)
|
|
|
|
{
|
|
|
|
Texture2D texture;
|
|
|
|
|
|
|
|
// Init texture to default values
|
|
|
|
texture.id = 0;
|
|
|
|
texture.width = 0;
|
|
|
|
texture.height = 0;
|
2015-05-05 00:46:31 +03:00
|
|
|
texture.mipmaps = 0;
|
|
|
|
texture.format = 0;
|
|
|
|
|
|
|
|
texture.id = rlglLoadTexture(image.data, image.width, image.height, image.format, image.mipmaps, false);
|
2015-05-11 01:15:46 +03:00
|
|
|
|
2015-05-05 00:46:31 +03:00
|
|
|
texture.width = image.width;
|
|
|
|
texture.height = image.height;
|
|
|
|
texture.mipmaps = image.mipmaps;
|
|
|
|
texture.format = image.format;
|
2014-12-15 03:08:30 +03:00
|
|
|
|
|
|
|
return texture;
|
|
|
|
}
|
|
|
|
|
2014-01-23 15:36:18 +04:00
|
|
|
// Load an image as texture from rRES file (raylib Resource)
|
|
|
|
Texture2D LoadTextureFromRES(const char *rresName, int resId)
|
|
|
|
{
|
|
|
|
Texture2D texture;
|
|
|
|
|
|
|
|
Image image = LoadImageFromRES(rresName, resId);
|
2014-12-15 03:08:30 +03:00
|
|
|
texture = LoadTextureFromImage(image, false);
|
2014-04-19 18:36:49 +04:00
|
|
|
UnloadImage(image);
|
2014-09-03 18:51:28 +04:00
|
|
|
|
2014-01-23 15:36:18 +04:00
|
|
|
return texture;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Unload image from CPU memory (RAM)
|
|
|
|
void UnloadImage(Image image)
|
|
|
|
{
|
2015-05-05 00:46:31 +03:00
|
|
|
free(image.data);
|
2014-01-23 15:36:18 +04:00
|
|
|
}
|
|
|
|
|
2013-11-19 02:38:44 +04:00
|
|
|
// Unload texture from GPU memory
|
|
|
|
void UnloadTexture(Texture2D texture)
|
|
|
|
{
|
2014-04-19 18:36:49 +04:00
|
|
|
rlDeleteTextures(texture.id);
|
2013-11-19 02:38:44 +04:00
|
|
|
}
|
|
|
|
|
2014-12-31 20:03:32 +03:00
|
|
|
// Convert image to POT (power-of-two)
|
|
|
|
// NOTE: Requirement on OpenGL ES 2.0 (RPI, HTML5)
|
|
|
|
void ConvertToPOT(Image *image, Color fillColor)
|
|
|
|
{
|
2015-05-05 00:46:31 +03:00
|
|
|
// TODO: Review for new image struct
|
|
|
|
/*
|
2014-12-31 20:03:32 +03:00
|
|
|
// Just add the required amount of pixels at the right and bottom sides of image...
|
|
|
|
int potWidth = GetNextPOT(image->width);
|
|
|
|
int potHeight = GetNextPOT(image->height);
|
|
|
|
|
|
|
|
// Check if POT texture generation is required (if texture is not already POT)
|
|
|
|
if ((potWidth != image->width) || (potHeight != image->height))
|
|
|
|
{
|
|
|
|
Color *imgDataPixelPOT = NULL;
|
|
|
|
|
|
|
|
// Generate POT array from NPOT data
|
|
|
|
imgDataPixelPOT = (Color *)malloc(potWidth * potHeight * sizeof(Color));
|
|
|
|
|
|
|
|
for (int j = 0; j < potHeight; j++)
|
|
|
|
{
|
|
|
|
for (int i = 0; i < potWidth; i++)
|
|
|
|
{
|
2015-05-05 00:46:31 +03:00
|
|
|
if ((j < image->height) && (i < image->width)) imgDataPixelPOT[j*potWidth + i] = image->data[j*image->width + i];
|
2014-12-31 20:03:32 +03:00
|
|
|
else imgDataPixelPOT[j*potWidth + i] = fillColor;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
TraceLog(WARNING, "Image converted to POT: (%ix%i) -> (%ix%i)", image->width, image->height, potWidth, potHeight);
|
|
|
|
|
|
|
|
free(image->pixels);
|
|
|
|
|
|
|
|
image->pixels = imgDataPixelPOT;
|
|
|
|
image->width = potWidth;
|
|
|
|
image->height = potHeight;
|
|
|
|
}
|
2015-05-05 00:46:31 +03:00
|
|
|
*/
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get pixel data from image in the form of Color struct array
|
|
|
|
Color *GetPixelData(Image image)
|
|
|
|
{
|
|
|
|
Color *pixels = (Color *)malloc(image.width*image.height*sizeof(Color));
|
|
|
|
|
|
|
|
int k = 0;
|
|
|
|
|
|
|
|
for (int i = 0; i < image.width*image.height; i++)
|
|
|
|
{
|
|
|
|
switch (image.format)
|
|
|
|
{
|
|
|
|
case UNCOMPRESSED_GRAYSCALE:
|
|
|
|
{
|
|
|
|
pixels[i].r = ((unsigned char *)image.data)[k];
|
|
|
|
pixels[i].g = ((unsigned char *)image.data)[k];
|
|
|
|
pixels[i].b = ((unsigned char *)image.data)[k];
|
|
|
|
pixels[i].a = 255;
|
|
|
|
|
|
|
|
k++;
|
|
|
|
} break;
|
|
|
|
case UNCOMPRESSED_GRAY_ALPHA:
|
|
|
|
{
|
|
|
|
pixels[i].r = ((unsigned char *)image.data)[k];
|
|
|
|
pixels[i].g = ((unsigned char *)image.data)[k];
|
|
|
|
pixels[i].b = ((unsigned char *)image.data)[k];
|
|
|
|
pixels[i].a = ((unsigned char *)image.data)[k + 1];
|
|
|
|
|
|
|
|
k += 2;
|
|
|
|
} break;
|
|
|
|
case UNCOMPRESSED_R5G5B5A1:
|
|
|
|
{
|
|
|
|
unsigned short pixel = ((unsigned short *)image.data)[k];
|
|
|
|
|
|
|
|
pixels[i].r = (unsigned char)((float)((pixel & 0b1111100000000000) >> 11)*(255/31));
|
|
|
|
pixels[i].g = (unsigned char)((float)((pixel & 0b0000011111000000) >> 6)*(255/31));
|
|
|
|
pixels[i].b = (unsigned char)((float)((pixel & 0b0000000000111110) >> 1)*(255/31));
|
|
|
|
pixels[i].a = (unsigned char)((pixel & 0b0000000000000001)*255);
|
|
|
|
|
|
|
|
k++;
|
|
|
|
} break;
|
|
|
|
case UNCOMPRESSED_R5G6B5:
|
|
|
|
{
|
|
|
|
unsigned short pixel = ((unsigned short *)image.data)[k];
|
|
|
|
|
|
|
|
pixels[i].r = (unsigned char)((float)((pixel & 0b1111100000000000) >> 11)*(255/31));
|
|
|
|
pixels[i].g = (unsigned char)((float)((pixel & 0b0000011111100000) >> 5)*(255/63));
|
|
|
|
pixels[i].b = (unsigned char)((float)(pixel & 0b0000000000011111)*(255/31));
|
|
|
|
pixels[i].a = 255;
|
|
|
|
|
|
|
|
k++;
|
|
|
|
} break;
|
|
|
|
case UNCOMPRESSED_R4G4B4A4:
|
|
|
|
{
|
|
|
|
unsigned short pixel = ((unsigned short *)image.data)[k];
|
|
|
|
|
|
|
|
pixels[i].r = (unsigned char)((float)((pixel & 0b1111000000000000) >> 12)*(255/15));
|
|
|
|
pixels[i].g = (unsigned char)((float)((pixel & 0b0000111100000000) >> 8)*(255/15));
|
|
|
|
pixels[i].b = (unsigned char)((float)((pixel & 0b0000000011110000) >> 4)*(255/15));
|
|
|
|
pixels[i].a = (unsigned char)((float)(pixel & 0b0000000000001111)*(255/15));
|
|
|
|
|
|
|
|
k++;
|
|
|
|
} break;
|
|
|
|
case UNCOMPRESSED_R8G8B8A8:
|
|
|
|
{
|
|
|
|
pixels[i].r = ((unsigned char *)image.data)[k];
|
|
|
|
pixels[i].g = ((unsigned char *)image.data)[k + 1];
|
|
|
|
pixels[i].b = ((unsigned char *)image.data)[k + 2];
|
|
|
|
pixels[i].a = ((unsigned char *)image.data)[k + 3];
|
|
|
|
|
|
|
|
k += 4;
|
|
|
|
} break;
|
|
|
|
case UNCOMPRESSED_R8G8B8:
|
|
|
|
{
|
|
|
|
pixels[i].r = (unsigned char)((unsigned char *)image.data)[k];
|
|
|
|
pixels[i].g = (unsigned char)((unsigned char *)image.data)[k + 1];
|
|
|
|
pixels[i].b = (unsigned char)((unsigned char *)image.data)[k + 2];
|
|
|
|
pixels[i].a = 255;
|
|
|
|
|
|
|
|
k += 3;
|
|
|
|
} break;
|
|
|
|
default: TraceLog(WARNING, "Format not supported for pixel data retrieval"); break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return pixels;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Fill image data with pixels Color data (RGBA - 32bit)
|
2015-05-11 01:15:46 +03:00
|
|
|
// NOTE: Data is transformed to desired format
|
|
|
|
Image LoadImageFromData(Color *pixels, int width, int height, int format)
|
2015-05-05 00:46:31 +03:00
|
|
|
{
|
2015-05-11 01:15:46 +03:00
|
|
|
Image image;
|
|
|
|
image.data = NULL;
|
|
|
|
image.width = width;
|
|
|
|
image.height = height;
|
|
|
|
image.mipmaps = 1;
|
|
|
|
image.format = format;
|
|
|
|
|
2015-05-05 00:46:31 +03:00
|
|
|
int k = 0;
|
|
|
|
|
2015-05-11 01:15:46 +03:00
|
|
|
switch (format)
|
2015-05-05 00:46:31 +03:00
|
|
|
{
|
2015-05-11 01:15:46 +03:00
|
|
|
case UNCOMPRESSED_GRAYSCALE:
|
|
|
|
{
|
|
|
|
image.data = (unsigned char *)malloc(image.width*image.height*sizeof(unsigned char));
|
|
|
|
|
|
|
|
for (int i = 0; i < image.width*image.height; i++)
|
|
|
|
{
|
|
|
|
((unsigned char *)image.data)[i] = (unsigned char)((float)pixels[k].r*0.299f + (float)pixels[k].g*0.587f + (float)pixels[k].b*0.114f);
|
|
|
|
k++;
|
|
|
|
}
|
|
|
|
|
|
|
|
} break;
|
|
|
|
case UNCOMPRESSED_GRAY_ALPHA:
|
|
|
|
{
|
|
|
|
image.data = (unsigned char *)malloc(image.width*image.height*2*sizeof(unsigned char));
|
|
|
|
|
|
|
|
for (int i = 0; i < image.width*image.height*2; i += 2)
|
|
|
|
{
|
|
|
|
((unsigned char *)image.data)[i] = (unsigned char)((float)pixels[k].r*0.299f + (float)pixels[k].g*0.587f + (float)pixels[k].b*0.114f);
|
|
|
|
((unsigned char *)image.data)[i + 1] = pixels[k].a;
|
|
|
|
k++;
|
|
|
|
}
|
|
|
|
|
|
|
|
} break;
|
|
|
|
case UNCOMPRESSED_R5G6B5:
|
|
|
|
{
|
|
|
|
image.data = (unsigned short *)malloc(image.width*image.height*sizeof(unsigned short));
|
|
|
|
|
|
|
|
unsigned char r;
|
|
|
|
unsigned char g;
|
|
|
|
unsigned char b;
|
|
|
|
|
|
|
|
for (int i = 0; i < image.width*image.height; i++)
|
|
|
|
{
|
|
|
|
r = (unsigned char)(round((float)pixels[k].r*31/255));
|
|
|
|
g = (unsigned char)(round((float)pixels[k].g*63/255));
|
|
|
|
b = (unsigned char)(round((float)pixels[k].b*31/255));
|
|
|
|
|
|
|
|
((unsigned short *)image.data)[i] = (unsigned short)r << 11 | (unsigned short)g << 5 | (unsigned short)b;
|
|
|
|
|
|
|
|
k++;
|
|
|
|
}
|
|
|
|
|
|
|
|
} break;
|
|
|
|
case UNCOMPRESSED_R8G8B8:
|
|
|
|
{
|
|
|
|
image.data = (unsigned char *)malloc(image.width*image.height*3*sizeof(unsigned char));
|
|
|
|
|
|
|
|
for (int i = 0; i < image.width*image.height*3; i += 3)
|
|
|
|
{
|
|
|
|
((unsigned char *)image.data)[i] = pixels[k].r;
|
|
|
|
((unsigned char *)image.data)[i + 1] = pixels[k].g;
|
|
|
|
((unsigned char *)image.data)[i + 2] = pixels[k].b;
|
|
|
|
k++;
|
|
|
|
}
|
|
|
|
} break;
|
|
|
|
case UNCOMPRESSED_R5G5B5A1:
|
|
|
|
{
|
|
|
|
image.data = (unsigned short *)malloc(image.width*image.height*sizeof(unsigned short));
|
|
|
|
|
|
|
|
unsigned char r;
|
|
|
|
unsigned char g;
|
|
|
|
unsigned char b;
|
|
|
|
unsigned char a = 1;
|
|
|
|
|
|
|
|
for (int i = 0; i < image.width*image.height; i++)
|
|
|
|
{
|
|
|
|
r = (unsigned char)(round((float)pixels[k].r*31/255));
|
|
|
|
g = (unsigned char)(round((float)pixels[k].g*31/255));
|
|
|
|
b = (unsigned char)(round((float)pixels[k].b*31/255));
|
|
|
|
a = (pixels[k].a > 50) ? 1 : 0;
|
|
|
|
|
|
|
|
((unsigned short *)image.data)[i] = (unsigned short)r << 11 | (unsigned short)g << 6 | (unsigned short)b << 1| (unsigned short)a;
|
|
|
|
|
|
|
|
k++;
|
|
|
|
}
|
|
|
|
|
|
|
|
} break;
|
|
|
|
case UNCOMPRESSED_R4G4B4A4:
|
|
|
|
{
|
|
|
|
image.data = (unsigned short *)malloc(image.width*image.height*sizeof(unsigned short));
|
|
|
|
|
|
|
|
unsigned char r;
|
|
|
|
unsigned char g;
|
|
|
|
unsigned char b;
|
|
|
|
unsigned char a;
|
|
|
|
|
|
|
|
for (int i = 0; i < image.width*image.height; i++)
|
|
|
|
{
|
|
|
|
r = (unsigned char)(round((float)pixels[k].r*15/255));
|
|
|
|
g = (unsigned char)(round((float)pixels[k].g*15/255));
|
|
|
|
b = (unsigned char)(round((float)pixels[k].b*15/255));
|
|
|
|
a = (unsigned char)(round((float)pixels[k].a*15/255));
|
|
|
|
|
|
|
|
((unsigned short *)image.data)[i] = (unsigned short)r << 12 | (unsigned short)g << 8| (unsigned short)b << 4| (unsigned short)a;
|
|
|
|
|
|
|
|
k++;
|
|
|
|
}
|
|
|
|
|
|
|
|
} break;
|
|
|
|
case UNCOMPRESSED_R8G8B8A8:
|
|
|
|
{
|
|
|
|
image.data = (unsigned char *)malloc(image.width*image.height*4*sizeof(unsigned char));
|
|
|
|
|
|
|
|
for (int i = 0; i < image.width*image.height*4; i += 4)
|
|
|
|
{
|
|
|
|
((unsigned char *)image.data)[i] = pixels[k].r;
|
|
|
|
((unsigned char *)image.data)[i + 1] = pixels[k].g;
|
|
|
|
((unsigned char *)image.data)[i + 2] = pixels[k].b;
|
|
|
|
((unsigned char *)image.data)[i + 3] = pixels[k].a;
|
|
|
|
k++;
|
|
|
|
}
|
|
|
|
} break;
|
|
|
|
default:
|
|
|
|
{
|
|
|
|
TraceLog(WARNING, "Format not recognized, image could not be loaded");
|
|
|
|
|
|
|
|
return image;
|
|
|
|
} break;
|
2015-05-05 00:46:31 +03:00
|
|
|
}
|
2015-05-11 01:15:46 +03:00
|
|
|
|
|
|
|
return image;
|
2014-12-31 20:03:32 +03:00
|
|
|
}
|
|
|
|
|
2013-11-19 02:38:44 +04:00
|
|
|
// Draw a Texture2D
|
|
|
|
void DrawTexture(Texture2D texture, int posX, int posY, Color tint)
|
|
|
|
{
|
2014-04-09 22:25:26 +04:00
|
|
|
DrawTextureEx(texture, (Vector2){ (float)posX, (float)posY }, 0, 1.0f, tint);
|
2013-11-19 02:38:44 +04:00
|
|
|
}
|
|
|
|
|
2014-03-16 23:59:02 +04:00
|
|
|
// Draw a Texture2D with position defined as Vector2
|
|
|
|
void DrawTextureV(Texture2D texture, Vector2 position, Color tint)
|
|
|
|
{
|
|
|
|
DrawTextureEx(texture, position, 0, 1.0f, tint);
|
|
|
|
}
|
|
|
|
|
2013-11-19 02:38:44 +04:00
|
|
|
// Draw a Texture2D with extended parameters
|
|
|
|
void DrawTextureEx(Texture2D texture, Vector2 position, float rotation, float scale, Color tint)
|
|
|
|
{
|
2014-04-19 18:36:49 +04:00
|
|
|
Rectangle sourceRec = { 0, 0, texture.width, texture.height };
|
|
|
|
Rectangle destRec = { (int)position.x, (int)position.y, texture.width*scale, texture.height*scale };
|
|
|
|
Vector2 origin = { 0, 0 };
|
2014-09-03 18:51:28 +04:00
|
|
|
|
2014-04-19 18:36:49 +04:00
|
|
|
DrawTexturePro(texture, sourceRec, destRec, origin, rotation, tint);
|
2013-11-19 02:38:44 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
// Draw a part of a texture (defined by a rectangle)
|
2013-11-28 22:59:56 +04:00
|
|
|
void DrawTextureRec(Texture2D texture, Rectangle sourceRec, Vector2 position, Color tint)
|
2013-11-19 02:38:44 +04:00
|
|
|
{
|
2014-04-19 18:36:49 +04:00
|
|
|
Rectangle destRec = { (int)position.x, (int)position.y, sourceRec.width, sourceRec.height };
|
|
|
|
Vector2 origin = { 0, 0 };
|
2014-09-03 18:51:28 +04:00
|
|
|
|
2015-04-06 15:02:29 +03:00
|
|
|
DrawTexturePro(texture, sourceRec, destRec, origin, 0.0f, tint);
|
2013-11-28 22:59:56 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
// Draw a part of a texture (defined by a rectangle) with 'pro' parameters
|
2014-04-19 18:36:49 +04:00
|
|
|
// NOTE: origin is relative to destination rectangle size
|
2013-11-28 22:59:56 +04:00
|
|
|
void DrawTexturePro(Texture2D texture, Rectangle sourceRec, Rectangle destRec, Vector2 origin, float rotation, Color tint)
|
|
|
|
{
|
2014-04-19 18:36:49 +04:00
|
|
|
rlEnableTexture(texture.id);
|
2014-09-03 18:51:28 +04:00
|
|
|
|
2014-04-04 22:11:57 +04:00
|
|
|
rlPushMatrix();
|
2014-04-19 18:36:49 +04:00
|
|
|
rlTranslatef(destRec.x, destRec.y, 0);
|
2014-04-04 22:11:57 +04:00
|
|
|
rlRotatef(rotation, 0, 0, 1);
|
2014-04-19 18:36:49 +04:00
|
|
|
rlTranslatef(-origin.x, -origin.y, 0);
|
2014-09-03 18:51:28 +04:00
|
|
|
|
2014-04-04 22:11:57 +04:00
|
|
|
rlBegin(RL_QUADS);
|
|
|
|
rlColor4ub(tint.r, tint.g, tint.b, tint.a);
|
|
|
|
rlNormal3f(0.0f, 0.0f, 1.0f); // Normal vector pointing towards viewer
|
2014-09-03 18:51:28 +04:00
|
|
|
|
2014-04-04 22:11:57 +04:00
|
|
|
// Bottom-left corner for texture and quad
|
2014-09-03 18:51:28 +04:00
|
|
|
rlTexCoord2f((float)sourceRec.x / texture.width, (float)sourceRec.y / texture.height);
|
2014-04-04 22:11:57 +04:00
|
|
|
rlVertex2f(0.0f, 0.0f);
|
2014-09-03 18:51:28 +04:00
|
|
|
|
2014-04-04 22:11:57 +04:00
|
|
|
// Bottom-right corner for texture and quad
|
2014-04-19 18:36:49 +04:00
|
|
|
rlTexCoord2f((float)sourceRec.x / texture.width, (float)(sourceRec.y + sourceRec.height) / texture.height);
|
|
|
|
rlVertex2f(0.0f, destRec.height);
|
2014-09-03 18:51:28 +04:00
|
|
|
|
2014-04-04 22:11:57 +04:00
|
|
|
// Top-right corner for texture and quad
|
2014-09-03 18:51:28 +04:00
|
|
|
rlTexCoord2f((float)(sourceRec.x + sourceRec.width) / texture.width, (float)(sourceRec.y + sourceRec.height) / texture.height);
|
2014-04-04 22:11:57 +04:00
|
|
|
rlVertex2f(destRec.width, destRec.height);
|
2014-09-03 18:51:28 +04:00
|
|
|
|
2014-04-04 22:11:57 +04:00
|
|
|
// Top-left corner for texture and quad
|
2014-04-19 18:36:49 +04:00
|
|
|
rlTexCoord2f((float)(sourceRec.x + sourceRec.width) / texture.width, (float)sourceRec.y / texture.height);
|
|
|
|
rlVertex2f(destRec.width, 0.0f);
|
2014-04-04 22:11:57 +04:00
|
|
|
rlEnd();
|
|
|
|
rlPopMatrix();
|
2014-09-03 18:51:28 +04:00
|
|
|
|
2014-03-25 15:40:35 +04:00
|
|
|
rlDisableTexture();
|
|
|
|
}
|
|
|
|
|
2014-12-15 03:08:30 +03:00
|
|
|
//----------------------------------------------------------------------------------
|
|
|
|
// Module specific Functions Definition
|
|
|
|
//----------------------------------------------------------------------------------
|
2014-04-09 22:25:26 +04:00
|
|
|
|
2014-04-19 18:36:49 +04:00
|
|
|
// Loading DDS image data (compressed or uncompressed)
|
2015-05-05 00:46:31 +03:00
|
|
|
static Image LoadDDS(const char *fileName)
|
2014-09-03 18:51:28 +04:00
|
|
|
{
|
2015-05-05 00:46:31 +03:00
|
|
|
// Required extension:
|
|
|
|
// GL_EXT_texture_compression_s3tc
|
|
|
|
|
|
|
|
// Supported tokens (defined by extensions)
|
|
|
|
// GL_COMPRESSED_RGB_S3TC_DXT1_EXT 0x83F0
|
|
|
|
// GL_COMPRESSED_RGBA_S3TC_DXT1_EXT 0x83F1
|
|
|
|
// GL_COMPRESSED_RGBA_S3TC_DXT3_EXT 0x83F2
|
|
|
|
// GL_COMPRESSED_RGBA_S3TC_DXT5_EXT 0x83F3
|
|
|
|
|
2014-04-19 18:36:49 +04:00
|
|
|
#define FOURCC_DXT1 0x31545844 // Equivalent to "DXT1" in ASCII
|
|
|
|
#define FOURCC_DXT3 0x33545844 // Equivalent to "DXT3" in ASCII
|
|
|
|
#define FOURCC_DXT5 0x35545844 // Equivalent to "DXT5" in ASCII
|
2014-09-03 18:51:28 +04:00
|
|
|
|
2014-04-09 22:25:26 +04:00
|
|
|
// DDS Pixel Format
|
|
|
|
typedef struct {
|
|
|
|
unsigned int size;
|
|
|
|
unsigned int flags;
|
|
|
|
unsigned int fourCC;
|
|
|
|
unsigned int rgbBitCount;
|
|
|
|
unsigned int rBitMask;
|
|
|
|
unsigned int gBitMask;
|
2015-05-05 00:46:31 +03:00
|
|
|
unsigned int bBitMask;
|
2014-04-09 22:25:26 +04:00
|
|
|
unsigned int aBitMask;
|
|
|
|
} ddsPixelFormat;
|
2014-09-03 18:51:28 +04:00
|
|
|
|
2014-04-09 22:25:26 +04:00
|
|
|
// DDS Header (124 bytes)
|
|
|
|
typedef struct {
|
|
|
|
unsigned int size;
|
|
|
|
unsigned int flags;
|
|
|
|
unsigned int height;
|
|
|
|
unsigned int width;
|
|
|
|
unsigned int pitchOrLinearSize;
|
|
|
|
unsigned int depth;
|
2015-05-05 00:46:31 +03:00
|
|
|
unsigned int mipmapCount;
|
2014-04-09 22:25:26 +04:00
|
|
|
unsigned int reserved1[11];
|
|
|
|
ddsPixelFormat ddspf;
|
|
|
|
unsigned int caps;
|
|
|
|
unsigned int caps2;
|
|
|
|
unsigned int caps3;
|
|
|
|
unsigned int caps4;
|
|
|
|
unsigned int reserved2;
|
|
|
|
} ddsHeader;
|
2014-09-03 18:51:28 +04:00
|
|
|
|
2015-05-05 00:46:31 +03:00
|
|
|
Image image;
|
2015-02-02 02:53:49 +03:00
|
|
|
|
2014-12-17 21:47:17 +03:00
|
|
|
image.data = NULL;
|
|
|
|
image.width = 0;
|
|
|
|
image.height = 0;
|
|
|
|
image.mipmaps = 0;
|
2015-04-06 15:02:29 +03:00
|
|
|
image.format = 0;
|
2014-04-09 22:25:26 +04:00
|
|
|
|
|
|
|
FILE *ddsFile = fopen(fileName, "rb");
|
2014-09-03 18:51:28 +04:00
|
|
|
|
2014-04-09 22:25:26 +04:00
|
|
|
if (ddsFile == NULL)
|
|
|
|
{
|
2015-05-05 00:46:31 +03:00
|
|
|
TraceLog(WARNING, "[%s] DDS file could not be opened", fileName);
|
2014-04-09 22:25:26 +04:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Verify the type of file
|
|
|
|
char filecode[4];
|
2014-09-03 18:51:28 +04:00
|
|
|
|
2014-04-09 22:25:26 +04:00
|
|
|
fread(filecode, 1, 4, ddsFile);
|
2014-09-03 18:51:28 +04:00
|
|
|
|
|
|
|
if (strncmp(filecode, "DDS ", 4) != 0)
|
|
|
|
{
|
2014-12-31 20:03:32 +03:00
|
|
|
TraceLog(WARNING, "[%s] DDS file does not seem to be a valid image", fileName);
|
2014-04-09 22:25:26 +04:00
|
|
|
}
|
|
|
|
else
|
2014-09-03 18:51:28 +04:00
|
|
|
{
|
2015-05-05 00:46:31 +03:00
|
|
|
ddsHeader header;
|
|
|
|
|
|
|
|
// Get the image header
|
2014-04-09 22:25:26 +04:00
|
|
|
fread(&header, sizeof(ddsHeader), 1, ddsFile);
|
|
|
|
|
|
|
|
TraceLog(DEBUG, "[%s] DDS file header size: %i", fileName, sizeof(ddsHeader));
|
|
|
|
TraceLog(DEBUG, "[%s] DDS file pixel format size: %i", fileName, header.ddspf.size);
|
|
|
|
TraceLog(DEBUG, "[%s] DDS file pixel format flags: 0x%x", fileName, header.ddspf.flags);
|
2014-04-19 18:36:49 +04:00
|
|
|
TraceLog(DEBUG, "[%s] DDS file format: 0x%x", fileName, header.ddspf.fourCC);
|
2014-09-03 18:51:28 +04:00
|
|
|
|
2014-04-19 18:36:49 +04:00
|
|
|
image.width = header.width;
|
|
|
|
image.height = header.height;
|
2015-05-05 00:46:31 +03:00
|
|
|
image.mipmaps = 1; // Default value, could be changed (header.mipmapCount)
|
2014-09-03 18:51:28 +04:00
|
|
|
|
2015-05-05 00:46:31 +03:00
|
|
|
if (header.ddspf.rgbBitCount == 16) // 16bit mode, no compressed
|
2014-04-19 18:36:49 +04:00
|
|
|
{
|
2015-05-05 00:46:31 +03:00
|
|
|
if (header.ddspf.flags == 0x40) // no alpha channel
|
|
|
|
{
|
|
|
|
image.data = (unsigned short *)malloc(image.width*image.height*sizeof(unsigned short));
|
|
|
|
fread(image.data, image.width*image.height*sizeof(unsigned short), 1, ddsFile);
|
2014-09-03 18:51:28 +04:00
|
|
|
|
2015-05-05 00:46:31 +03:00
|
|
|
image.format = UNCOMPRESSED_R5G6B5;
|
|
|
|
}
|
|
|
|
else if (header.ddspf.flags == 0x41) // with alpha channel
|
2014-04-19 18:36:49 +04:00
|
|
|
{
|
2015-05-05 00:46:31 +03:00
|
|
|
if (header.ddspf.aBitMask == 0x8000) // 1bit alpha
|
|
|
|
{
|
|
|
|
image.data = (unsigned short *)malloc(image.width*image.height*sizeof(unsigned short));
|
|
|
|
fread(image.data, image.width*image.height*sizeof(unsigned short), 1, ddsFile);
|
|
|
|
|
|
|
|
unsigned char alpha = 0;
|
|
|
|
|
|
|
|
// NOTE: Data comes as A1R5G5B5, it must be reordered to R5G5B5A1
|
|
|
|
for (int i = 0; i < image.width*image.height; i++)
|
|
|
|
{
|
|
|
|
alpha = ((unsigned short *)image.data)[i] >> 15;
|
|
|
|
((unsigned short *)image.data)[i] = ((unsigned short *)image.data)[i] << 1;
|
|
|
|
((unsigned short *)image.data)[i] += alpha;
|
|
|
|
}
|
|
|
|
|
|
|
|
image.format = UNCOMPRESSED_R5G5B5A1;
|
|
|
|
}
|
|
|
|
else if (header.ddspf.aBitMask == 0xf000) // 4bit alpha
|
2014-04-19 18:36:49 +04:00
|
|
|
{
|
2015-05-05 00:46:31 +03:00
|
|
|
image.data = (unsigned short *)malloc(image.width*image.height*sizeof(unsigned short));
|
|
|
|
fread(image.data, image.width*image.height*sizeof(unsigned short), 1, ddsFile);
|
|
|
|
|
|
|
|
unsigned char alpha = 0;
|
|
|
|
|
|
|
|
// NOTE: Data comes as A4R4G4B4, it must be reordered R4G4B4A4
|
|
|
|
for (int i = 0; i < image.width*image.height; i++)
|
|
|
|
{
|
|
|
|
alpha = ((unsigned short *)image.data)[i] >> 12;
|
|
|
|
((unsigned short *)image.data)[i] = ((unsigned short *)image.data)[i] << 4;
|
|
|
|
((unsigned short *)image.data)[i] += alpha;
|
|
|
|
}
|
|
|
|
|
|
|
|
image.format = UNCOMPRESSED_R4G4B4A4;
|
2014-04-19 18:36:49 +04:00
|
|
|
}
|
|
|
|
}
|
2015-05-05 00:46:31 +03:00
|
|
|
}
|
|
|
|
if (header.ddspf.flags == 0x40 && header.ddspf.rgbBitCount == 24) // DDS_RGB, no compressed
|
|
|
|
{
|
|
|
|
// NOTE: not sure if this case exists...
|
|
|
|
image.data = (unsigned char *)malloc(image.width*image.height*3*sizeof(unsigned char));
|
|
|
|
fread(image.data, image.width*image.height*3, 1, ddsFile);
|
2015-04-06 15:02:29 +03:00
|
|
|
|
|
|
|
image.format = UNCOMPRESSED_R8G8B8;
|
2014-04-19 18:36:49 +04:00
|
|
|
}
|
|
|
|
else if (header.ddspf.flags == 0x41 && header.ddspf.rgbBitCount == 32) // DDS_RGBA, no compressed
|
|
|
|
{
|
2015-05-05 00:46:31 +03:00
|
|
|
image.data = (unsigned char *)malloc(image.width*image.height*4*sizeof(unsigned char));
|
2014-04-19 18:36:49 +04:00
|
|
|
fread(image.data, image.width*image.height*4, 1, ddsFile);
|
2014-09-03 18:51:28 +04:00
|
|
|
|
2015-04-06 15:02:29 +03:00
|
|
|
image.format = UNCOMPRESSED_R8G8B8A8;
|
2014-04-19 18:36:49 +04:00
|
|
|
}
|
2015-04-06 15:02:29 +03:00
|
|
|
else if (((header.ddspf.flags == 0x04) || (header.ddspf.flags == 0x05)) && (header.ddspf.fourCC > 0))
|
2014-04-19 18:36:49 +04:00
|
|
|
{
|
|
|
|
int bufsize;
|
2014-09-03 18:51:28 +04:00
|
|
|
|
2014-04-19 18:36:49 +04:00
|
|
|
// Calculate data size, including all mipmaps
|
2015-05-05 00:46:31 +03:00
|
|
|
if (header.mipmapCount > 1) bufsize = header.pitchOrLinearSize*2;
|
2014-09-03 18:51:28 +04:00
|
|
|
else bufsize = header.pitchOrLinearSize;
|
2015-04-06 15:02:29 +03:00
|
|
|
|
|
|
|
TraceLog(DEBUG, "Pitch or linear size: %i", header.pitchOrLinearSize);
|
2014-09-03 18:51:28 +04:00
|
|
|
|
2015-05-05 00:46:31 +03:00
|
|
|
image.data = (unsigned char*)malloc(bufsize*sizeof(unsigned char));
|
2014-09-03 18:51:28 +04:00
|
|
|
|
|
|
|
fread(image.data, 1, bufsize, ddsFile);
|
|
|
|
|
2015-05-05 00:46:31 +03:00
|
|
|
image.mipmaps = header.mipmapCount;
|
2014-09-03 18:51:28 +04:00
|
|
|
|
2014-04-19 18:36:49 +04:00
|
|
|
switch(header.ddspf.fourCC)
|
2014-09-03 18:51:28 +04:00
|
|
|
{
|
2015-04-06 15:02:29 +03:00
|
|
|
case FOURCC_DXT1:
|
|
|
|
{
|
|
|
|
if (header.ddspf.flags == 0x04) image.format = COMPRESSED_DXT1_RGB;
|
|
|
|
else image.format = COMPRESSED_DXT1_RGBA;
|
|
|
|
} break;
|
|
|
|
case FOURCC_DXT3: image.format = COMPRESSED_DXT3_RGBA; break;
|
|
|
|
case FOURCC_DXT5: image.format = COMPRESSED_DXT5_RGBA; break;
|
2014-04-19 18:36:49 +04:00
|
|
|
default: break;
|
|
|
|
}
|
|
|
|
}
|
2014-04-09 22:25:26 +04:00
|
|
|
}
|
2015-05-05 00:46:31 +03:00
|
|
|
|
|
|
|
fclose(ddsFile); // Close file pointer
|
2014-04-09 22:25:26 +04:00
|
|
|
}
|
2014-09-03 18:51:28 +04:00
|
|
|
|
2014-09-17 00:51:31 +04:00
|
|
|
return image;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Loading PKM image data (ETC1/ETC2 compression)
|
|
|
|
// NOTE: KTX is the standard Khronos Group compression format (ETC1/ETC2, mipmaps)
|
|
|
|
// PKM is a much simpler file format used mainly to contain a single ETC1/ETC2 compressed image (no mipmaps)
|
2015-05-05 00:46:31 +03:00
|
|
|
static Image LoadPKM(const char *fileName)
|
2014-09-17 00:51:31 +04:00
|
|
|
{
|
2015-05-05 00:46:31 +03:00
|
|
|
// Required extensions:
|
|
|
|
// GL_OES_compressed_ETC1_RGB8_texture (ETC1) (OpenGL ES 2.0)
|
|
|
|
// GL_ARB_ES3_compatibility (ETC2/EAC) (OpenGL ES 3.0)
|
|
|
|
|
|
|
|
// Supported tokens (defined by extensions)
|
|
|
|
// GL_ETC1_RGB8_OES 0x8D64
|
|
|
|
// GL_COMPRESSED_RGB8_ETC2 0x9274
|
|
|
|
// GL_COMPRESSED_RGBA8_ETC2_EAC 0x9278
|
2014-09-17 00:51:31 +04:00
|
|
|
|
|
|
|
// PKM file (ETC1) Header (16 bytes)
|
|
|
|
typedef struct {
|
|
|
|
char id[4]; // "PKM "
|
2015-05-05 00:46:31 +03:00
|
|
|
char version[2]; // "10" or "20"
|
|
|
|
unsigned short format; // Data format (big-endian) (Check list below)
|
|
|
|
unsigned short width; // Texture width (big-endian) (origWidth rounded to multiple of 4)
|
|
|
|
unsigned short height; // Texture height (big-endian) (origHeight rounded to multiple of 4)
|
2014-09-17 00:51:31 +04:00
|
|
|
unsigned short origWidth; // Original width (big-endian)
|
|
|
|
unsigned short origHeight; // Original height (big-endian)
|
|
|
|
} pkmHeader;
|
2015-05-05 00:46:31 +03:00
|
|
|
|
|
|
|
// Formats list
|
|
|
|
// version 10: format: 0=ETC1_RGB, [1=ETC1_RGBA, 2=ETC1_RGB_MIP, 3=ETC1_RGBA_MIP] (not used)
|
|
|
|
// version 20: format: 0=ETC1_RGB, 1=ETC2_RGB, 2=ETC2_RGBA_OLD, 3=ETC2_RGBA, 4=ETC2_RGBA1, 5=ETC2_R, 6=ETC2_RG, 7=ETC2_SIGNED_R, 8=ETC2_SIGNED_R
|
2014-09-17 00:51:31 +04:00
|
|
|
|
|
|
|
// NOTE: The extended width and height are the widths rounded up to a multiple of 4.
|
2015-05-05 00:46:31 +03:00
|
|
|
// NOTE: ETC is always 4bit per pixel (64 bit for each 4x4 block of pixels)
|
2014-09-17 00:51:31 +04:00
|
|
|
|
2015-05-05 00:46:31 +03:00
|
|
|
Image image;
|
|
|
|
|
|
|
|
image.data = NULL;
|
|
|
|
image.width = 0;
|
|
|
|
image.height = 0;
|
|
|
|
image.mipmaps = 0;
|
|
|
|
image.format = 0;
|
2014-09-17 00:51:31 +04:00
|
|
|
|
|
|
|
FILE *pkmFile = fopen(fileName, "rb");
|
|
|
|
|
|
|
|
if (pkmFile == NULL)
|
|
|
|
{
|
2015-05-05 00:46:31 +03:00
|
|
|
TraceLog(WARNING, "[%s] PKM file could not be opened", fileName);
|
2014-09-17 00:51:31 +04:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2015-05-05 00:46:31 +03:00
|
|
|
pkmHeader header;
|
2014-09-17 00:51:31 +04:00
|
|
|
|
2015-05-05 00:46:31 +03:00
|
|
|
// Get the image header
|
|
|
|
fread(&header, sizeof(pkmHeader), 1, pkmFile);
|
2014-09-17 00:51:31 +04:00
|
|
|
|
2015-05-05 00:46:31 +03:00
|
|
|
if (strncmp(header.id, "PKM ", 4) != 0)
|
2014-09-17 00:51:31 +04:00
|
|
|
{
|
2014-12-31 20:03:32 +03:00
|
|
|
TraceLog(WARNING, "[%s] PKM file does not seem to be a valid image", fileName);
|
2014-09-17 00:51:31 +04:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2015-05-05 00:46:31 +03:00
|
|
|
// NOTE: format, width and height come as big-endian, data must be swapped to little-endian
|
|
|
|
header.format = ((header.format & 0x00FF) << 8) | ((header.format & 0xFF00) >> 8);
|
|
|
|
header.width = ((header.width & 0x00FF) << 8) | ((header.width & 0xFF00) >> 8);
|
|
|
|
header.height = ((header.height & 0x00FF) << 8) | ((header.height & 0xFF00) >> 8);
|
|
|
|
|
|
|
|
TraceLog(INFO, "PKM (ETC) image width: %i", header.width);
|
|
|
|
TraceLog(INFO, "PKM (ETC) image height: %i", header.height);
|
|
|
|
TraceLog(INFO, "PKM (ETC) image format: %i", header.format);
|
|
|
|
|
|
|
|
image.width = header.width;
|
|
|
|
image.height = header.height;
|
|
|
|
image.mipmaps = 1;
|
|
|
|
|
|
|
|
int size = image.width*image.height*4/8; // Total data size in bytes
|
2014-09-17 00:51:31 +04:00
|
|
|
|
|
|
|
image.data = (unsigned char*)malloc(size * sizeof(unsigned char));
|
|
|
|
|
|
|
|
fread(image.data, 1, size, pkmFile);
|
|
|
|
|
2015-05-05 00:46:31 +03:00
|
|
|
if (header.format == 0) image.format = COMPRESSED_ETC1_RGB;
|
|
|
|
else if (header.format == 1) image.format = COMPRESSED_ETC2_RGB;
|
|
|
|
else if (header.format == 3) image.format = COMPRESSED_ETC2_EAC_RGBA;
|
2014-09-17 00:51:31 +04:00
|
|
|
}
|
2015-05-05 00:46:31 +03:00
|
|
|
|
|
|
|
fclose(pkmFile); // Close file pointer
|
|
|
|
}
|
|
|
|
|
|
|
|
return image;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Load KTX compressed image data (ETC1/ETC2 compression)
|
|
|
|
static Image LoadKTX(const char *fileName)
|
|
|
|
{
|
|
|
|
// Required extensions:
|
|
|
|
// GL_OES_compressed_ETC1_RGB8_texture (ETC1)
|
|
|
|
// GL_ARB_ES3_compatibility (ETC2/EAC)
|
|
|
|
|
|
|
|
// Supported tokens (defined by extensions)
|
|
|
|
// GL_ETC1_RGB8_OES 0x8D64
|
|
|
|
// GL_COMPRESSED_RGB8_ETC2 0x9274
|
|
|
|
// GL_COMPRESSED_RGBA8_ETC2_EAC 0x9278
|
|
|
|
|
|
|
|
// KTX file Header (64 bytes)
|
|
|
|
// https://www.khronos.org/opengles/sdk/tools/KTX/file_format_spec/
|
|
|
|
typedef struct {
|
|
|
|
char id[12]; // Identifier: "«KTX 11»\r\n\x1A\n"
|
|
|
|
unsigned int endianness; // Little endian: 0x01 0x02 0x03 0x04
|
|
|
|
unsigned int glType; // For compressed textures, glType must equal 0
|
|
|
|
unsigned int glTypeSize; // For compressed texture data, usually 1
|
|
|
|
unsigned int glFormat; // For compressed textures is 0
|
|
|
|
unsigned int glInternalFormat; // Compressed internal format
|
|
|
|
unsigned int glBaseInternalFormat; // Same as glFormat (RGB, RGBA, ALPHA...)
|
|
|
|
unsigned int width; // Texture image width in pixels
|
|
|
|
unsigned int height; // Texture image height in pixels
|
|
|
|
unsigned int depth; // For 2D textures is 0
|
|
|
|
unsigned int elements; // Number of array elements, usually 0
|
|
|
|
unsigned int faces; // Cubemap faces, for no-cubemap = 1
|
|
|
|
unsigned int mipmapLevels; // Non-mipmapped textures = 1
|
|
|
|
unsigned int keyValueDataSize; // Used to encode any arbitrary data...
|
|
|
|
} ktxHeader;
|
|
|
|
|
|
|
|
// NOTE: Before start of every mipmap data block, we have: unsigned int dataSize
|
|
|
|
|
|
|
|
Image image;
|
|
|
|
|
|
|
|
image.width = 0;
|
|
|
|
image.height = 0;
|
|
|
|
image.mipmaps = 0;
|
|
|
|
image.format = 0;
|
|
|
|
|
|
|
|
FILE *ktxFile = fopen(fileName, "rb");
|
|
|
|
|
|
|
|
if (ktxFile == NULL)
|
|
|
|
{
|
|
|
|
TraceLog(WARNING, "[%s] KTX image file could not be opened", fileName);
|
2014-09-17 00:51:31 +04:00
|
|
|
}
|
2015-05-05 00:46:31 +03:00
|
|
|
else
|
|
|
|
{
|
|
|
|
ktxHeader header;
|
|
|
|
|
|
|
|
// Get the image header
|
|
|
|
fread(&header, sizeof(ktxHeader), 1, ktxFile);
|
|
|
|
|
|
|
|
if ((header.id[1] != 'K') || (header.id[2] != 'T') || (header.id[3] != 'X') ||
|
|
|
|
(header.id[4] != ' ') || (header.id[5] != '1') || (header.id[6] != '1'))
|
|
|
|
{
|
|
|
|
TraceLog(WARNING, "[%s] KTX file does not seem to be a valid file", fileName);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
image.width = header.width;
|
|
|
|
image.height = header.height;
|
|
|
|
image.mipmaps = header.mipmapLevels;
|
|
|
|
|
|
|
|
TraceLog(INFO, "KTX (ETC) image width: %i", header.width);
|
|
|
|
TraceLog(INFO, "KTX (ETC) image height: %i", header.height);
|
|
|
|
TraceLog(INFO, "KTX (ETC) image format: 0x%x", header.glInternalFormat);
|
|
|
|
|
|
|
|
unsigned char unused;
|
|
|
|
|
|
|
|
if (header.keyValueDataSize > 0)
|
|
|
|
{
|
|
|
|
for (int i = 0; i < header.keyValueDataSize; i++) fread(&unused, 1, 1, ktxFile);
|
|
|
|
}
|
|
|
|
|
|
|
|
int dataSize;
|
|
|
|
fread(&dataSize, sizeof(unsigned int), 1, ktxFile);
|
|
|
|
|
|
|
|
image.data = (unsigned char*)malloc(dataSize * sizeof(unsigned char));
|
2014-09-17 00:51:31 +04:00
|
|
|
|
2015-05-05 00:46:31 +03:00
|
|
|
fread(image.data, 1, dataSize, ktxFile);
|
|
|
|
|
|
|
|
if (header.glInternalFormat == 0x8D64) image.format = COMPRESSED_ETC1_RGB;
|
|
|
|
else if (header.glInternalFormat == 0x9274) image.format = COMPRESSED_ETC2_RGB;
|
|
|
|
else if (header.glInternalFormat == 0x9278) image.format = COMPRESSED_ETC2_EAC_RGBA;
|
|
|
|
}
|
|
|
|
|
|
|
|
fclose(ktxFile); // Close file pointer
|
|
|
|
}
|
|
|
|
|
2015-04-13 21:15:28 +03:00
|
|
|
return image;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Loading PVR image data (uncompressed or PVRT compression)
|
2015-05-05 00:46:31 +03:00
|
|
|
// NOTE: PVR v2 not supported, use PVR v3 instead
|
|
|
|
static Image LoadPVR(const char *fileName)
|
2015-04-13 21:15:28 +03:00
|
|
|
{
|
2015-05-05 00:46:31 +03:00
|
|
|
// Required extension:
|
|
|
|
// GL_IMG_texture_compression_pvrtc
|
|
|
|
|
|
|
|
// Supported tokens (defined by extensions)
|
|
|
|
// GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG 0x8C00
|
|
|
|
// GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG 0x8C02
|
2015-04-13 21:15:28 +03:00
|
|
|
|
|
|
|
// PVR file v2 Header (52 bytes)
|
|
|
|
typedef struct {
|
|
|
|
unsigned int headerLength;
|
|
|
|
unsigned int height;
|
|
|
|
unsigned int width;
|
|
|
|
unsigned int numMipmaps;
|
|
|
|
unsigned int flags;
|
|
|
|
unsigned int dataLength;
|
|
|
|
unsigned int bpp;
|
|
|
|
unsigned int bitmaskRed;
|
|
|
|
unsigned int bitmaskGreen;
|
|
|
|
unsigned int bitmaskBlue;
|
|
|
|
unsigned int bitmaskAlpha;
|
|
|
|
unsigned int pvrTag;
|
|
|
|
unsigned int numSurfs;
|
|
|
|
} pvrHeaderV2;
|
|
|
|
|
|
|
|
// PVR file v3 Header (52 bytes)
|
|
|
|
// NOTE: After it could be metadata (15 bytes?)
|
|
|
|
typedef struct {
|
2015-05-05 00:46:31 +03:00
|
|
|
char id[4];
|
|
|
|
unsigned int flags;
|
|
|
|
unsigned char channels[4]; // pixelFormat high part
|
|
|
|
unsigned char channelDepth[4]; // pixelFormat low part
|
|
|
|
unsigned int colourSpace;
|
|
|
|
unsigned int channelType;
|
|
|
|
unsigned int height;
|
|
|
|
unsigned int width;
|
|
|
|
unsigned int depth;
|
|
|
|
unsigned int numSurfaces;
|
|
|
|
unsigned int numFaces;
|
|
|
|
unsigned int numMipmaps;
|
|
|
|
unsigned int metaDataSize;
|
2015-04-13 21:15:28 +03:00
|
|
|
} pvrHeaderV3;
|
|
|
|
|
2015-05-05 00:46:31 +03:00
|
|
|
// Metadata (usually 15 bytes)
|
|
|
|
typedef struct {
|
|
|
|
unsigned int devFOURCC;
|
|
|
|
unsigned int key;
|
|
|
|
unsigned int dataSize; // Not used?
|
|
|
|
unsigned char *data; // Not used?
|
|
|
|
} pvrMetadata;
|
2015-04-13 21:15:28 +03:00
|
|
|
|
2015-05-05 00:46:31 +03:00
|
|
|
Image image;
|
2015-04-13 21:15:28 +03:00
|
|
|
|
|
|
|
image.data = NULL;
|
|
|
|
image.width = 0;
|
|
|
|
image.height = 0;
|
|
|
|
image.mipmaps = 0;
|
|
|
|
image.format = 0;
|
|
|
|
|
|
|
|
FILE *pvrFile = fopen(fileName, "rb");
|
|
|
|
|
|
|
|
if (pvrFile == NULL)
|
|
|
|
{
|
2015-05-05 00:46:31 +03:00
|
|
|
TraceLog(WARNING, "[%s] PVR file could not be opened", fileName);
|
2015-04-13 21:15:28 +03:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Check PVR image version
|
2015-05-05 00:46:31 +03:00
|
|
|
unsigned char pvrVersion = 0;
|
|
|
|
fread(&pvrVersion, sizeof(unsigned char), 1, pvrFile);
|
|
|
|
fseek(pvrFile, 0, SEEK_SET);
|
2015-04-13 21:15:28 +03:00
|
|
|
|
2015-05-05 00:46:31 +03:00
|
|
|
// Load different PVR data formats
|
|
|
|
if (pvrVersion == 0x50)
|
2015-04-13 21:15:28 +03:00
|
|
|
{
|
|
|
|
pvrHeaderV3 header;
|
|
|
|
|
2015-05-05 00:46:31 +03:00
|
|
|
// Get PVR image header
|
2015-04-13 21:15:28 +03:00
|
|
|
fread(&header, sizeof(pvrHeaderV3), 1, pvrFile);
|
|
|
|
|
2015-05-05 00:46:31 +03:00
|
|
|
if ((header.id[0] != 'P') || (header.id[1] != 'V') || (header.id[2] != 'R') || (header.id[3] != 3))
|
|
|
|
{
|
|
|
|
TraceLog(WARNING, "[%s] PVR file does not seem to be a valid image", fileName);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
image.width = header.width;
|
|
|
|
image.height = header.height;
|
|
|
|
image.mipmaps = header.numMipmaps + 1;
|
|
|
|
|
|
|
|
// Check data format
|
|
|
|
if (((header.channels[0] == 'l') && (header.channels[1] == 0)) && (header.channelDepth[0] == 8)) image.format = UNCOMPRESSED_GRAYSCALE;
|
|
|
|
else if (((header.channels[0] == 'l') && (header.channels[1] == 'a')) && ((header.channelDepth[0] == 8) && (header.channelDepth[1] == 8))) image.format = UNCOMPRESSED_GRAY_ALPHA;
|
|
|
|
else if ((header.channels[0] == 'r') && (header.channels[1] == 'g') && (header.channels[2] == 'b'))
|
|
|
|
{
|
|
|
|
if (header.channels[3] == 'a')
|
|
|
|
{
|
|
|
|
if ((header.channelDepth[0] == 5) && (header.channelDepth[1] == 5) && (header.channelDepth[2] == 5) && (header.channelDepth[3] == 1)) image.format = UNCOMPRESSED_R5G5B5A1;
|
|
|
|
else if ((header.channelDepth[0] == 4) && (header.channelDepth[1] == 4) && (header.channelDepth[2] == 4) && (header.channelDepth[3] == 4)) image.format = UNCOMPRESSED_R4G4B4A4;
|
|
|
|
else if ((header.channelDepth[0] == 8) && (header.channelDepth[1] == 8) && (header.channelDepth[2] == 8) && (header.channelDepth[3] == 8)) image.format = UNCOMPRESSED_R8G8B8A8;
|
|
|
|
}
|
|
|
|
else if (header.channels[3] == 0)
|
|
|
|
{
|
|
|
|
if ((header.channelDepth[0] == 5) && (header.channelDepth[1] == 6) && (header.channelDepth[2] == 5)) image.format = UNCOMPRESSED_R5G6B5;
|
|
|
|
else if ((header.channelDepth[0] == 8) && (header.channelDepth[1] == 8) && (header.channelDepth[2] == 8)) image.format = UNCOMPRESSED_R8G8B8;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (header.channels[0] == 2) image.format = COMPRESSED_PVRT_RGB;
|
|
|
|
else if (header.channels[0] == 3) image.format = COMPRESSED_PVRT_RGBA;
|
|
|
|
|
|
|
|
// Skip meta data header
|
|
|
|
unsigned char unused = 0;
|
|
|
|
for (int i = 0; i < header.metaDataSize; i++) fread(&unused, sizeof(unsigned char), 1, pvrFile);
|
|
|
|
|
|
|
|
// Calculate data size (depends on format)
|
|
|
|
int bpp = 0;
|
|
|
|
|
|
|
|
switch (image.format)
|
|
|
|
{
|
|
|
|
case UNCOMPRESSED_GRAYSCALE: bpp = 8; break;
|
|
|
|
case UNCOMPRESSED_GRAY_ALPHA:
|
|
|
|
case UNCOMPRESSED_R5G5B5A1:
|
|
|
|
case UNCOMPRESSED_R5G6B5:
|
|
|
|
case UNCOMPRESSED_R4G4B4A4: bpp = 16; break;
|
|
|
|
case UNCOMPRESSED_R8G8B8A8: bpp = 32; break;
|
|
|
|
case UNCOMPRESSED_R8G8B8: bpp = 24; break;
|
|
|
|
case COMPRESSED_PVRT_RGB:
|
|
|
|
case COMPRESSED_PVRT_RGBA: bpp = 4; break;
|
|
|
|
default: break;
|
|
|
|
}
|
|
|
|
|
|
|
|
int dataSize = image.width*image.height*bpp/8; // Total data size in bytes
|
|
|
|
image.data = (unsigned char*)malloc(dataSize*sizeof(unsigned char));
|
|
|
|
|
|
|
|
// Read data from file
|
|
|
|
fread(image.data, dataSize, 1, pvrFile);
|
|
|
|
}
|
2015-04-13 21:15:28 +03:00
|
|
|
}
|
2015-05-05 00:46:31 +03:00
|
|
|
else if (pvrVersion == 52) TraceLog(INFO, "PVR v2 not supported, update your files to PVR v3");
|
2015-04-13 21:15:28 +03:00
|
|
|
|
2015-05-05 00:46:31 +03:00
|
|
|
fclose(pvrFile); // Close file pointer
|
|
|
|
}
|
2015-04-13 21:15:28 +03:00
|
|
|
|
2015-05-05 00:46:31 +03:00
|
|
|
return image;
|
|
|
|
}
|
2015-04-13 21:15:28 +03:00
|
|
|
|
2015-05-05 00:46:31 +03:00
|
|
|
// Load ASTC compressed image data (ASTC compression)
|
|
|
|
static Image LoadASTC(const char *fileName)
|
|
|
|
{
|
|
|
|
// Required extensions:
|
|
|
|
// GL_KHR_texture_compression_astc_hdr
|
|
|
|
// GL_KHR_texture_compression_astc_ldr
|
|
|
|
|
|
|
|
// Supported tokens (defined by extensions)
|
|
|
|
// GL_COMPRESSED_RGBA_ASTC_4x4_KHR 0x93b0
|
|
|
|
// GL_COMPRESSED_RGBA_ASTC_8x8_KHR 0x93b7
|
|
|
|
|
|
|
|
// ASTC file Header (16 bytes)
|
|
|
|
typedef struct {
|
|
|
|
unsigned char id[4]; // Signature: 0x13 0xAB 0xA1 0x5C
|
|
|
|
unsigned char blockX; // Block X dimensions
|
|
|
|
unsigned char blockY; // Block Y dimensions
|
|
|
|
unsigned char blockZ; // Block Z dimensions (1 for 2D images)
|
|
|
|
unsigned char width[3]; // Image width in pixels (24bit value)
|
|
|
|
unsigned char height[3]; // Image height in pixels (24bit value)
|
|
|
|
unsigned char lenght[3]; // Image Z-size (1 for 2D images)
|
|
|
|
} astcHeader;
|
2015-04-13 21:15:28 +03:00
|
|
|
|
2015-05-05 00:46:31 +03:00
|
|
|
Image image;
|
|
|
|
|
|
|
|
image.data = NULL;
|
|
|
|
image.width = 0;
|
|
|
|
image.height = 0;
|
|
|
|
image.mipmaps = 0;
|
|
|
|
image.format = 0;
|
|
|
|
|
|
|
|
FILE *astcFile = fopen(fileName, "rb");
|
|
|
|
|
|
|
|
if (astcFile == NULL)
|
|
|
|
{
|
|
|
|
TraceLog(WARNING, "[%s] ASTC file could not be opened", fileName);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
astcHeader header;
|
|
|
|
|
|
|
|
// Get ASTC image header
|
|
|
|
fread(&header, sizeof(astcHeader), 1, astcFile);
|
|
|
|
|
|
|
|
if ((header.id[3] != 0x5c) || (header.id[2] != 0xa1) || (header.id[1] != 0xab) || (header.id[0] != 0x13))
|
|
|
|
{
|
|
|
|
TraceLog(WARNING, "[%s] ASTC file does not seem to be a valid image", fileName);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
image.width = 0x00000000 | ((int)header.width[0] << 16) | ((int)header.width[1] << 8) | ((int)header.width[2]);
|
|
|
|
image.height = 0x00000000 | ((int)header.height[0] << 16) | ((int)header.height[1] << 8) | ((int)header.height[2]);
|
|
|
|
image.mipmaps = 1;
|
|
|
|
|
|
|
|
TraceLog(DEBUG, "ASTC image width: %i", image.width);
|
|
|
|
TraceLog(DEBUG, "ASTC image height: %i", image.height);
|
|
|
|
TraceLog(DEBUG, "ASTC image blocks: %ix%i", header.blockX, header.blockY);
|
|
|
|
|
|
|
|
// NOTE: Each block is always stored in 128bit so we can calculate the bpp
|
|
|
|
int bpp = 128/(header.blockX*header.blockY);
|
|
|
|
|
|
|
|
// NOTE: Currently we only support 2 blocks configurations: 4x4 and 8x8
|
|
|
|
if ((bpp == 8) || (bpp == 2))
|
|
|
|
{
|
|
|
|
int dataSize = image.width*image.height*bpp/8; // Data size in bytes
|
|
|
|
|
|
|
|
image.data = (unsigned char *)malloc(dataSize*sizeof(unsigned char));
|
|
|
|
fread(image.data, dataSize, 1, astcFile);
|
|
|
|
|
|
|
|
if (bpp == 8) image.format = COMPRESSED_ASTC_4x4_RGBA;
|
|
|
|
else if (bpp == 2) image.format = COMPRESSED_ASTC_4x4_RGBA;
|
|
|
|
}
|
|
|
|
else TraceLog(WARNING, "[%s] ASTC block size configuration not supported", fileName);
|
|
|
|
}
|
|
|
|
|
|
|
|
fclose(astcFile);
|
2015-04-13 21:15:28 +03:00
|
|
|
}
|
|
|
|
|
2014-04-09 22:25:26 +04:00
|
|
|
return image;
|
2013-11-19 02:38:44 +04:00
|
|
|
}
|