diff options
Diffstat (limited to 'libs/raylib/src/textures.c')
-rw-r--r-- | libs/raylib/src/textures.c | 4430 |
1 files changed, 2618 insertions, 1812 deletions
diff --git a/libs/raylib/src/textures.c b/libs/raylib/src/textures.c index 1bf086b..de0d76b 100644 --- a/libs/raylib/src/textures.c +++ b/libs/raylib/src/textures.c @@ -38,7 +38,7 @@ * * LICENSE: zlib/libpng * -* Copyright (c) 2013-2020 Ramon Santamaria (@raysan5) +* Copyright (c) 2013-2021 Ramon Santamaria (@raysan5) * * This software is provided "as-is", without any express or implied warranty. In no event * will the authors be held liable for any damages arising from the use of this software. @@ -65,14 +65,13 @@ #endif #include <stdlib.h> // Required for: malloc(), free() -#include <stdio.h> // Required for: FILE, fopen(), fclose(), fread() #include <string.h> // Required for: strlen() [Used in ImageTextEx()] #include <math.h> // Required for: fabsf() #include "utils.h" // Required for: fopen() Android mapping #include "rlgl.h" // raylib OpenGL abstraction layer to OpenGL 1.1, 3.3 or ES2 - // Required for: rlLoadTexture() rlDeleteTextures(), + // Required for: rlLoadTexture() rlUnloadTexture(), // rlGenerateMipmaps(), some funcs for DrawTexturePro() // Support only desired texture formats on stb_image @@ -123,7 +122,7 @@ // NOTE: Used to read image data (multiple formats support) #endif -#if defined(SUPPORT_IMAGE_EXPORT) +#if (defined(SUPPORT_IMAGE_EXPORT) || defined(SUPPORT_COMPRESSION_API)) #define STBIW_MALLOC RL_MALLOC #define STBIW_FREE RL_FREE #define STBIW_REALLOC RL_REALLOC @@ -148,7 +147,9 @@ //---------------------------------------------------------------------------------- // Defines and Macros //---------------------------------------------------------------------------------- -// Nop... +#ifndef PIXELFORMAT_UNCOMPRESSED_R5G5B5A1_ALPHA_THRESHOLD + #define PIXELFORMAT_UNCOMPRESSED_R5G5B5A1_ALPHA_THRESHOLD 50 // Threshold over 255 to set alpha as 0 +#endif //---------------------------------------------------------------------------------- // Types and Structures Definition @@ -168,25 +169,23 @@ //---------------------------------------------------------------------------------- // Module specific Functions Declaration //---------------------------------------------------------------------------------- -#if defined(SUPPORT_FILEFORMAT_GIF) -static Image LoadAnimatedGIF(const char *fileName, int *frames, int **delays); // Load animated GIF file -#endif #if defined(SUPPORT_FILEFORMAT_DDS) -static Image LoadDDS(const char *fileName); // Load DDS file +static Image LoadDDS(const unsigned char *fileData, unsigned int fileSize); // Load DDS file data #endif #if defined(SUPPORT_FILEFORMAT_PKM) -static Image LoadPKM(const char *fileName); // Load PKM file +static Image LoadPKM(const unsigned char *fileData, unsigned int fileSize); // Load PKM file data #endif #if defined(SUPPORT_FILEFORMAT_KTX) -static Image LoadKTX(const char *fileName); // Load KTX file +static Image LoadKTX(const unsigned char *fileData, unsigned int fileSize); // Load KTX file data static int SaveKTX(Image image, const char *fileName); // Save image data as KTX file #endif #if defined(SUPPORT_FILEFORMAT_PVR) -static Image LoadPVR(const char *fileName); // Load PVR file +static Image LoadPVR(const unsigned char *fileData, unsigned int fileSize); // Load PVR file data #endif #if defined(SUPPORT_FILEFORMAT_ASTC) -static Image LoadASTC(const char *fileName); // Load ASTC file +static Image LoadASTC(const unsigned char *fileData, unsigned int fileSize); // Load ASTC file data #endif +static Vector4 *LoadImageDataNormalized(Image image); // Load pixel data from image as Vector4 array (float normalized) //---------------------------------------------------------------------------------- // Module Functions Definition @@ -200,6 +199,7 @@ Image LoadImage(const char *fileName) #if defined(SUPPORT_FILEFORMAT_PNG) || \ defined(SUPPORT_FILEFORMAT_BMP) || \ defined(SUPPORT_FILEFORMAT_TGA) || \ + defined(SUPPORT_FILEFORMAT_JPG) || \ defined(SUPPORT_FILEFORMAT_GIF) || \ defined(SUPPORT_FILEFORMAT_PIC) || \ defined(SUPPORT_FILEFORMAT_HDR) || \ @@ -207,37 +207,129 @@ Image LoadImage(const char *fileName) #define STBI_REQUIRED #endif + // Loading file to memory + unsigned int fileSize = 0; + unsigned char *fileData = LoadFileData(fileName, &fileSize); + + if (fileData != NULL) + { + // Loading image from memory data + image = LoadImageFromMemory(GetFileExtension(fileName), fileData, fileSize); + + if (image.data != NULL) TRACELOG(LOG_INFO, "IMAGE: [%s] Data loaded successfully (%ix%i)", fileName, image.width, image.height); + else TRACELOG(LOG_WARNING, "IMAGE: [%s] Failed to load data", fileName); + + RL_FREE(fileData); + } + + return image; +} + +// Load an image from RAW file data +Image LoadImageRaw(const char *fileName, int width, int height, int format, int headerSize) +{ + Image image = { 0 }; + + unsigned int dataSize = 0; + unsigned char *fileData = LoadFileData(fileName, &dataSize); + + if (fileData != NULL) + { + unsigned char *dataPtr = fileData; + unsigned int size = GetPixelDataSize(width, height, format); + + if (headerSize > 0) dataPtr += headerSize; + + image.data = RL_MALLOC(size); // Allocate required memory in bytes + memcpy(image.data, dataPtr, size); // Copy required data to image + image.width = width; + image.height = height; + image.mipmaps = 1; + image.format = format; + + RL_FREE(fileData); + } + + return image; +} + +// Load animated image data +// - Image.data buffer includes all frames: [image#0][image#1][image#2][...] +// - Number of frames is returned through 'frames' parameter +// - All frames are returned in RGBA format +// - Frames delay data is discarded +Image LoadImageAnim(const char *fileName, int *frames) +{ + Image image = { 0 }; + int framesCount = 1; + +#if defined(SUPPORT_FILEFORMAT_GIF) + if (IsFileExtension(fileName, ".gif")) + { + unsigned int dataSize = 0; + unsigned char *fileData = LoadFileData(fileName, &dataSize); + + if (fileData != NULL) + { + int comp = 0; + int **delays = NULL; + image.data = stbi_load_gif_from_memory(fileData, dataSize, delays, &image.width, &image.height, &framesCount, &comp, 4); + + image.mipmaps = 1; + image.format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8; + + RL_FREE(fileData); + RL_FREE(delays); // NOTE: Frames delays are discarded + } + } +#else + if (false) { } +#endif + else image = LoadImage(fileName); + + // TODO: Support APNG animated images? + + *frames = framesCount; + return image; +} + +// Load image from memory buffer, fileType refers to extension: i.e. ".png" +Image LoadImageFromMemory(const char *fileType, const unsigned char *fileData, int dataSize) +{ + Image image = { 0 }; + + char fileExtLower[16] = { 0 }; + strcpy(fileExtLower, TextToLower(fileType)); + #if defined(SUPPORT_FILEFORMAT_PNG) - if ((IsFileExtension(fileName, ".png")) + if ((TextIsEqual(fileExtLower, ".png")) #else if ((false) #endif #if defined(SUPPORT_FILEFORMAT_BMP) - || (IsFileExtension(fileName, ".bmp")) + || (TextIsEqual(fileExtLower, ".bmp")) #endif #if defined(SUPPORT_FILEFORMAT_TGA) - || (IsFileExtension(fileName, ".tga")) + || (TextIsEqual(fileExtLower, ".tga")) #endif #if defined(SUPPORT_FILEFORMAT_JPG) - || (IsFileExtension(fileName, ".jpg")) + || (TextIsEqual(fileExtLower, ".jpg") || + TextIsEqual(fileExtLower, ".jpeg")) #endif #if defined(SUPPORT_FILEFORMAT_GIF) - || (IsFileExtension(fileName, ".gif")) + || (TextIsEqual(fileExtLower, ".gif")) #endif #if defined(SUPPORT_FILEFORMAT_PIC) - || (IsFileExtension(fileName, ".pic")) + || (TextIsEqual(fileExtLower, ".pic")) #endif #if defined(SUPPORT_FILEFORMAT_PSD) - || (IsFileExtension(fileName, ".psd")) + || (TextIsEqual(fileExtLower, ".psd")) #endif ) { #if defined(STBI_REQUIRED) // NOTE: Using stb_image to load images (Supports multiple image formats) - unsigned int dataSize = 0; - unsigned char *fileData = LoadFileData(fileName, &dataSize); - if (fileData != NULL) { int comp = 0; @@ -245,22 +337,17 @@ Image LoadImage(const char *fileName) image.mipmaps = 1; - if (comp == 1) image.format = UNCOMPRESSED_GRAYSCALE; - else if (comp == 2) image.format = UNCOMPRESSED_GRAY_ALPHA; - else if (comp == 3) image.format = UNCOMPRESSED_R8G8B8; - else if (comp == 4) image.format = UNCOMPRESSED_R8G8B8A8; - - RL_FREE(fileData); + if (comp == 1) image.format = PIXELFORMAT_UNCOMPRESSED_GRAYSCALE; + else if (comp == 2) image.format = PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA; + else if (comp == 3) image.format = PIXELFORMAT_UNCOMPRESSED_R8G8B8; + else if (comp == 4) image.format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8; } #endif } #if defined(SUPPORT_FILEFORMAT_HDR) - else if (IsFileExtension(fileName, ".hdr")) + else if (TextIsEqual(fileExtLower, ".hdr")) { #if defined(STBI_REQUIRED) - unsigned int dataSize = 0; - unsigned char *fileData = LoadFileData(fileName, &dataSize); - if (fileData != NULL) { int comp = 0; @@ -268,625 +355,416 @@ Image LoadImage(const char *fileName) image.mipmaps = 1; - if (comp == 1) image.format = UNCOMPRESSED_R32; - else if (comp == 3) image.format = UNCOMPRESSED_R32G32B32; - else if (comp == 4) image.format = UNCOMPRESSED_R32G32B32A32; + if (comp == 1) image.format = PIXELFORMAT_UNCOMPRESSED_R32; + else if (comp == 3) image.format = PIXELFORMAT_UNCOMPRESSED_R32G32B32; + else if (comp == 4) image.format = PIXELFORMAT_UNCOMPRESSED_R32G32B32A32; else { - TRACELOG(LOG_WARNING, "IMAGE: [%s] HDR fileformat not supported", fileName); + TRACELOG(LOG_WARNING, "IMAGE: HDR file format not supported"); UnloadImage(image); } - - RL_FREE(fileData); } #endif } #endif #if defined(SUPPORT_FILEFORMAT_DDS) - else if (IsFileExtension(fileName, ".dds")) image = LoadDDS(fileName); + else if (TextIsEqual(fileExtLower, ".dds")) image = LoadDDS(fileData, dataSize); #endif #if defined(SUPPORT_FILEFORMAT_PKM) - else if (IsFileExtension(fileName, ".pkm")) image = LoadPKM(fileName); + else if (TextIsEqual(fileExtLower, ".pkm")) image = LoadPKM(fileData, dataSize); #endif #if defined(SUPPORT_FILEFORMAT_KTX) - else if (IsFileExtension(fileName, ".ktx")) image = LoadKTX(fileName); + else if (TextIsEqual(fileExtLower, ".ktx")) image = LoadKTX(fileData, dataSize); #endif #if defined(SUPPORT_FILEFORMAT_PVR) - else if (IsFileExtension(fileName, ".pvr")) image = LoadPVR(fileName); + else if (TextIsEqual(fileExtLower, ".pvr")) image = LoadPVR(fileData, dataSize); #endif #if defined(SUPPORT_FILEFORMAT_ASTC) - else if (IsFileExtension(fileName, ".astc")) image = LoadASTC(fileName); + else if (TextIsEqual(fileExtLower, ".astc")) image = LoadASTC(fileData, dataSize); #endif - else TRACELOG(LOG_WARNING, "IMAGE: [%s] Fileformat not supported", fileName); - - if (image.data != NULL) TRACELOG(LOG_INFO, "IMAGE: [%s] Data loaded successfully (%ix%i)", fileName, image.width, image.height); - else TRACELOG(LOG_WARNING, "IMAGE: [%s] Failed to load data", fileName); + else TRACELOG(LOG_WARNING, "IMAGE: File format not supported"); return image; } -// Load image from Color array data (RGBA - 32bit) -// NOTE: Creates a copy of pixels data array -Image LoadImageEx(Color *pixels, int width, int height) +// Unload image from CPU memory (RAM) +void UnloadImage(Image image) { - Image image = { 0 }; - image.data = NULL; - image.width = width; - image.height = height; - image.mipmaps = 1; - image.format = UNCOMPRESSED_R8G8B8A8; - - int k = 0; + RL_FREE(image.data); +} - image.data = (unsigned char *)RL_MALLOC(image.width*image.height*4*sizeof(unsigned char)); +// Export image data to file +// NOTE: File format depends on fileName extension +bool ExportImage(Image image, const char *fileName) +{ + int success = 0; - for (int i = 0; i < image.width*image.height*4; i += 4) +#if defined(SUPPORT_IMAGE_EXPORT) + int channels = 4; + bool allocatedData = false; + unsigned char *imgData = (unsigned char *)image.data; + + if (image.format == PIXELFORMAT_UNCOMPRESSED_GRAYSCALE) channels = 1; + else if (image.format == PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA) channels = 2; + else if (image.format == PIXELFORMAT_UNCOMPRESSED_R8G8B8) channels = 3; + else if (image.format == PIXELFORMAT_UNCOMPRESSED_R8G8B8A8) channels = 4; + else { - ((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++; + // NOTE: Getting Color array as RGBA unsigned char values + imgData = (unsigned char *)LoadImageColors(image); + allocatedData = true; } - return image; -} - -// Load image from raw data with parameters -// NOTE: This functions makes a copy of provided data -Image LoadImagePro(void *data, int width, int height, int format) -{ - Image srcImage = { 0 }; +#if defined(SUPPORT_FILEFORMAT_PNG) + if (IsFileExtension(fileName, ".png")) success = stbi_write_png(fileName, image.width, image.height, channels, imgData, image.width*channels); +#else + if (false) {} +#endif +#if defined(SUPPORT_FILEFORMAT_BMP) + else if (IsFileExtension(fileName, ".bmp")) success = stbi_write_bmp(fileName, image.width, image.height, channels, imgData); +#endif +#if defined(SUPPORT_FILEFORMAT_TGA) + else if (IsFileExtension(fileName, ".tga")) success = stbi_write_tga(fileName, image.width, image.height, channels, imgData); +#endif +#if defined(SUPPORT_FILEFORMAT_JPG) + else if (IsFileExtension(fileName, ".jpg")) success = stbi_write_jpg(fileName, image.width, image.height, channels, imgData, 90); // JPG quality: between 1 and 100 +#endif +#if defined(SUPPORT_FILEFORMAT_KTX) + else if (IsFileExtension(fileName, ".ktx")) success = SaveKTX(image, fileName); +#endif + else if (IsFileExtension(fileName, ".raw")) + { + // Export raw pixel data (without header) + // NOTE: It's up to the user to track image parameters + success = SaveFileData(fileName, image.data, GetPixelDataSize(image.width, image.height, image.format)); + } - srcImage.data = data; - srcImage.width = width; - srcImage.height = height; - srcImage.mipmaps = 1; - srcImage.format = format; + if (allocatedData) RL_FREE(imgData); +#endif // SUPPORT_IMAGE_EXPORT - Image dstImage = ImageCopy(srcImage); + if (success != 0) TRACELOG(LOG_INFO, "FILEIO: [%s] Image exported successfully", fileName); + else TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to export image", fileName); - return dstImage; + return success; } -// Load an image from RAW file data -Image LoadImageRaw(const char *fileName, int width, int height, int format, int headerSize) +// Export image as code file (.h) defining an array of bytes +bool ExportImageAsCode(Image image, const char *fileName) { - Image image = { 0 }; + bool success = false; - unsigned int dataSize = 0; - unsigned char *fileData = LoadFileData(fileName, &dataSize); +#ifndef TEXT_BYTES_PER_LINE + #define TEXT_BYTES_PER_LINE 20 +#endif - if (fileData != NULL) - { - unsigned char *dataPtr = fileData; - unsigned int size = GetPixelDataSize(width, height, format); + int dataSize = GetPixelDataSize(image.width, image.height, image.format); - if (headerSize > 0) dataPtr += headerSize; + // NOTE: Text data buffer size is estimated considering image data size in bytes + // and requiring 6 char bytes for every byte: "0x00, " + char *txtData = (char *)RL_CALLOC(6*dataSize + 2000, sizeof(char)); - image.data = RL_MALLOC(size); // Allocate required memory in bytes - memcpy(image.data, dataPtr, size); // Copy required data to image - image.width = width; - image.height = height; - image.mipmaps = 1; - image.format = format; + int bytesCount = 0; + bytesCount += sprintf(txtData + bytesCount, "////////////////////////////////////////////////////////////////////////////////////////\n"); + bytesCount += sprintf(txtData + bytesCount, "// //\n"); + bytesCount += sprintf(txtData + bytesCount, "// ImageAsCode exporter v1.0 - Image pixel data exported as an array of bytes //\n"); + bytesCount += sprintf(txtData + bytesCount, "// //\n"); + bytesCount += sprintf(txtData + bytesCount, "// more info and bugs-report: github.com/raysan5/raylib //\n"); + bytesCount += sprintf(txtData + bytesCount, "// feedback and support: ray[at]raylib.com //\n"); + bytesCount += sprintf(txtData + bytesCount, "// //\n"); + bytesCount += sprintf(txtData + bytesCount, "// Copyright (c) 2020 Ramon Santamaria (@raysan5) //\n"); + bytesCount += sprintf(txtData + bytesCount, "// //\n"); + bytesCount += sprintf(txtData + bytesCount, "////////////////////////////////////////////////////////////////////////////////////////\n\n"); - RL_FREE(fileData); - } + // Get file name from path and convert variable name to uppercase + char varFileName[256] = { 0 }; + strcpy(varFileName, GetFileNameWithoutExt(fileName)); + for (int i = 0; varFileName[i] != '\0'; i++) if ((varFileName[i] >= 'a') && (varFileName[i] <= 'z')) { varFileName[i] = varFileName[i] - 32; } - return image; -} + // Add image information + bytesCount += sprintf(txtData + bytesCount, "// Image data information\n"); + bytesCount += sprintf(txtData + bytesCount, "#define %s_WIDTH %i\n", varFileName, image.width); + bytesCount += sprintf(txtData + bytesCount, "#define %s_HEIGHT %i\n", varFileName, image.height); + bytesCount += sprintf(txtData + bytesCount, "#define %s_FORMAT %i // raylib internal pixel format\n\n", varFileName, image.format); -// Load texture from file into GPU memory (VRAM) -Texture2D LoadTexture(const char *fileName) -{ - Texture2D texture = { 0 }; + bytesCount += sprintf(txtData + bytesCount, "static unsigned char %s_DATA[%i] = { ", varFileName, dataSize); + for (int i = 0; i < dataSize - 1; i++) bytesCount += sprintf(txtData + bytesCount, ((i%TEXT_BYTES_PER_LINE == 0)? "0x%x,\n" : "0x%x, "), ((unsigned char *)image.data)[i]); + bytesCount += sprintf(txtData + bytesCount, "0x%x };\n", ((unsigned char *)image.data)[dataSize - 1]); - Image image = LoadImage(fileName); + // NOTE: Text data length exported is determined by '\0' (NULL) character + success = SaveFileText(fileName, txtData); - if (image.data != NULL) - { - texture = LoadTextureFromImage(image); - UnloadImage(image); - } + RL_FREE(txtData); - return texture; + return success; } -// Load a texture from image data -// NOTE: image is not unloaded, it must be done manually -Texture2D LoadTextureFromImage(Image image) +//------------------------------------------------------------------------------------ +// Image generation functions +//------------------------------------------------------------------------------------ +// Generate image: plain color +Image GenImageColor(int width, int height, Color color) { - Texture2D texture = { 0 }; - - if ((image.data != NULL) && (image.width != 0) && (image.height != 0)) - { - texture.id = rlLoadTexture(image.data, image.width, image.height, image.format, image.mipmaps); - } - else TRACELOG(LOG_WARNING, "IMAGE: Data is not valid to load texture"); - - texture.width = image.width; - texture.height = image.height; - texture.mipmaps = image.mipmaps; - texture.format = image.format; + Color *pixels = (Color *)RL_CALLOC(width*height, sizeof(Color)); - return texture; -} + for (int i = 0; i < width*height; i++) pixels[i] = color; -// Load texture for rendering (framebuffer) -// NOTE: Render texture is loaded by default with RGBA color attachment and depth RenderBuffer -RenderTexture2D LoadRenderTexture(int width, int height) -{ - RenderTexture2D target = rlLoadRenderTexture(width, height, UNCOMPRESSED_R8G8B8A8, 24, false); + Image image = { + .data = pixels, + .width = width, + .height = height, + .format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8, + .mipmaps = 1 + }; - return target; + return image; } -// Unload image from CPU memory (RAM) -void UnloadImage(Image image) +#if defined(SUPPORT_IMAGE_GENERATION) +// Generate image: vertical gradient +Image GenImageGradientV(int width, int height, Color top, Color bottom) { - RL_FREE(image.data); -} + Color *pixels = (Color *)RL_MALLOC(width*height*sizeof(Color)); -// Unload texture from GPU memory (VRAM) -void UnloadTexture(Texture2D texture) -{ - if (texture.id > 0) + for (int j = 0; j < height; j++) { - rlDeleteTextures(texture.id); - - TRACELOG(LOG_INFO, "TEXTURE: [ID %i] Unloaded texture data from VRAM (GPU)", texture.id); + float factor = (float)j/(float)height; + for (int i = 0; i < width; i++) + { + pixels[j*width + i].r = (int)((float)bottom.r*factor + (float)top.r*(1.f - factor)); + pixels[j*width + i].g = (int)((float)bottom.g*factor + (float)top.g*(1.f - factor)); + pixels[j*width + i].b = (int)((float)bottom.b*factor + (float)top.b*(1.f - factor)); + pixels[j*width + i].a = (int)((float)bottom.a*factor + (float)top.a*(1.f - factor)); + } } -} -// Unload render texture from GPU memory (VRAM) -void UnloadRenderTexture(RenderTexture2D target) -{ - if (target.id > 0) rlDeleteRenderTextures(target); + Image image = { + .data = pixels, + .width = width, + .height = height, + .format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8, + .mipmaps = 1 + }; + + return image; } -// Get pixel data from image in the form of Color struct array -Color *GetImageData(Image image) +// Generate image: horizontal gradient +Image GenImageGradientH(int width, int height, Color left, Color right) { - if ((image.width == 0) || (image.height == 0)) return NULL; - - Color *pixels = (Color *)RL_MALLOC(image.width*image.height*sizeof(Color)); + Color *pixels = (Color *)RL_MALLOC(width*height*sizeof(Color)); - if (image.format >= COMPRESSED_DXT1_RGB) TRACELOG(LOG_WARNING, "IMAGE: Pixel data retrieval not supported for compressed image formats"); - else + for (int i = 0; i < width; i++) { - if ((image.format == UNCOMPRESSED_R32) || - (image.format == UNCOMPRESSED_R32G32B32) || - (image.format == UNCOMPRESSED_R32G32B32A32)) TRACELOG(LOG_WARNING, "IMAGE: Pixel format converted from 32bit to 8bit per channel"); - - for (int i = 0, k = 0; i < image.width*image.height; i++) + float factor = (float)i/(float)width; + for (int j = 0; j < height; j++) { - switch (image.format) - { - case UNCOMPRESSED_GRAYSCALE: - { - pixels[i].r = ((unsigned char *)image.data)[i]; - pixels[i].g = ((unsigned char *)image.data)[i]; - pixels[i].b = ((unsigned char *)image.data)[i]; - pixels[i].a = 255; - - } 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)[i]; - - 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); - - } break; - case UNCOMPRESSED_R5G6B5: - { - unsigned short pixel = ((unsigned short *)image.data)[i]; - - 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; - - } break; - case UNCOMPRESSED_R4G4B4A4: - { - unsigned short pixel = ((unsigned short *)image.data)[i]; - - 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)); - - } 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; - case UNCOMPRESSED_R32: - { - pixels[i].r = (unsigned char)(((float *)image.data)[k]*255.0f); - pixels[i].g = 0; - pixels[i].b = 0; - pixels[i].a = 255; - - } break; - case UNCOMPRESSED_R32G32B32: - { - pixels[i].r = (unsigned char)(((float *)image.data)[k]*255.0f); - pixels[i].g = (unsigned char)(((float *)image.data)[k + 1]*255.0f); - pixels[i].b = (unsigned char)(((float *)image.data)[k + 2]*255.0f); - pixels[i].a = 255; - - k += 3; - } break; - case UNCOMPRESSED_R32G32B32A32: - { - pixels[i].r = (unsigned char)(((float *)image.data)[k]*255.0f); - pixels[i].g = (unsigned char)(((float *)image.data)[k]*255.0f); - pixels[i].b = (unsigned char)(((float *)image.data)[k]*255.0f); - pixels[i].a = (unsigned char)(((float *)image.data)[k]*255.0f); - - k += 4; - } break; - default: break; - } + pixels[j*width + i].r = (int)((float)right.r*factor + (float)left.r*(1.f - factor)); + pixels[j*width + i].g = (int)((float)right.g*factor + (float)left.g*(1.f - factor)); + pixels[j*width + i].b = (int)((float)right.b*factor + (float)left.b*(1.f - factor)); + pixels[j*width + i].a = (int)((float)right.a*factor + (float)left.a*(1.f - factor)); } } - return pixels; + Image image = { + .data = pixels, + .width = width, + .height = height, + .format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8, + .mipmaps = 1 + }; + + return image; } -// Get pixel data from image as Vector4 array (float normalized) -Vector4 *GetImageDataNormalized(Image image) +// Generate image: radial gradient +Image GenImageGradientRadial(int width, int height, float density, Color inner, Color outer) { - Vector4 *pixels = (Vector4 *)RL_MALLOC(image.width*image.height*sizeof(Vector4)); + Color *pixels = (Color *)RL_MALLOC(width*height*sizeof(Color)); + float radius = (width < height)? (float)width/2.0f : (float)height/2.0f; - if (image.format >= COMPRESSED_DXT1_RGB) TRACELOG(LOG_WARNING, "IMAGE: Pixel data retrieval not supported for compressed image formats"); - else + float centerX = (float)width/2.0f; + float centerY = (float)height/2.0f; + + for (int y = 0; y < height; y++) { - for (int i = 0, k = 0; i < image.width*image.height; i++) + for (int x = 0; x < width; x++) { - switch (image.format) - { - case UNCOMPRESSED_GRAYSCALE: - { - pixels[i].x = (float)((unsigned char *)image.data)[i]/255.0f; - pixels[i].y = (float)((unsigned char *)image.data)[i]/255.0f; - pixels[i].z = (float)((unsigned char *)image.data)[i]/255.0f; - pixels[i].w = 1.0f; - - } break; - case UNCOMPRESSED_GRAY_ALPHA: - { - pixels[i].x = (float)((unsigned char *)image.data)[k]/255.0f; - pixels[i].y = (float)((unsigned char *)image.data)[k]/255.0f; - pixels[i].z = (float)((unsigned char *)image.data)[k]/255.0f; - pixels[i].w = (float)((unsigned char *)image.data)[k + 1]/255.0f; - - k += 2; - } break; - case UNCOMPRESSED_R5G5B5A1: - { - unsigned short pixel = ((unsigned short *)image.data)[i]; - - pixels[i].x = (float)((pixel & 0b1111100000000000) >> 11)*(1.0f/31); - pixels[i].y = (float)((pixel & 0b0000011111000000) >> 6)*(1.0f/31); - pixels[i].z = (float)((pixel & 0b0000000000111110) >> 1)*(1.0f/31); - pixels[i].w = ((pixel & 0b0000000000000001) == 0)? 0.0f : 1.0f; - - } break; - case UNCOMPRESSED_R5G6B5: - { - unsigned short pixel = ((unsigned short *)image.data)[i]; - - pixels[i].x = (float)((pixel & 0b1111100000000000) >> 11)*(1.0f/31); - pixels[i].y = (float)((pixel & 0b0000011111100000) >> 5)*(1.0f/63); - pixels[i].z = (float)(pixel & 0b0000000000011111)*(1.0f/31); - pixels[i].w = 1.0f; - - } break; - case UNCOMPRESSED_R4G4B4A4: - { - unsigned short pixel = ((unsigned short *)image.data)[i]; - - pixels[i].x = (float)((pixel & 0b1111000000000000) >> 12)*(1.0f/15); - pixels[i].y = (float)((pixel & 0b0000111100000000) >> 8)*(1.0f/15); - pixels[i].z = (float)((pixel & 0b0000000011110000) >> 4)*(1.0f/15); - pixels[i].w = (float)(pixel & 0b0000000000001111)*(1.0f/15); - - } break; - case UNCOMPRESSED_R8G8B8A8: - { - pixels[i].x = (float)((unsigned char *)image.data)[k]/255.0f; - pixels[i].y = (float)((unsigned char *)image.data)[k + 1]/255.0f; - pixels[i].z = (float)((unsigned char *)image.data)[k + 2]/255.0f; - pixels[i].w = (float)((unsigned char *)image.data)[k + 3]/255.0f; - - k += 4; - } break; - case UNCOMPRESSED_R8G8B8: - { - pixels[i].x = (float)((unsigned char *)image.data)[k]/255.0f; - pixels[i].y = (float)((unsigned char *)image.data)[k + 1]/255.0f; - pixels[i].z = (float)((unsigned char *)image.data)[k + 2]/255.0f; - pixels[i].w = 1.0f; - - k += 3; - } break; - case UNCOMPRESSED_R32: - { - pixels[i].x = ((float *)image.data)[k]; - pixels[i].y = 0.0f; - pixels[i].z = 0.0f; - pixels[i].w = 1.0f; - - } break; - case UNCOMPRESSED_R32G32B32: - { - pixels[i].x = ((float *)image.data)[k]; - pixels[i].y = ((float *)image.data)[k + 1]; - pixels[i].z = ((float *)image.data)[k + 2]; - pixels[i].w = 1.0f; + float dist = hypotf((float)x - centerX, (float)y - centerY); + float factor = (dist - radius*density)/(radius*(1.0f - density)); - k += 3; - } break; - case UNCOMPRESSED_R32G32B32A32: - { - pixels[i].x = ((float *)image.data)[k]; - pixels[i].y = ((float *)image.data)[k + 1]; - pixels[i].z = ((float *)image.data)[k + 2]; - pixels[i].w = ((float *)image.data)[k + 3]; + factor = (float)fmax(factor, 0.0f); + factor = (float)fmin(factor, 1.f); // dist can be bigger than radius so we have to check - k += 4; - } - default: break; - } + pixels[y*width + x].r = (int)((float)outer.r*factor + (float)inner.r*(1.0f - factor)); + pixels[y*width + x].g = (int)((float)outer.g*factor + (float)inner.g*(1.0f - factor)); + pixels[y*width + x].b = (int)((float)outer.b*factor + (float)inner.b*(1.0f - factor)); + pixels[y*width + x].a = (int)((float)outer.a*factor + (float)inner.a*(1.0f - factor)); } } - return pixels; + Image image = { + .data = pixels, + .width = width, + .height = height, + .format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8, + .mipmaps = 1 + }; + + return image; } -// Get image alpha border rectangle -Rectangle GetImageAlphaBorder(Image image, float threshold) +// Generate image: checked +Image GenImageChecked(int width, int height, int checksX, int checksY, Color col1, Color col2) { - Rectangle crop = { 0 }; - - Color *pixels = GetImageData(image); + Color *pixels = (Color *)RL_MALLOC(width*height*sizeof(Color)); - if (pixels != NULL) + for (int y = 0; y < height; y++) { - int xMin = 65536; // Define a big enough number - int xMax = 0; - int yMin = 65536; - int yMax = 0; - - for (int y = 0; y < image.height; y++) - { - for (int x = 0; x < image.width; x++) - { - if (pixels[y*image.width + x].a > (unsigned char)(threshold*255.0f)) - { - if (x < xMin) xMin = x; - if (x > xMax) xMax = x; - if (y < yMin) yMin = y; - if (y > yMax) yMax = y; - } - } - } - - // Check for empty blank image - if ((xMin != 65536) && (xMax != 65536)) + for (int x = 0; x < width; x++) { - crop = (Rectangle){ xMin, yMin, (xMax + 1) - xMin, (yMax + 1) - yMin }; + if ((x/checksX + y/checksY)%2 == 0) pixels[y*width + x] = col1; + else pixels[y*width + x] = col2; } - - RL_FREE(pixels); } - return crop; + Image image = { + .data = pixels, + .width = width, + .height = height, + .format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8, + .mipmaps = 1 + }; + + return image; } -// Get pixel data size in bytes (image or texture) -// NOTE: Size depends on pixel format -int GetPixelDataSize(int width, int height, int format) +// Generate image: white noise +Image GenImageWhiteNoise(int width, int height, float factor) { - int dataSize = 0; // Size in bytes - int bpp = 0; // Bits per pixel + Color *pixels = (Color *)RL_MALLOC(width*height*sizeof(Color)); - switch (format) + for (int i = 0; i < width*height; i++) { - case UNCOMPRESSED_GRAYSCALE: bpp = 8; break; - case UNCOMPRESSED_GRAY_ALPHA: - case UNCOMPRESSED_R5G6B5: - case UNCOMPRESSED_R5G5B5A1: - case UNCOMPRESSED_R4G4B4A4: bpp = 16; break; - case UNCOMPRESSED_R8G8B8A8: bpp = 32; break; - case UNCOMPRESSED_R8G8B8: bpp = 24; break; - case UNCOMPRESSED_R32: bpp = 32; break; - case UNCOMPRESSED_R32G32B32: bpp = 32*3; break; - case UNCOMPRESSED_R32G32B32A32: bpp = 32*4; break; - case COMPRESSED_DXT1_RGB: - case COMPRESSED_DXT1_RGBA: - case COMPRESSED_ETC1_RGB: - case COMPRESSED_ETC2_RGB: - case COMPRESSED_PVRT_RGB: - case COMPRESSED_PVRT_RGBA: bpp = 4; break; - case COMPRESSED_DXT3_RGBA: - case COMPRESSED_DXT5_RGBA: - case COMPRESSED_ETC2_EAC_RGBA: - case COMPRESSED_ASTC_4x4_RGBA: bpp = 8; break; - case COMPRESSED_ASTC_8x8_RGBA: bpp = 2; break; - default: break; + if (GetRandomValue(0, 99) < (int)(factor*100.0f)) pixels[i] = WHITE; + else pixels[i] = BLACK; } - dataSize = width*height*bpp/8; // Total data size in bytes + Image image = { + .data = pixels, + .width = width, + .height = height, + .format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8, + .mipmaps = 1 + }; - return dataSize; + return image; } -// Get pixel data from GPU texture and return an Image -// NOTE: Compressed texture formats not supported -Image GetTextureData(Texture2D texture) +// Generate image: perlin noise +Image GenImagePerlinNoise(int width, int height, int offsetX, int offsetY, float scale) { - Image image = { 0 }; + Color *pixels = (Color *)RL_MALLOC(width*height*sizeof(Color)); - if (texture.format < 8) + for (int y = 0; y < height; y++) { - image.data = rlReadTexturePixels(texture); - - if (image.data != NULL) + for (int x = 0; x < width; x++) { - image.width = texture.width; - image.height = texture.height; - image.format = texture.format; - image.mipmaps = 1; + float nx = (float)(x + offsetX)*scale/(float)width; + float ny = (float)(y + offsetY)*scale/(float)height; -#if defined(GRAPHICS_API_OPENGL_ES2) - // NOTE: Data retrieved on OpenGL ES 2.0 should be RGBA, - // coming from FBO color buffer attachment, but it seems - // original texture format is retrieved on RPI... - image.format = UNCOMPRESSED_R8G8B8A8; -#endif - TRACELOG(LOG_INFO, "TEXTURE: [ID %i] Pixel data retrieved successfully", texture.id); - } - else TRACELOG(LOG_WARNING, "TEXTURE: [ID %i] Failed to retrieve pixel data", texture.id); - } - else TRACELOG(LOG_WARNING, "TEXTURE: [ID %i] Failed to retrieve compressed pixel data", texture.id); + // Typical values to start playing with: + // lacunarity = ~2.0 -- spacing between successive octaves (use exactly 2.0 for wrapping output) + // gain = 0.5 -- relative weighting applied to each successive octave + // octaves = 6 -- number of "octaves" of noise3() to sum - return image; -} + // NOTE: We need to translate the data from [-1..1] to [0..1] + float p = (stb_perlin_fbm_noise3(nx, ny, 1.0f, 2.0f, 0.5f, 6) + 1.0f)/2.0f; -// Get pixel data from GPU frontbuffer and return an Image (screenshot) -Image GetScreenData(void) -{ - Image image = { 0 }; + int intensity = (int)(p*255.0f); + pixels[y*width + x] = (Color){intensity, intensity, intensity, 255}; + } + } - image.width = GetScreenWidth(); - image.height = GetScreenHeight(); - image.mipmaps = 1; - image.format = UNCOMPRESSED_R8G8B8A8; - image.data = rlReadScreenPixels(image.width, image.height); + Image image = { + .data = pixels, + .width = width, + .height = height, + .format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8, + .mipmaps = 1 + }; return image; } -// Update GPU texture with new data -// NOTE: pixels data must match texture.format -void UpdateTexture(Texture2D texture, const void *pixels) +// Generate image: cellular algorithm. Bigger tileSize means bigger cells +Image GenImageCellular(int width, int height, int tileSize) { - rlUpdateTexture(texture.id, texture.width, texture.height, texture.format, pixels); -} + Color *pixels = (Color *)RL_MALLOC(width*height*sizeof(Color)); -// Export image data to file -// NOTE: File format depends on fileName extension -void ExportImage(Image image, const char *fileName) -{ - int success = 0; + int seedsPerRow = width/tileSize; + int seedsPerCol = height/tileSize; + int seedsCount = seedsPerRow*seedsPerCol; -#if defined(SUPPORT_IMAGE_EXPORT) - // NOTE: Getting Color array as RGBA unsigned char values - unsigned char *imgData = (unsigned char *)GetImageData(image); + Vector2 *seeds = (Vector2 *)RL_MALLOC(seedsCount*sizeof(Vector2)); -#if defined(SUPPORT_FILEFORMAT_PNG) - if (IsFileExtension(fileName, ".png")) success = stbi_write_png(fileName, image.width, image.height, 4, imgData, image.width*4); -#else - if (false) {} -#endif -#if defined(SUPPORT_FILEFORMAT_BMP) - else if (IsFileExtension(fileName, ".bmp")) success = stbi_write_bmp(fileName, image.width, image.height, 4, imgData); -#endif -#if defined(SUPPORT_FILEFORMAT_TGA) - else if (IsFileExtension(fileName, ".tga")) success = stbi_write_tga(fileName, image.width, image.height, 4, imgData); -#endif -#if defined(SUPPORT_FILEFORMAT_JPG) - else if (IsFileExtension(fileName, ".jpg")) success = stbi_write_jpg(fileName, image.width, image.height, 4, imgData, 80); // JPG quality: between 1 and 100 -#endif -#if defined(SUPPORT_FILEFORMAT_KTX) - else if (IsFileExtension(fileName, ".ktx")) success = SaveKTX(image, fileName); -#endif - else if (IsFileExtension(fileName, ".raw")) + for (int i = 0; i < seedsCount; i++) { - // Export raw pixel data (without header) - // NOTE: It's up to the user to track image parameters - SaveFileData(fileName, image.data, GetPixelDataSize(image.width, image.height, image.format)); - success = true; + int y = (i/seedsPerRow)*tileSize + GetRandomValue(0, tileSize - 1); + int x = (i%seedsPerRow)*tileSize + GetRandomValue(0, tileSize - 1); + seeds[i] = (Vector2){ (float)x, (float)y}; } - RL_FREE(imgData); -#endif - - if (success != 0) TRACELOG(LOG_INFO, "FILEIO: [%s] Image exported successfully", fileName); - else TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to export image", fileName); -} + for (int y = 0; y < height; y++) + { + int tileY = y/tileSize; -// Export image as code file (.h) defining an array of bytes -void ExportImageAsCode(Image image, const char *fileName) -{ - #define BYTES_TEXT_PER_LINE 20 + for (int x = 0; x < width; x++) + { + int tileX = x/tileSize; - FILE *txtFile = fopen(fileName, "wt"); + float minDistance = (float)strtod("Inf", NULL); - if (txtFile != NULL) - { - char varFileName[256] = { 0 }; - int dataSize = GetPixelDataSize(image.width, image.height, image.format); + // Check all adjacent tiles + for (int i = -1; i < 2; i++) + { + if ((tileX + i < 0) || (tileX + i >= seedsPerRow)) continue; - fprintf(txtFile, "////////////////////////////////////////////////////////////////////////////////////////\n"); - fprintf(txtFile, "// //\n"); - fprintf(txtFile, "// ImageAsCode exporter v1.0 - Image pixel data exported as an array of bytes //\n"); - fprintf(txtFile, "// //\n"); - fprintf(txtFile, "// more info and bugs-report: github.com/raysan5/raylib //\n"); - fprintf(txtFile, "// feedback and support: ray[at]raylib.com //\n"); - fprintf(txtFile, "// //\n"); - fprintf(txtFile, "// Copyright (c) 2020 Ramon Santamaria (@raysan5) //\n"); - fprintf(txtFile, "// //\n"); - fprintf(txtFile, "////////////////////////////////////////////////////////////////////////////////////////\n\n"); + for (int j = -1; j < 2; j++) + { + if ((tileY + j < 0) || (tileY + j >= seedsPerCol)) continue; - // Get file name from path and convert variable name to uppercase - strcpy(varFileName, GetFileNameWithoutExt(fileName)); - for (int i = 0; varFileName[i] != '\0'; i++) if ((varFileName[i] >= 'a') && (varFileName[i] <= 'z')) { varFileName[i] = varFileName[i] - 32; } + Vector2 neighborSeed = seeds[(tileY + j)*seedsPerRow + tileX + i]; - // Add image information - fprintf(txtFile, "// Image data information\n"); - fprintf(txtFile, "#define %s_WIDTH %i\n", varFileName, image.width); - fprintf(txtFile, "#define %s_HEIGHT %i\n", varFileName, image.height); - fprintf(txtFile, "#define %s_FORMAT %i // raylib internal pixel format\n\n", varFileName, image.format); + float dist = (float)hypot(x - (int)neighborSeed.x, y - (int)neighborSeed.y); + minDistance = (float)fmin(minDistance, dist); + } + } - fprintf(txtFile, "static unsigned char %s_DATA[%i] = { ", varFileName, dataSize); - for (int i = 0; i < dataSize - 1; i++) fprintf(txtFile, ((i%BYTES_TEXT_PER_LINE == 0)? "0x%x,\n" : "0x%x, "), ((unsigned char *)image.data)[i]); - fprintf(txtFile, "0x%x };\n", ((unsigned char *)image.data)[dataSize - 1]); + // I made this up but it seems to give good results at all tile sizes + int intensity = (int)(minDistance*256.0f/tileSize); + if (intensity > 255) intensity = 255; - fclose(txtFile); + pixels[y*width + x] = (Color){ intensity, intensity, intensity, 255 }; + } } + + RL_FREE(seeds); + + Image image = { + .data = pixels, + .width = width, + .height = height, + .format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8, + .mipmaps = 1 + }; + + return image; } +#endif // SUPPORT_IMAGE_GENERATION +//------------------------------------------------------------------------------------ +// Image manipulation functions +//------------------------------------------------------------------------------------ // Copy an image to a new image Image ImageCopy(Image image) { @@ -927,59 +805,75 @@ Image ImageCopy(Image image) // Create an image from another image piece Image ImageFromImage(Image image, Rectangle rec) { - Image result = ImageCopy(image); + Image result = { 0 }; -#if defined(SUPPORT_IMAGE_MANIPULATION) - ImageCrop(&result, rec); -#endif + int bytesPerPixel = GetPixelDataSize(1, 1, image.format); + + // TODO: Check rec is valid? + + result.width = (int)rec.width; + result.height = (int)rec.height; + result.data = RL_CALLOC((int)(rec.width*rec.height)*bytesPerPixel, 1); + result.format = image.format; + result.mipmaps = 1; + + for (int y = 0; y < rec.height; y++) + { + memcpy(((unsigned char *)result.data) + y*(int)rec.width*bytesPerPixel, ((unsigned char *)image.data) + ((y + (int)rec.y)*image.width + (int)rec.x)*bytesPerPixel, (int)rec.width*bytesPerPixel); + } return result; } -// Convert image to POT (power-of-two) -// NOTE: It could be useful on OpenGL ES 2.0 (RPI, HTML5) -void ImageToPOT(Image *image, Color fillColor) +// Crop an image to area defined by a rectangle +// NOTE: Security checks are performed in case rectangle goes out of bounds +void ImageCrop(Image *image, Rectangle crop) { // Security check to avoid program crash if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; - // Calculate next power-of-two values - // NOTE: Just add the required amount of pixels at the right and bottom sides of image... - int potWidth = (int)powf(2, ceilf(logf((float)image->width)/logf(2))); - int potHeight = (int)powf(2, ceilf(logf((float)image->height)/logf(2))); + // Security checks to validate crop rectangle + if (crop.x < 0) { crop.width += crop.x; crop.x = 0; } + if (crop.y < 0) { crop.height += crop.y; crop.y = 0; } + if ((crop.x + crop.width) > image->width) crop.width = image->width - crop.x; + if ((crop.y + crop.height) > image->height) crop.height = image->height - crop.y; + if ((crop.x > image->width) || (crop.y > image->height)) + { + TRACELOG(LOG_WARNING, "IMAGE: Failed to crop, rectangle out of bounds"); + return; + } - // Check if POT texture generation is required (if texture is not already POT) - if ((potWidth != image->width) || (potHeight != image->height)) + if (image->mipmaps > 1) TRACELOG(LOG_WARNING, "Image manipulation only applied to base mipmap level"); + if (image->format >= PIXELFORMAT_COMPRESSED_DXT1_RGB) TRACELOG(LOG_WARNING, "Image manipulation not supported for compressed formats"); + else { - Color *pixels = GetImageData(*image); // Get pixels data - Color *pixelsPOT = NULL; + int bytesPerPixel = GetPixelDataSize(1, 1, image->format); + + unsigned char *croppedData = (unsigned char *)RL_MALLOC((int)(crop.width*crop.height)*bytesPerPixel); - // Generate POT array from NPOT data - pixelsPOT = (Color *)RL_MALLOC(potWidth*potHeight*sizeof(Color)); + // OPTION 1: Move cropped data line-by-line + for (int y = (int)crop.y, offsetSize = 0; y < (int)(crop.y + crop.height); y++) + { + memcpy(croppedData + offsetSize, ((unsigned char *)image->data) + (y*image->width + (int)crop.x)*bytesPerPixel, (int)crop.width*bytesPerPixel); + offsetSize += ((int)crop.width*bytesPerPixel); + } - for (int j = 0; j < potHeight; j++) + /* + // OPTION 2: Move cropped data pixel-by-pixel or byte-by-byte + for (int y = (int)crop.y; y < (int)(crop.y + crop.height); y++) { - for (int i = 0; i < potWidth; i++) + for (int x = (int)crop.x; x < (int)(crop.x + crop.width); x++) { - if ((j < image->height) && (i < image->width)) pixelsPOT[j*potWidth + i] = pixels[j*image->width + i]; - else pixelsPOT[j*potWidth + i] = fillColor; + //memcpy(croppedData + ((y - (int)crop.y)*(int)crop.width + (x - (int)crop.x))*bytesPerPixel, ((unsigned char *)image->data) + (y*image->width + x)*bytesPerPixel, bytesPerPixel); + for (int i = 0; i < bytesPerPixel; i++) croppedData[((y - (int)crop.y)*(int)crop.width + (x - (int)crop.x))*bytesPerPixel + i] = ((unsigned char *)image->data)[(y*image->width + x)*bytesPerPixel + i]; } } + */ - RL_FREE(pixels); // Free pixels data - RL_FREE(image->data); // Free old image data - - int format = image->format; // Store image data format to reconvert later - - // NOTE: Image size changes, new width and height - *image = LoadImageEx(pixelsPOT, potWidth, potHeight); - - RL_FREE(pixelsPOT); // Free POT pixels data - - ImageFormat(image, format); // Reconvert image to previous format - - // TODO: Verification required for log - TRACELOG(LOG_WARNING, "IMAGE: Converted to POT: (%ix%i) -> (%ix%i)", image->width, image->height, potWidth, potHeight); + RL_FREE(image->data); + image->data = croppedData; + image->width = (int)crop.width; + image->height = (int)crop.height; } } @@ -991,9 +885,9 @@ void ImageFormat(Image *image, int newFormat) if ((newFormat != 0) && (image->format != newFormat)) { - if ((image->format < COMPRESSED_DXT1_RGB) && (newFormat < COMPRESSED_DXT1_RGB)) + if ((image->format < PIXELFORMAT_COMPRESSED_DXT1_RGB) && (newFormat < PIXELFORMAT_COMPRESSED_DXT1_RGB)) { - Vector4 *pixels = GetImageDataNormalized(*image); // Supports 8 to 32 bit per channel + Vector4 *pixels = LoadImageDataNormalized(*image); // Supports 8 to 32 bit per channel RL_FREE(image->data); // WARNING! We loose mipmaps data --> Regenerated at the end... image->data = NULL; @@ -1003,7 +897,7 @@ void ImageFormat(Image *image, int newFormat) switch (image->format) { - case UNCOMPRESSED_GRAYSCALE: + case PIXELFORMAT_UNCOMPRESSED_GRAYSCALE: { image->data = (unsigned char *)RL_MALLOC(image->width*image->height*sizeof(unsigned char)); @@ -1013,7 +907,7 @@ void ImageFormat(Image *image, int newFormat) } } break; - case UNCOMPRESSED_GRAY_ALPHA: + case PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA: { image->data = (unsigned char *)RL_MALLOC(image->width*image->height*2*sizeof(unsigned char)); @@ -1024,7 +918,7 @@ void ImageFormat(Image *image, int newFormat) } } break; - case UNCOMPRESSED_R5G6B5: + case PIXELFORMAT_UNCOMPRESSED_R5G6B5: { image->data = (unsigned short *)RL_MALLOC(image->width*image->height*sizeof(unsigned short)); @@ -1042,7 +936,7 @@ void ImageFormat(Image *image, int newFormat) } } break; - case UNCOMPRESSED_R8G8B8: + case PIXELFORMAT_UNCOMPRESSED_R8G8B8: { image->data = (unsigned char *)RL_MALLOC(image->width*image->height*3*sizeof(unsigned char)); @@ -1053,10 +947,8 @@ void ImageFormat(Image *image, int newFormat) ((unsigned char *)image->data)[i + 2] = (unsigned char)(pixels[k].z*255.0f); } } break; - case UNCOMPRESSED_R5G5B5A1: + case PIXELFORMAT_UNCOMPRESSED_R5G5B5A1: { - #define ALPHA_THRESHOLD 50 - image->data = (unsigned short *)RL_MALLOC(image->width*image->height*sizeof(unsigned short)); unsigned char r = 0; @@ -1069,13 +961,13 @@ void ImageFormat(Image *image, int newFormat) r = (unsigned char)(round(pixels[i].x*31.0f)); g = (unsigned char)(round(pixels[i].y*31.0f)); b = (unsigned char)(round(pixels[i].z*31.0f)); - a = (pixels[i].w > ((float)ALPHA_THRESHOLD/255.0f))? 1 : 0; + a = (pixels[i].w > ((float)PIXELFORMAT_UNCOMPRESSED_R5G5B5A1_ALPHA_THRESHOLD/255.0f))? 1 : 0; ((unsigned short *)image->data)[i] = (unsigned short)r << 11 | (unsigned short)g << 6 | (unsigned short)b << 1 | (unsigned short)a; } } break; - case UNCOMPRESSED_R4G4B4A4: + case PIXELFORMAT_UNCOMPRESSED_R4G4B4A4: { image->data = (unsigned short *)RL_MALLOC(image->width*image->height*sizeof(unsigned short)); @@ -1095,7 +987,7 @@ void ImageFormat(Image *image, int newFormat) } } break; - case UNCOMPRESSED_R8G8B8A8: + case PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: { image->data = (unsigned char *)RL_MALLOC(image->width*image->height*4*sizeof(unsigned char)); @@ -1107,7 +999,7 @@ void ImageFormat(Image *image, int newFormat) ((unsigned char *)image->data)[i + 3] = (unsigned char)(pixels[k].w*255.0f); } } break; - case UNCOMPRESSED_R32: + case PIXELFORMAT_UNCOMPRESSED_R32: { // WARNING: Image is converted to GRAYSCALE eqeuivalent 32bit @@ -1118,7 +1010,7 @@ void ImageFormat(Image *image, int newFormat) ((float *)image->data)[i] = (float)(pixels[i].x*0.299f + pixels[i].y*0.587f + pixels[i].z*0.114f); } } break; - case UNCOMPRESSED_R32G32B32: + case PIXELFORMAT_UNCOMPRESSED_R32G32B32: { image->data = (float *)RL_MALLOC(image->width*image->height*3*sizeof(float)); @@ -1129,7 +1021,7 @@ void ImageFormat(Image *image, int newFormat) ((float *)image->data)[i + 2] = pixels[k].z; } } break; - case UNCOMPRESSED_R32G32B32A32: + case PIXELFORMAT_UNCOMPRESSED_R32G32B32A32: { image->data = (float *)RL_MALLOC(image->width*image->height*4*sizeof(float)); @@ -1161,6 +1053,200 @@ void ImageFormat(Image *image, int newFormat) } } +// Convert image to POT (power-of-two) +// NOTE: It could be useful on OpenGL ES 2.0 (RPI, HTML5) +void ImageToPOT(Image *image, Color fill) +{ + // Security check to avoid program crash + if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; + + // Calculate next power-of-two values + // NOTE: Just add the required amount of pixels at the right and bottom sides of image... + int potWidth = (int)powf(2, ceilf(logf((float)image->width)/logf(2))); + int potHeight = (int)powf(2, ceilf(logf((float)image->height)/logf(2))); + + // Check if POT texture generation is required (if texture is not already POT) + if ((potWidth != image->width) || (potHeight != image->height)) ImageResizeCanvas(image, potWidth, potHeight, 0, 0, fill); +} + +#if defined(SUPPORT_IMAGE_MANIPULATION) +// Create an image from text (default font) +Image ImageText(const char *text, int fontSize, Color color) +{ + int defaultFontSize = 10; // Default Font chars height in pixel + if (fontSize < defaultFontSize) fontSize = defaultFontSize; + int spacing = fontSize/defaultFontSize; + + Image imText = ImageTextEx(GetFontDefault(), text, (float)fontSize, (float)spacing, color); + + return imText; +} + +// Create an image from text (custom sprite font) +Image ImageTextEx(Font font, const char *text, float fontSize, float spacing, Color tint) +{ + int length = (int)strlen(text); + + int textOffsetX = 0; // Image drawing position X + int textOffsetY = 0; // Offset between lines (on line break '\n') + + // NOTE: Text image is generated at font base size, later scaled to desired font size + Vector2 imSize = MeasureTextEx(font, text, (float)font.baseSize, spacing); + + // Create image to store text + Image imText = GenImageColor((int)imSize.x, (int)imSize.y, BLANK); + + for (int i = 0; i < length; i++) + { + // Get next codepoint from byte string and glyph index in font + int codepointByteCount = 0; + int codepoint = GetNextCodepoint(&text[i], &codepointByteCount); + int index = GetGlyphIndex(font, codepoint); + + // NOTE: Normally we exit the decoding sequence as soon as a bad byte is found (and return 0x3f) + // but we need to draw all of the bad bytes using the '?' symbol moving one byte + if (codepoint == 0x3f) codepointByteCount = 1; + + if (codepoint == '\n') + { + // NOTE: Fixed line spacing of 1.5 line-height + // TODO: Support custom line spacing defined by user + textOffsetY += (font.baseSize + font.baseSize/2); + textOffsetX = 0; + } + else + { + if ((codepoint != ' ') && (codepoint != '\t')) + { + Rectangle rec = { (float)(textOffsetX + font.chars[index].offsetX), (float)(textOffsetY + font.chars[index].offsetY), (float)font.recs[index].width, (float)font.recs[index].height }; + ImageDraw(&imText, font.chars[index].image, (Rectangle){ 0, 0, (float)font.chars[index].image.width, (float)font.chars[index].image.height }, rec, tint); + } + + if (font.chars[index].advanceX == 0) textOffsetX += (int)(font.recs[index].width + spacing); + else textOffsetX += font.chars[index].advanceX + (int)spacing; + } + + i += (codepointByteCount - 1); // Move text bytes counter to next codepoint + } + + // Scale image depending on text size + if (fontSize > imSize.y) + { + float scaleFactor = fontSize/imSize.y; + TRACELOG(LOG_INFO, "IMAGE: Text scaled by factor: %f", scaleFactor); + + // Using nearest-neighbor scaling algorithm for default font + if (font.texture.id == GetFontDefault().texture.id) ImageResizeNN(&imText, (int)(imSize.x*scaleFactor), (int)(imSize.y*scaleFactor)); + else ImageResize(&imText, (int)(imSize.x*scaleFactor), (int)(imSize.y*scaleFactor)); + } + + return imText; +} + +// Crop image depending on alpha value +// NOTE: Threshold is defined as a percentatge: 0.0f -> 1.0f +void ImageAlphaCrop(Image *image, float threshold) +{ + // Security check to avoid program crash + if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; + + Rectangle crop = GetImageAlphaBorder(*image, threshold); + + // Crop if rectangle is valid + if (((int)crop.width != 0) && ((int)crop.height != 0)) ImageCrop(image, crop); +} + +// Clear alpha channel to desired color +// NOTE: Threshold defines the alpha limit, 0.0f to 1.0f +void ImageAlphaClear(Image *image, Color color, float threshold) +{ + // Security check to avoid program crash + if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; + + if (image->mipmaps > 1) TRACELOG(LOG_WARNING, "Image manipulation only applied to base mipmap level"); + if (image->format >= PIXELFORMAT_COMPRESSED_DXT1_RGB) TRACELOG(LOG_WARNING, "Image manipulation not supported for compressed formats"); + else + { + switch (image->format) + { + case PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA: + { + unsigned char thresholdValue = (unsigned char)(threshold*255.0f); + for (int i = 1; i < image->width*image->height*2; i += 2) + { + if (((unsigned char *)image->data)[i] <= thresholdValue) + { + ((unsigned char *)image->data)[i - 1] = color.r; + ((unsigned char *)image->data)[i] = color.a; + } + } + } break; + case PIXELFORMAT_UNCOMPRESSED_R5G5B5A1: + { + unsigned char thresholdValue = ((threshold < 0.5f)? 0 : 1); + + unsigned char r = (unsigned char)(round((float)color.r*31.0f)); + unsigned char g = (unsigned char)(round((float)color.g*31.0f)); + unsigned char b = (unsigned char)(round((float)color.b*31.0f)); + unsigned char a = (color.a < 128)? 0 : 1; + + for (int i = 0; i < image->width*image->height; i++) + { + if ((((unsigned short *)image->data)[i] & 0b0000000000000001) <= thresholdValue) + { + ((unsigned short *)image->data)[i] = (unsigned short)r << 11 | (unsigned short)g << 6 | (unsigned short)b << 1 | (unsigned short)a; + } + } + } break; + case PIXELFORMAT_UNCOMPRESSED_R4G4B4A4: + { + unsigned char thresholdValue = (unsigned char)(threshold*15.0f); + + unsigned char r = (unsigned char)(round((float)color.r*15.0f)); + unsigned char g = (unsigned char)(round((float)color.g*15.0f)); + unsigned char b = (unsigned char)(round((float)color.b*15.0f)); + unsigned char a = (unsigned char)(round((float)color.a*15.0f)); + + for (int i = 0; i < image->width*image->height; i++) + { + if ((((unsigned short *)image->data)[i] & 0x000f) <= thresholdValue) + { + ((unsigned short *)image->data)[i] = (unsigned short)r << 12 | (unsigned short)g << 8 | (unsigned short)b << 4 | (unsigned short)a; + } + } + } break; + case PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: + { + unsigned char thresholdValue = (unsigned char)(threshold*255.0f); + for (int i = 3; i < image->width*image->height*4; i += 4) + { + if (((unsigned char *)image->data)[i] <= thresholdValue) + { + ((unsigned char *)image->data)[i - 3] = color.r; + ((unsigned char *)image->data)[i - 2] = color.g; + ((unsigned char *)image->data)[i - 1] = color.b; + ((unsigned char *)image->data)[i] = color.a; + } + } + } break; + case PIXELFORMAT_UNCOMPRESSED_R32G32B32A32: + { + for (int i = 3; i < image->width*image->height*4; i += 4) + { + if (((float *)image->data)[i] <= threshold) + { + ((float *)image->data)[i - 3] = (float)color.r/255.0f; + ((float *)image->data)[i - 2] = (float)color.g/255.0f; + ((float *)image->data)[i - 1] = (float)color.b/255.0f; + ((float *)image->data)[i] = (float)color.a/255.0f; + } + } + } break; + default: break; + } + } +} + // Apply alpha mask to image // NOTE 1: Returned image is GRAY_ALPHA (16bit) or RGBA (32bit) // NOTE 2: alphaMask should be same size as image @@ -1170,7 +1256,7 @@ void ImageAlphaMask(Image *image, Image alphaMask) { TRACELOG(LOG_WARNING, "IMAGE: Alpha mask must be same size as image"); } - else if (image->format >= COMPRESSED_DXT1_RGB) + else if (image->format >= PIXELFORMAT_COMPRESSED_DXT1_RGB) { TRACELOG(LOG_WARNING, "IMAGE: Alpha mask can not be applied to compressed data formats"); } @@ -1178,10 +1264,10 @@ void ImageAlphaMask(Image *image, Image alphaMask) { // Force mask to be Grayscale Image mask = ImageCopy(alphaMask); - if (mask.format != UNCOMPRESSED_GRAYSCALE) ImageFormat(&mask, UNCOMPRESSED_GRAYSCALE); + if (mask.format != PIXELFORMAT_UNCOMPRESSED_GRAYSCALE) ImageFormat(&mask, PIXELFORMAT_UNCOMPRESSED_GRAYSCALE); // In case image is only grayscale, we just add alpha channel - if (image->format == UNCOMPRESSED_GRAYSCALE) + if (image->format == PIXELFORMAT_UNCOMPRESSED_GRAYSCALE) { unsigned char *data = (unsigned char *)RL_MALLOC(image->width*image->height*2); @@ -1194,12 +1280,12 @@ void ImageAlphaMask(Image *image, Image alphaMask) RL_FREE(image->data); image->data = data; - image->format = UNCOMPRESSED_GRAY_ALPHA; + image->format = PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA; } else { // Convert image to RGBA - if (image->format != UNCOMPRESSED_R8G8B8A8) ImageFormat(image, UNCOMPRESSED_R8G8B8A8); + if (image->format != PIXELFORMAT_UNCOMPRESSED_R8G8B8A8) ImageFormat(image, PIXELFORMAT_UNCOMPRESSED_R8G8B8A8); // Apply alpha mask to alpha channel for (int i = 0, k = 3; (i < mask.width*mask.height) || (i < image->width*image->height); i++, k += 4) @@ -1212,26 +1298,6 @@ void ImageAlphaMask(Image *image, Image alphaMask) } } -// Clear alpha channel to desired color -// NOTE: Threshold defines the alpha limit, 0.0f to 1.0f -void ImageAlphaClear(Image *image, Color color, float threshold) -{ - // Security check to avoid program crash - if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; - - Color *pixels = GetImageData(*image); - - for (int i = 0; i < image->width*image->height; i++) if (pixels[i].a <= (unsigned char)(threshold*255.0f)) pixels[i] = color; - - UnloadImage(*image); - - int prevFormat = image->format; - *image = LoadImageEx(pixels, image->width, image->height); - - ImageFormat(image, prevFormat); - RL_FREE(pixels); -} - // Premultiply alpha channel void ImageAlphaPremultiply(Image *image) { @@ -1239,211 +1305,86 @@ void ImageAlphaPremultiply(Image *image) if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; float alpha = 0.0f; - Color *pixels = GetImageData(*image); + Color *pixels = LoadImageColors(*image); for (int i = 0; i < image->width*image->height; i++) { - alpha = (float)pixels[i].a/255.0f; - pixels[i].r = (unsigned char)((float)pixels[i].r*alpha); - pixels[i].g = (unsigned char)((float)pixels[i].g*alpha); - pixels[i].b = (unsigned char)((float)pixels[i].b*alpha); - } - - UnloadImage(*image); - - int prevFormat = image->format; - *image = LoadImageEx(pixels, image->width, image->height); - - ImageFormat(image, prevFormat); - RL_FREE(pixels); -} - -#if defined(SUPPORT_IMAGE_MANIPULATION) -// Load cubemap from image, multiple image cubemap layouts supported -TextureCubemap LoadTextureCubemap(Image image, int layoutType) -{ - TextureCubemap cubemap = { 0 }; - - if (layoutType == CUBEMAP_AUTO_DETECT) // Try to automatically guess layout type - { - // Check image width/height to determine the type of cubemap provided - if (image.width > image.height) + if (pixels[i].a == 0) { - if ((image.width/6) == image.height) { layoutType = CUBEMAP_LINE_HORIZONTAL; cubemap.width = image.width/6; } - else if ((image.width/4) == (image.height/3)) { layoutType = CUBEMAP_CROSS_FOUR_BY_THREE; cubemap.width = image.width/4; } - else if (image.width >= (int)((float)image.height*1.85f)) { layoutType = CUBEMAP_PANORAMA; cubemap.width = image.width/4; } + pixels[i].r = 0; + pixels[i].g = 0; + pixels[i].b = 0; } - else if (image.height > image.width) + else if (pixels[i].a < 255) { - if ((image.height/6) == image.width) { layoutType = CUBEMAP_LINE_VERTICAL; cubemap.width = image.height/6; } - else if ((image.width/3) == (image.height/4)) { layoutType = CUBEMAP_CROSS_THREE_BY_FOUR; cubemap.width = image.width/3; } + alpha = (float)pixels[i].a/255.0f; + pixels[i].r = (unsigned char)((float)pixels[i].r*alpha); + pixels[i].g = (unsigned char)((float)pixels[i].g*alpha); + pixels[i].b = (unsigned char)((float)pixels[i].b*alpha); } - - cubemap.height = cubemap.width; } - if (layoutType != CUBEMAP_AUTO_DETECT) - { - int size = cubemap.width; - - Image faces = { 0 }; // Vertical column image - Rectangle faceRecs[6] = { 0 }; // Face source rectangles - for (int i = 0; i < 6; i++) faceRecs[i] = (Rectangle){ 0, 0, size, size }; - - if (layoutType == CUBEMAP_LINE_VERTICAL) - { - faces = image; - for (int i = 0; i < 6; i++) faceRecs[i].y = size*i; - } - else if (layoutType == CUBEMAP_PANORAMA) - { - // TODO: Convert panorama image to square faces... - // Ref: https://github.com/denivip/panorama/blob/master/panorama.cpp - } - else - { - if (layoutType == CUBEMAP_LINE_HORIZONTAL) for (int i = 0; i < 6; i++) faceRecs[i].x = size*i; - else if (layoutType == CUBEMAP_CROSS_THREE_BY_FOUR) - { - faceRecs[0].x = size; faceRecs[0].y = size; - faceRecs[1].x = size; faceRecs[1].y = 3*size; - faceRecs[2].x = size; faceRecs[2].y = 0; - faceRecs[3].x = size; faceRecs[3].y = 2*size; - faceRecs[4].x = 0; faceRecs[4].y = size; - faceRecs[5].x = 2*size; faceRecs[5].y = size; - } - else if (layoutType == CUBEMAP_CROSS_FOUR_BY_THREE) - { - faceRecs[0].x = 2*size; faceRecs[0].y = size; - faceRecs[1].x = 0; faceRecs[1].y = size; - faceRecs[2].x = size; faceRecs[2].y = 0; - faceRecs[3].x = size; faceRecs[3].y = 2*size; - faceRecs[4].x = size; faceRecs[4].y = size; - faceRecs[5].x = 3*size; faceRecs[5].y = size; - } - - // Convert image data to 6 faces in a vertical column, that's the optimum layout for loading - faces = GenImageColor(size, size*6, MAGENTA); - ImageFormat(&faces, image.format); - - // TODO: Image formating does not work with compressed textures! - } + RL_FREE(image->data); - for (int i = 0; i < 6; i++) ImageDraw(&faces, image, faceRecs[i], (Rectangle){ 0, size*i, size, size }, WHITE); - - cubemap.id = rlLoadTextureCubemap(faces.data, size, faces.format); - if (cubemap.id == 0) TRACELOG(LOG_WARNING, "IMAGE: Failed to load cubemap image"); - - UnloadImage(faces); - } - else TRACELOG(LOG_WARNING, "IMAGE: Failed to detect cubemap image layout"); + int format = image->format; + image->data = pixels; + image->format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8; - return cubemap; + ImageFormat(image, format); } -// Crop an image to area defined by a rectangle -// NOTE: Security checks are performed in case rectangle goes out of bounds -void ImageCrop(Image *image, Rectangle crop) +// Resize and image to new size +// NOTE: Uses stb default scaling filters (both bicubic): +// STBIR_DEFAULT_FILTER_UPSAMPLE STBIR_FILTER_CATMULLROM +// STBIR_DEFAULT_FILTER_DOWNSAMPLE STBIR_FILTER_MITCHELL (high-quality Catmull-Rom) +void ImageResize(Image *image, int newWidth, int newHeight) { // Security check to avoid program crash if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; - // Security checks to validate crop rectangle - if (crop.x < 0) { crop.width += crop.x; crop.x = 0; } - if (crop.y < 0) { crop.height += crop.y; crop.y = 0; } - if ((crop.x + crop.width) > image->width) crop.width = image->width - crop.x; - if ((crop.y + crop.height) > image->height) crop.height = image->height - crop.y; + bool fastPath = true; + if ((image->format != PIXELFORMAT_UNCOMPRESSED_GRAYSCALE) && (image->format != PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA) && (image->format != PIXELFORMAT_UNCOMPRESSED_R8G8B8) && (image->format != PIXELFORMAT_UNCOMPRESSED_R8G8B8A8)) fastPath = true; - if ((crop.x < image->width) && (crop.y < image->height)) + if (fastPath) { - // Start the cropping process - Color *pixels = GetImageData(*image); // Get data as Color pixels array - Color *cropPixels = (Color *)RL_MALLOC((int)crop.width*(int)crop.height*sizeof(Color)); + int bytesPerPixel = GetPixelDataSize(1, 1, image->format); + unsigned char *output = RL_MALLOC(newWidth*newHeight*bytesPerPixel); - for (int j = (int)crop.y; j < (int)(crop.y + crop.height); j++) + switch (image->format) { - for (int i = (int)crop.x; i < (int)(crop.x + crop.width); i++) - { - cropPixels[(j - (int)crop.y)*(int)crop.width + (i - (int)crop.x)] = pixels[j*image->width + i]; - } + case PIXELFORMAT_UNCOMPRESSED_GRAYSCALE: stbir_resize_uint8((unsigned char *)image->data, image->width, image->height, 0, output, newWidth, newHeight, 0, 1); break; + case PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA: stbir_resize_uint8((unsigned char *)image->data, image->width, image->height, 0, output, newWidth, newHeight, 0, 2); break; + case PIXELFORMAT_UNCOMPRESSED_R8G8B8: stbir_resize_uint8((unsigned char *)image->data, image->width, image->height, 0, output, newWidth, newHeight, 0, 3); break; + case PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: stbir_resize_uint8((unsigned char *)image->data, image->width, image->height, 0, output, newWidth, newHeight, 0, 4); break; + default: break; } - RL_FREE(pixels); - - int format = image->format; - - UnloadImage(*image); - - *image = LoadImageEx(cropPixels, (int)crop.width, (int)crop.height); - - RL_FREE(cropPixels); - - // Reformat 32bit RGBA image to original format - ImageFormat(image, format); + RL_FREE(image->data); + image->data = output; + image->width = newWidth; + image->height = newHeight; } - else TRACELOG(LOG_WARNING, "IMAGE: Failed to crop, rectangle out of bounds"); -} - -// Crop image depending on alpha value -void ImageAlphaCrop(Image *image, float threshold) -{ - // Security check to avoid program crash - if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; - - Color *pixels = GetImageData(*image); - - int xMin = 65536; // Define a big enough number - int xMax = 0; - int yMin = 65536; - int yMax = 0; - - for (int y = 0; y < image->height; y++) + else { - for (int x = 0; x < image->width; x++) - { - if (pixels[y*image->width + x].a > (unsigned char)(threshold*255.0f)) - { - if (x < xMin) xMin = x; - if (x > xMax) xMax = x; - if (y < yMin) yMin = y; - if (y > yMax) yMax = y; - } - } - } - - Rectangle crop = { xMin, yMin, (xMax + 1) - xMin, (yMax + 1) - yMin }; + // Get data as Color pixels array to work with it + Color *pixels = LoadImageColors(*image); + Color *output = (Color *)RL_MALLOC(newWidth*newHeight*sizeof(Color)); - RL_FREE(pixels); + // NOTE: Color data is casted to (unsigned char *), there shouldn't been any problem... + stbir_resize_uint8((unsigned char *)pixels, image->width, image->height, 0, (unsigned char *)output, newWidth, newHeight, 0, 4); - // Check for not empty image brefore cropping - if (!((xMax < xMin) || (yMax < yMin))) ImageCrop(image, crop); -} - -// Resize and image to new size -// NOTE: Uses stb default scaling filters (both bicubic): -// STBIR_DEFAULT_FILTER_UPSAMPLE STBIR_FILTER_CATMULLROM -// STBIR_DEFAULT_FILTER_DOWNSAMPLE STBIR_FILTER_MITCHELL (high-quality Catmull-Rom) -void ImageResize(Image *image, int newWidth, int newHeight) -{ - // Security check to avoid program crash - if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; - - // Get data as Color pixels array to work with it - Color *pixels = GetImageData(*image); - Color *output = (Color *)RL_MALLOC(newWidth*newHeight*sizeof(Color)); - - // NOTE: Color data is casted to (unsigned char *), there shouldn't been any problem... - stbir_resize_uint8((unsigned char *)pixels, image->width, image->height, 0, (unsigned char *)output, newWidth, newHeight, 0, 4); - - int format = image->format; + int format = image->format; - UnloadImage(*image); + UnloadImageColors(pixels); + RL_FREE(image->data); - *image = LoadImageEx(output, newWidth, newHeight); - ImageFormat(image, format); // Reformat 32bit RGBA image to original format + image->data = output; + image->width = newWidth; + image->height = newHeight; + image->format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8; - RL_FREE(output); - RL_FREE(pixels); + ImageFormat(image, format); // Reformat 32bit RGBA image to original format + } } // Resize and image to new size using Nearest-Neighbor scaling algorithm @@ -1452,7 +1393,7 @@ void ImageResizeNN(Image *image,int newWidth,int newHeight) // Security check to avoid program crash if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; - Color *pixels = GetImageData(*image); + Color *pixels = LoadImageColors(*image); Color *output = (Color *)RL_MALLOC(newWidth*newHeight*sizeof(Color)); // EDIT: added +1 to account for an early rounding problem @@ -1473,90 +1414,68 @@ void ImageResizeNN(Image *image,int newWidth,int newHeight) int format = image->format; - UnloadImage(*image); + RL_FREE(image->data); + + image->data = output; + image->width = newWidth; + image->height = newHeight; + image->format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8; - *image = LoadImageEx(output, newWidth, newHeight); ImageFormat(image, format); // Reformat 32bit RGBA image to original format - RL_FREE(output); - RL_FREE(pixels); + UnloadImageColors(pixels); } // Resize canvas and fill with color // NOTE: Resize offset is relative to the top-left corner of the original image -void ImageResizeCanvas(Image *image, int newWidth, int newHeight, int offsetX, int offsetY, Color color) +void ImageResizeCanvas(Image *image, int newWidth, int newHeight, int offsetX, int offsetY, Color fill) { // Security check to avoid program crash if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; - if ((newWidth != image->width) || (newHeight != image->height)) + if (image->mipmaps > 1) TRACELOG(LOG_WARNING, "Image manipulation only applied to base mipmap level"); + if (image->format >= PIXELFORMAT_COMPRESSED_DXT1_RGB) TRACELOG(LOG_WARNING, "Image manipulation not supported for compressed formats"); + else if ((newWidth != image->width) || (newHeight != image->height)) { - // Support offsets out of canvas new size -> original image is cropped + Rectangle srcRec = { 0, 0, (float)image->width, (float)image->height }; + Vector2 dstPos = { (float)offsetX, (float)offsetY }; + if (offsetX < 0) { - ImageCrop(image, (Rectangle) { -offsetX, 0, image->width + offsetX, image->height }); - offsetX = 0; - } - else if (offsetX > (newWidth - image->width)) - { - ImageCrop(image, (Rectangle) { 0, 0, image->width - (offsetX - (newWidth - image->width)), image->height }); - offsetX = newWidth - image->width; + srcRec.x = (float)-offsetX; + srcRec.width += (float)offsetX; + dstPos.x = 0; } + else if ((offsetX + image->width) > newWidth) srcRec.width = (float)(newWidth - offsetX); if (offsetY < 0) { - ImageCrop(image, (Rectangle) { 0, -offsetY, image->width, image->height + offsetY }); - offsetY = 0; - } - else if (offsetY > (newHeight - image->height)) - { - ImageCrop(image, (Rectangle) { 0, 0, image->width, image->height - (offsetY - (newHeight - image->height)) }); - offsetY = newHeight - image->height; + srcRec.y = (float)-offsetY; + srcRec.height += (float)offsetY; + dstPos.y = 0; } + else if ((offsetY + image->height) > newHeight) srcRec.height = (float)(newHeight - offsetY); - if ((newWidth > image->width) && (newHeight > image->height)) - { - Image imTemp = GenImageColor(newWidth, newHeight, color); + if (newWidth < srcRec.width) srcRec.width = (float)newWidth; + if (newHeight < srcRec.height) srcRec.height = (float)newHeight; - Rectangle srcRec = { 0.0f, 0.0f, (float)image->width, (float)image->height }; - Rectangle dstRec = { (float)offsetX, (float)offsetY, srcRec.width, srcRec.height }; + int bytesPerPixel = GetPixelDataSize(1, 1, image->format); + unsigned char *resizedData = (unsigned char *)RL_CALLOC(newWidth*newHeight*bytesPerPixel, 1); - ImageDraw(&imTemp, *image, srcRec, dstRec, WHITE); - ImageFormat(&imTemp, image->format); - UnloadImage(*image); - *image = imTemp; - } - else if ((newWidth < image->width) && (newHeight < image->height)) - { - Rectangle crop = { (float)offsetX, (float)offsetY, (float)newWidth, (float)newHeight }; - ImageCrop(image, crop); - } - else // One side is bigger and the other is smaller - { - Image imTemp = GenImageColor(newWidth, newHeight, color); - - Rectangle srcRec = { 0.0f, 0.0f, (float)image->width, (float)image->height }; - Rectangle dstRec = { (float)offsetX, (float)offsetY, (float)image->width, (float)image->height }; + // TODO: Fill resizedData with fill color (must be formatted to image->format) - if (newWidth < image->width) - { - srcRec.x = offsetX; - srcRec.width = newWidth; - dstRec.x = 0.0f; - } - - if (newHeight < image->height) - { - srcRec.y = offsetY; - srcRec.height = newHeight; - dstRec.y = 0.0f; - } + int dstOffsetSize = ((int)dstPos.y*newWidth + (int)dstPos.x)*bytesPerPixel; - ImageDraw(&imTemp, *image, srcRec, dstRec, WHITE); - ImageFormat(&imTemp, image->format); - UnloadImage(*image); - *image = imTemp; + for (int y = 0; y < (int)srcRec.height; y++) + { + memcpy(resizedData + dstOffsetSize, ((unsigned char *)image->data) + ((y + (int)srcRec.y)*image->width + (int)srcRec.x)*bytesPerPixel, (int)srcRec.width*bytesPerPixel); + dstOffsetSize += (newWidth*bytesPerPixel); } + + RL_FREE(image->data); + image->data = resizedData; + image->width = newWidth; + image->height = newHeight; } } @@ -1638,7 +1557,7 @@ void ImageDither(Image *image, int rBpp, int gBpp, int bBpp, int aBpp) // Security check to avoid program crash if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; - if (image->format >= COMPRESSED_DXT1_RGB) + if (image->format >= PIXELFORMAT_COMPRESSED_DXT1_RGB) { TRACELOG(LOG_WARNING, "IMAGE: Compressed data formats can not be dithered"); return; @@ -1650,19 +1569,19 @@ void ImageDither(Image *image, int rBpp, int gBpp, int bBpp, int aBpp) } else { - Color *pixels = GetImageData(*image); + Color *pixels = LoadImageColors(*image); RL_FREE(image->data); // free old image data - if ((image->format != UNCOMPRESSED_R8G8B8) && (image->format != UNCOMPRESSED_R8G8B8A8)) + if ((image->format != PIXELFORMAT_UNCOMPRESSED_R8G8B8) && (image->format != PIXELFORMAT_UNCOMPRESSED_R8G8B8A8)) { TRACELOG(LOG_WARNING, "IMAGE: Format is already 16bpp or lower, dithering could have no effect"); } // Define new image format, check if desired bpp match internal known format - if ((rBpp == 5) && (gBpp == 6) && (bBpp == 5) && (aBpp == 0)) image->format = UNCOMPRESSED_R5G6B5; - else if ((rBpp == 5) && (gBpp == 5) && (bBpp == 5) && (aBpp == 1)) image->format = UNCOMPRESSED_R5G5B5A1; - else if ((rBpp == 4) && (gBpp == 4) && (bBpp == 4) && (aBpp == 4)) image->format = UNCOMPRESSED_R4G4B4A4; + if ((rBpp == 5) && (gBpp == 6) && (bBpp == 5) && (aBpp == 0)) image->format = PIXELFORMAT_UNCOMPRESSED_R5G6B5; + else if ((rBpp == 5) && (gBpp == 5) && (bBpp == 5) && (aBpp == 1)) image->format = PIXELFORMAT_UNCOMPRESSED_R5G5B5A1; + else if ((rBpp == 4) && (gBpp == 4) && (bBpp == 4) && (aBpp == 4)) image->format = PIXELFORMAT_UNCOMPRESSED_R4G4B4A4; else { image->format = 0; @@ -1738,434 +1657,77 @@ void ImageDither(Image *image, int rBpp, int gBpp, int bBpp, int aBpp) } } - RL_FREE(pixels); - } -} - -// Extract color palette from image to maximum size -// NOTE: Memory allocated should be freed manually! -Color *ImageExtractPalette(Image image, int maxPaletteSize, int *extractCount) -{ - #define COLOR_EQUAL(col1, col2) ((col1.r == col2.r)&&(col1.g == col2.g)&&(col1.b == col2.b)&&(col1.a == col2.a)) - - Color *pixels = GetImageData(image); - Color *palette = (Color *)RL_MALLOC(maxPaletteSize*sizeof(Color)); - - int palCount = 0; - for (int i = 0; i < maxPaletteSize; i++) palette[i] = BLANK; // Set all colors to BLANK - - for (int i = 0; i < image.width*image.height; i++) - { - if (pixels[i].a > 0) - { - bool colorInPalette = false; - - // Check if the color is already on palette - for (int j = 0; j < maxPaletteSize; j++) - { - if (COLOR_EQUAL(pixels[i], palette[j])) - { - colorInPalette = true; - break; - } - } - - // Store color if not on the palette - if (!colorInPalette) - { - palette[palCount] = pixels[i]; // Add pixels[i] to palette - palCount++; - - // We reached the limit of colors supported by palette - if (palCount >= maxPaletteSize) - { - i = image.width*image.height; // Finish palette get - TRACELOG(LOG_WARNING, "IMAGE: Palette is greater than %i colors", maxPaletteSize); - } - } - } + UnloadImageColors(pixels); } - - RL_FREE(pixels); - - *extractCount = palCount; - - return palette; } -// Draw an image (source) within an image (destination) -// NOTE: Color tint is applied to source image -void ImageDraw(Image *dst, Image src, Rectangle srcRec, Rectangle dstRec, Color tint) +// Flip image vertically +void ImageFlipVertical(Image *image) { // Security check to avoid program crash - if ((dst->data == NULL) || (dst->width == 0) || (dst->height == 0) || - (src.data == NULL) || (src.width == 0) || (src.height == 0)) return; - - // Security checks to avoid size and rectangle issues (out of bounds) - // Check that srcRec is inside src image - if (srcRec.x < 0) srcRec.x = 0; - if (srcRec.y < 0) srcRec.y = 0; - - if ((srcRec.x + srcRec.width) > src.width) - { - srcRec.width = src.width - srcRec.x; - TRACELOG(LOG_WARNING, "IMAGE: Source rectangle width out of bounds, rescaled width: %i", srcRec.width); - } - - if ((srcRec.y + srcRec.height) > src.height) - { - srcRec.height = src.height - srcRec.y; - TRACELOG(LOG_WARNING, "IMAGE: Source rectangle height out of bounds, rescaled height: %i", srcRec.height); - } - - Image srcCopy = ImageCopy(src); // Make a copy of source image to work with it - - // Crop source image to desired source rectangle (if required) - if ((src.width != (int)srcRec.width) && (src.height != (int)srcRec.height)) ImageCrop(&srcCopy, srcRec); - - // Scale source image in case destination rec size is different than source rec size - if (((int)dstRec.width != (int)srcRec.width) || ((int)dstRec.height != (int)srcRec.height)) - { - ImageResize(&srcCopy, (int)dstRec.width, (int)dstRec.height); - } - - // Check that dstRec is inside dst image - // Allow negative position within destination with cropping - if (dstRec.x < 0) - { - ImageCrop(&srcCopy, (Rectangle) { -dstRec.x, 0, dstRec.width + dstRec.x, dstRec.height }); - dstRec.width = dstRec.width + dstRec.x; - dstRec.x = 0; - } - - if ((dstRec.x + dstRec.width) > dst->width) - { - ImageCrop(&srcCopy, (Rectangle) { 0, 0, dst->width - dstRec.x, dstRec.height }); - dstRec.width = dst->width - dstRec.x; - } - - if (dstRec.y < 0) - { - ImageCrop(&srcCopy, (Rectangle) { 0, -dstRec.y, dstRec.width, dstRec.height + dstRec.y }); - dstRec.height = dstRec.height + dstRec.y; - dstRec.y = 0; - } - - if ((dstRec.y + dstRec.height) > dst->height) - { - ImageCrop(&srcCopy, (Rectangle) { 0, 0, dstRec.width, dst->height - dstRec.y }); - dstRec.height = dst->height - dstRec.y; - } - - // Get image data as Color pixels array to work with it - Color *dstPixels = GetImageData(*dst); - Color *srcPixels = GetImageData(srcCopy); - - UnloadImage(srcCopy); // Source copy not required any more - - Vector4 fsrc, fdst, fout; // Normalized pixel data (ready for operation) - Vector4 ftint = ColorNormalize(tint); // Normalized color tint - - // Blit pixels, copy source image into destination - // TODO: Maybe out-of-bounds blitting could be considered here instead of so much cropping - for (int j = (int)dstRec.y; j < (int)(dstRec.y + dstRec.height); j++) - { - for (int i = (int)dstRec.x; i < (int)(dstRec.x + dstRec.width); i++) - { - // Alpha blending (https://en.wikipedia.org/wiki/Alpha_compositing) - - fdst = ColorNormalize(dstPixels[j*(int)dst->width + i]); - fsrc = ColorNormalize(srcPixels[(j - (int)dstRec.y)*(int)dstRec.width + (i - (int)dstRec.x)]); - - // Apply color tint to source image - fsrc.x *= ftint.x; fsrc.y *= ftint.y; fsrc.z *= ftint.z; fsrc.w *= ftint.w; - - fout.w = fsrc.w + fdst.w*(1.0f - fsrc.w); - - if (fout.w <= 0.0f) - { - fout.x = 0.0f; - fout.y = 0.0f; - fout.z = 0.0f; - } - else - { - fout.x = (fsrc.x*fsrc.w + fdst.x*fdst.w*(1 - fsrc.w))/fout.w; - fout.y = (fsrc.y*fsrc.w + fdst.y*fdst.w*(1 - fsrc.w))/fout.w; - fout.z = (fsrc.z*fsrc.w + fdst.z*fdst.w*(1 - fsrc.w))/fout.w; - } - - dstPixels[j*(int)dst->width + i] = (Color){ (unsigned char)(fout.x*255.0f), - (unsigned char)(fout.y*255.0f), - (unsigned char)(fout.z*255.0f), - (unsigned char)(fout.w*255.0f) }; - - // TODO: Support other blending options - } - } - - UnloadImage(*dst); - - *dst = LoadImageEx(dstPixels, (int)dst->width, (int)dst->height); - ImageFormat(dst, dst->format); - - RL_FREE(srcPixels); - RL_FREE(dstPixels); -} - -// Create an image from text (default font) -Image ImageText(const char *text, int fontSize, Color color) -{ - int defaultFontSize = 10; // Default Font chars height in pixel - if (fontSize < defaultFontSize) fontSize = defaultFontSize; - int spacing = fontSize / defaultFontSize; - - Image imText = ImageTextEx(GetFontDefault(), text, (float)fontSize, (float)spacing, color); - - return imText; -} - -// Create an image from text (custom sprite font) -Image ImageTextEx(Font font, const char *text, float fontSize, float spacing, Color tint) -{ - int length = strlen(text); - - int textOffsetX = 0; // Image drawing position X - int textOffsetY = 0; // Offset between lines (on line break '\n') - - // NOTE: Text image is generated at font base size, later scaled to desired font size - Vector2 imSize = MeasureTextEx(font, text, (float)font.baseSize, spacing); - - // Create image to store text - Image imText = GenImageColor((int)imSize.x, (int)imSize.y, BLANK); + if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; - for (int i = 0; i < length; i++) + if (image->mipmaps > 1) TRACELOG(LOG_WARNING, "Image manipulation only applied to base mipmap level"); + if (image->format >= PIXELFORMAT_COMPRESSED_DXT1_RGB) TRACELOG(LOG_WARNING, "Image manipulation not supported for compressed formats"); + else { - // Get next codepoint from byte string and glyph index in font - int codepointByteCount = 0; - int codepoint = GetNextCodepoint(&text[i], &codepointByteCount); - int index = GetGlyphIndex(font, codepoint); - - // NOTE: Normally we exit the decoding sequence as soon as a bad byte is found (and return 0x3f) - // but we need to draw all of the bad bytes using the '?' symbol moving one byte - if (codepoint == 0x3f) codepointByteCount = 1; + int bytesPerPixel = GetPixelDataSize(1, 1, image->format); + unsigned char *flippedData = (unsigned char *)RL_MALLOC(image->width*image->height*bytesPerPixel); - if (codepoint == '\n') + for (int i = (image->height - 1), offsetSize = 0; i >= 0; i--) { - // NOTE: Fixed line spacing of 1.5 line-height - // TODO: Support custom line spacing defined by user - textOffsetY += (font.baseSize + font.baseSize/2); - textOffsetX = 0.0f; + memcpy(flippedData + offsetSize, ((unsigned char *)image->data) + i*image->width*bytesPerPixel, image->width*bytesPerPixel); + offsetSize += image->width*bytesPerPixel; } - else - { - if ((codepoint != ' ') && (codepoint != '\t')) - { - Rectangle rec = { textOffsetX + font.chars[index].offsetX, textOffsetY + font.chars[index].offsetY, font.recs[index].width, font.recs[index].height }; - ImageDraw(&imText, font.chars[index].image, (Rectangle){ 0, 0, font.chars[index].image.width, font.chars[index].image.height }, rec, tint); - } - - if (font.chars[index].advanceX == 0) textOffsetX += (int)(font.recs[index].width + spacing); - else textOffsetX += font.chars[index].advanceX + (int)spacing; - } - - i += (codepointByteCount - 1); // Move text bytes counter to next codepoint - } - - // Scale image depending on text size - if (fontSize > imSize.y) - { - float scaleFactor = fontSize/imSize.y; - TRACELOG(LOG_INFO, "IMAGE: Text scaled by factor: %f", scaleFactor); - // Using nearest-neighbor scaling algorithm for default font - if (font.texture.id == GetFontDefault().texture.id) ImageResizeNN(&imText, (int)(imSize.x*scaleFactor), (int)(imSize.y*scaleFactor)); - else ImageResize(&imText, (int)(imSize.x*scaleFactor), (int)(imSize.y*scaleFactor)); + RL_FREE(image->data); + image->data = flippedData; } - - return imText; -} - -// Draw rectangle within an image -void ImageDrawRectangle(Image *dst, int posX, int posY, int width, int height, Color color) -{ - ImageDrawRectangleRec(dst, (Rectangle){ posX, posY, width, height }, color); -} - -// Draw rectangle within an image (Vector version) -void ImageDrawRectangleV(Image *dst, Vector2 position, Vector2 size, Color color) -{ - ImageDrawRectangle(dst, position.x, position.y, size.x, size.y, color); } -// Draw rectangle within an image -void ImageDrawRectangleRec(Image *dst, Rectangle rec, Color color) +// Flip image horizontally +void ImageFlipHorizontal(Image *image) { // Security check to avoid program crash - if ((dst->data == NULL) || (dst->width == 0) || (dst->height == 0)) return; - - Image imRec = GenImageColor((int)rec.width, (int)rec.height, color); - ImageDraw(dst, imRec, (Rectangle){ 0, 0, rec.width, rec.height }, rec, WHITE); - UnloadImage(imRec); -} - -// Draw rectangle lines within an image -void ImageDrawRectangleLines(Image *dst, Rectangle rec, int thick, Color color) -{ - ImageDrawRectangle(dst, rec.x, rec.y, rec.width, thick, color); - ImageDrawRectangle(dst, rec.x, rec.y + thick, thick, rec.height - thick*2, color); - ImageDrawRectangle(dst, rec.x + rec.width - thick, rec.y + thick, thick, rec.height - thick*2, color); - ImageDrawRectangle(dst, rec.x, rec.y + rec.height - thick, rec.width, thick, color); -} - -// Clear image background with given color -void ImageClearBackground(Image *dst, Color color) -{ - ImageDrawRectangle(dst, 0, 0, dst->width, dst->height, color); -} - -// Draw pixel within an image -void ImageDrawPixel(Image *dst, int x, int y, Color color) -{ - ImageDrawRectangle(dst, x, y, 1, 1, color); -} - -// Draw pixel within an image (Vector version) -void ImageDrawPixelV(Image *dst, Vector2 position, Color color) -{ - ImageDrawRectangle(dst, (int)position.x, (int)position.y, 1, 1, color); -} - -// Draw circle within an image -void ImageDrawCircle(Image *dst, int centerX, int centerY, int radius, Color color) -{ - int x = 0, y = radius; - int decesionParameter = 3 - 2*radius; - - while (y >= x) - { - ImageDrawPixel(dst, centerX + x, centerY + y, color); - ImageDrawPixel(dst, centerX - x, centerY + y, color); - ImageDrawPixel(dst, centerX + x, centerY - y, color); - ImageDrawPixel(dst, centerX - x, centerY - y, color); - ImageDrawPixel(dst, centerX + y, centerY + x, color); - ImageDrawPixel(dst, centerX - y, centerY + x, color); - ImageDrawPixel(dst, centerX + y, centerY - x, color); - ImageDrawPixel(dst, centerX - y, centerY - x, color); - x++; - - if (decesionParameter > 0) - { - y--; - decesionParameter = decesionParameter + 4*(x - y) + 10; - } - else decesionParameter = decesionParameter + 4*x + 6; - } -} - -// Draw circle within an image (Vector version) -void ImageDrawCircleV(Image *dst, Vector2 center, int radius, Color color) -{ - ImageDrawCircle(dst, (int)center.x, (int)center.y, radius, color); -} - -// Draw line within an image -void ImageDrawLine(Image *dst, int startPosX, int startPosY, int endPosX, int endPosY, Color color) -{ - int m = 2*(endPosY - startPosY); - int slopeError = m - (startPosY - startPosX); + if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; - for (int x = startPosX, y = startPosY; x <= startPosY; x++) + if (image->mipmaps > 1) TRACELOG(LOG_WARNING, "Image manipulation only applied to base mipmap level"); + if (image->format >= PIXELFORMAT_COMPRESSED_DXT1_RGB) TRACELOG(LOG_WARNING, "Image manipulation not supported for compressed formats"); + else { - ImageDrawPixel(dst, x, y, color); - slopeError += m; + int bytesPerPixel = GetPixelDataSize(1, 1, image->format); + unsigned char *flippedData = (unsigned char *)RL_MALLOC(image->width*image->height*bytesPerPixel); - if (slopeError >= 0) + for (int y = 0; y < image->height; y++) { - y++; - slopeError -= 2*(startPosY - startPosX); - } - } -} - -// Draw line within an image (Vector version) -void ImageDrawLineV(Image *dst, Vector2 start, Vector2 end, Color color) -{ - ImageDrawLine(dst, (int)start.x, (int)start.y, (int)end.x, (int)end.y, color); -} - -// Draw text (default font) within an image (destination) -void ImageDrawText(Image *dst, Vector2 position, const char *text, int fontSize, Color color) -{ - // NOTE: For default font, sapcing is set to desired font size / default font size (10) - ImageDrawTextEx(dst, position, GetFontDefault(), text, (float)fontSize, (float)fontSize/10, color); -} - -// Draw text (custom sprite font) within an image (destination) -void ImageDrawTextEx(Image *dst, Vector2 position, Font font, const char *text, float fontSize, float spacing, Color color) -{ - Image imText = ImageTextEx(font, text, fontSize, spacing, color); - - Rectangle srcRec = { 0.0f, 0.0f, (float)imText.width, (float)imText.height }; - Rectangle dstRec = { position.x, position.y, (float)imText.width, (float)imText.height }; - - ImageDraw(dst, imText, srcRec, dstRec, WHITE); - - UnloadImage(imText); -} - -// Flip image vertically -void ImageFlipVertical(Image *image) -{ - // Security check to avoid program crash - if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; - - Color *srcPixels = GetImageData(*image); - Color *dstPixels = (Color *)RL_MALLOC(image->width*image->height*sizeof(Color)); + for (int x = 0; x < image->width; x++) + { + // OPTION 1: Move pixels with memcopy() + //memcpy(flippedData + (y*image->width + x)*bytesPerPixel, ((unsigned char *)image->data) + (y*image->width + (image->width - 1 - x))*bytesPerPixel, bytesPerPixel); - for (int y = 0; y < image->height; y++) - { - for (int x = 0; x < image->width; x++) - { - dstPixels[y*image->width + x] = srcPixels[(image->height - 1 - y)*image->width + x]; + // OPTION 2: Just copy data pixel by pixel + for (int i = 0; i < bytesPerPixel; i++) flippedData[(y*image->width + x)*bytesPerPixel + i] = ((unsigned char *)image->data)[(y*image->width + (image->width - 1 - x))*bytesPerPixel + i]; + } } - } - - Image processed = LoadImageEx(dstPixels, image->width, image->height); - ImageFormat(&processed, image->format); - UnloadImage(*image); - - RL_FREE(srcPixels); - RL_FREE(dstPixels); - - image->data = processed.data; -} - -// Flip image horizontally -void ImageFlipHorizontal(Image *image) -{ - // Security check to avoid program crash - if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; - Color *srcPixels = GetImageData(*image); - Color *dstPixels = (Color *)RL_MALLOC(image->width*image->height*sizeof(Color)); + RL_FREE(image->data); + image->data = flippedData; - for (int y = 0; y < image->height; y++) - { - for (int x = 0; x < image->width; x++) + /* + // OPTION 3: Faster implementation (specific for 32bit pixels) + // NOTE: It does not require additional allocations + uint32_t *ptr = (uint32_t *)image->data; + for (int y = 0; y < image->height; y++) { - dstPixels[y*image->width + x] = srcPixels[y*image->width + (image->width - 1 - x)]; + for (int x = 0; x < image->width/2; x++) + { + uint32_t backup = ptr[y*image->width + x]; + ptr[y*image->width + x] = ptr[y*image->width + (image->width - 1 - x)]; + ptr[y*image->width + (image->width - 1 - x)] = backup; + } } + */ } - - Image processed = LoadImageEx(dstPixels, image->width, image->height); - ImageFormat(&processed, image->format); - UnloadImage(*image); - - RL_FREE(srcPixels); - RL_FREE(dstPixels); - - image->data = processed.data; } // Rotate image clockwise 90deg @@ -2174,27 +1736,30 @@ void ImageRotateCW(Image *image) // Security check to avoid program crash if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; - Color *srcPixels = GetImageData(*image); - Color *rotPixels = (Color *)RL_MALLOC(image->width*image->height*sizeof(Color)); - - for (int y = 0; y < image->height; y++) + if (image->mipmaps > 1) TRACELOG(LOG_WARNING, "Image manipulation only applied to base mipmap level"); + if (image->format >= PIXELFORMAT_COMPRESSED_DXT1_RGB) TRACELOG(LOG_WARNING, "Image manipulation not supported for compressed formats"); + else { - for (int x = 0; x < image->width; x++) + int bytesPerPixel = GetPixelDataSize(1, 1, image->format); + unsigned char *rotatedData = (unsigned char *)RL_MALLOC(image->width*image->height*bytesPerPixel); + + for (int y = 0; y < image->height; y++) { - rotPixels[x*image->height + (image->height - y - 1)] = srcPixels[y*image->width + x]; + for (int x = 0; x < image->width; x++) + { + //memcpy(rotatedData + (x*image->height + (image->height - y - 1))*bytesPerPixel, ((unsigned char *)image->data) + (y*image->width + x)*bytesPerPixel, bytesPerPixel); + for (int i = 0; i < bytesPerPixel; i++) rotatedData[(x*image->height + (image->height - y - 1))*bytesPerPixel + i] = ((unsigned char *)image->data)[(y*image->width + x)*bytesPerPixel + i]; + } } - } - Image processed = LoadImageEx(rotPixels, image->height, image->width); - ImageFormat(&processed, image->format); - UnloadImage(*image); + RL_FREE(image->data); + image->data = rotatedData; + int width = image->width; + int height = image-> height; - RL_FREE(srcPixels); - RL_FREE(rotPixels); - - image->data = processed.data; - image->width = processed.width; - image->height = processed.height; + image->width = height; + image->height = width; + } } // Rotate image counter-clockwise 90deg @@ -2203,27 +1768,30 @@ void ImageRotateCCW(Image *image) // Security check to avoid program crash if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; - Color *srcPixels = GetImageData(*image); - Color *rotPixels = (Color *)RL_MALLOC(image->width*image->height*sizeof(Color)); - - for (int y = 0; y < image->height; y++) + if (image->mipmaps > 1) TRACELOG(LOG_WARNING, "Image manipulation only applied to base mipmap level"); + if (image->format >= PIXELFORMAT_COMPRESSED_DXT1_RGB) TRACELOG(LOG_WARNING, "Image manipulation not supported for compressed formats"); + else { - for (int x = 0; x < image->width; x++) + int bytesPerPixel = GetPixelDataSize(1, 1, image->format); + unsigned char *rotatedData = (unsigned char *)RL_MALLOC(image->width*image->height*bytesPerPixel); + + for (int y = 0; y < image->height; y++) { - rotPixels[x*image->height + y] = srcPixels[y*image->width + (image->width - x - 1)]; + for (int x = 0; x < image->width; x++) + { + //memcpy(rotatedData + (x*image->height + y))*bytesPerPixel, ((unsigned char *)image->data) + (y*image->width + (image->width - x - 1))*bytesPerPixel, bytesPerPixel); + for (int i = 0; i < bytesPerPixel; i++) rotatedData[(x*image->height + y)*bytesPerPixel + i] = ((unsigned char *)image->data)[(y*image->width + (image->width - x - 1))*bytesPerPixel + i]; + } } - } - - Image processed = LoadImageEx(rotPixels, image->height, image->width); - ImageFormat(&processed, image->format); - UnloadImage(*image); - RL_FREE(srcPixels); - RL_FREE(rotPixels); + RL_FREE(image->data); + image->data = rotatedData; + int width = image->width; + int height = image-> height; - image->data = processed.data; - image->width = processed.width; - image->height = processed.height; + image->width = height; + image->height = width; + } } // Modify image color: tint @@ -2232,7 +1800,7 @@ void ImageColorTint(Image *image, Color color) // Security check to avoid program crash if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; - Color *pixels = GetImageData(*image); + Color *pixels = LoadImageColors(*image); float cR = (float)color.r/255; float cG = (float)color.g/255; @@ -2243,11 +1811,11 @@ void ImageColorTint(Image *image, Color color) { for (int x = 0; x < image->width; x++) { - int index = y * image->width + x; - unsigned char r = 255*((float)pixels[index].r/255*cR); - unsigned char g = 255*((float)pixels[index].g/255*cG); - unsigned char b = 255*((float)pixels[index].b/255*cB); - unsigned char a = 255*((float)pixels[index].a/255*cA); + int index = y*image->width + x; + unsigned char r = (unsigned char)(((float)pixels[index].r/255*cR)*255.0f); + unsigned char g = (unsigned char)(((float)pixels[index].g/255*cG)*255.0f); + unsigned char b = (unsigned char)(((float)pixels[index].b/255*cB)*255.0f); + unsigned char a = (unsigned char)(((float)pixels[index].a/255*cA)*255.0f); pixels[y*image->width + x].r = r; pixels[y*image->width + x].g = g; @@ -2256,12 +1824,13 @@ void ImageColorTint(Image *image, Color color) } } - Image processed = LoadImageEx(pixels, image->width, image->height); - ImageFormat(&processed, image->format); - UnloadImage(*image); - RL_FREE(pixels); + int format = image->format; + RL_FREE(image->data); - image->data = processed.data; + image->data = pixels; + image->format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8; + + ImageFormat(image, format); } // Modify image color: invert @@ -2270,7 +1839,7 @@ void ImageColorInvert(Image *image) // Security check to avoid program crash if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; - Color *pixels = GetImageData(*image); + Color *pixels = LoadImageColors(*image); for (int y = 0; y < image->height; y++) { @@ -2282,18 +1851,19 @@ void ImageColorInvert(Image *image) } } - Image processed = LoadImageEx(pixels, image->width, image->height); - ImageFormat(&processed, image->format); - UnloadImage(*image); - RL_FREE(pixels); + int format = image->format; + RL_FREE(image->data); + + image->data = pixels; + image->format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8; - image->data = processed.data; + ImageFormat(image, format); } // Modify image color: grayscale void ImageColorGrayscale(Image *image) { - ImageFormat(image, UNCOMPRESSED_GRAYSCALE); + ImageFormat(image, PIXELFORMAT_UNCOMPRESSED_GRAYSCALE); } // Modify image color: contrast @@ -2309,7 +1879,7 @@ void ImageColorContrast(Image *image, float contrast) contrast = (100.0f + contrast)/100.0f; contrast *= contrast; - Color *pixels = GetImageData(*image); + Color *pixels = LoadImageColors(*image); for (int y = 0; y < image->height; y++) { @@ -2345,12 +1915,13 @@ void ImageColorContrast(Image *image, float contrast) } } - Image processed = LoadImageEx(pixels, image->width, image->height); - ImageFormat(&processed, image->format); - UnloadImage(*image); - RL_FREE(pixels); + int format = image->format; + RL_FREE(image->data); - image->data = processed.data; + image->data = pixels; + image->format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8; + + ImageFormat(image, format); } // Modify image color: brightness @@ -2363,7 +1934,7 @@ void ImageColorBrightness(Image *image, int brightness) if (brightness < -255) brightness = -255; if (brightness > 255) brightness = 255; - Color *pixels = GetImageData(*image); + Color *pixels = LoadImageColors(*image); for (int y = 0; y < image->height; y++) { @@ -2388,12 +1959,13 @@ void ImageColorBrightness(Image *image, int brightness) } } - Image processed = LoadImageEx(pixels, image->width, image->height); - ImageFormat(&processed, image->format); - UnloadImage(*image); - RL_FREE(pixels); + int format = image->format; + RL_FREE(image->data); + + image->data = pixels; + image->format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8; - image->data = processed.data; + ImageFormat(image, format); } // Modify image color: replace color @@ -2402,7 +1974,7 @@ void ImageColorReplace(Image *image, Color color, Color replace) // Security check to avoid program crash if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; - Color *pixels = GetImageData(*image); + Color *pixels = LoadImageColors(*image); for (int y = 0; y < image->height; y++) { @@ -2421,237 +1993,966 @@ void ImageColorReplace(Image *image, Color color, Color replace) } } - Image processed = LoadImageEx(pixels, image->width, image->height); - ImageFormat(&processed, image->format); - UnloadImage(*image); - RL_FREE(pixels); + int format = image->format; + RL_FREE(image->data); + + image->data = pixels; + image->format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8; - image->data = processed.data; + ImageFormat(image, format); } #endif // SUPPORT_IMAGE_MANIPULATION -// Generate image: plain color -Image GenImageColor(int width, int height, Color color) +// Load color data from image as a Color array (RGBA - 32bit) +// NOTE: Memory allocated should be freed using UnloadImageColors(); +Color *LoadImageColors(Image image) { - Color *pixels = (Color *)RL_CALLOC(width*height, sizeof(Color)); + if ((image.width == 0) || (image.height == 0)) return NULL; - for (int i = 0; i < width*height; i++) pixels[i] = color; + Color *pixels = (Color *)RL_MALLOC(image.width*image.height*sizeof(Color)); - Image image = LoadImageEx(pixels, width, height); + if (image.format >= PIXELFORMAT_COMPRESSED_DXT1_RGB) TRACELOG(LOG_WARNING, "IMAGE: Pixel data retrieval not supported for compressed image formats"); + else + { + if ((image.format == PIXELFORMAT_UNCOMPRESSED_R32) || + (image.format == PIXELFORMAT_UNCOMPRESSED_R32G32B32) || + (image.format == PIXELFORMAT_UNCOMPRESSED_R32G32B32A32)) TRACELOG(LOG_WARNING, "IMAGE: Pixel format converted from 32bit to 8bit per channel"); - RL_FREE(pixels); + for (int i = 0, k = 0; i < image.width*image.height; i++) + { + switch (image.format) + { + case PIXELFORMAT_UNCOMPRESSED_GRAYSCALE: + { + pixels[i].r = ((unsigned char *)image.data)[i]; + pixels[i].g = ((unsigned char *)image.data)[i]; + pixels[i].b = ((unsigned char *)image.data)[i]; + pixels[i].a = 255; - return image; + } break; + case PIXELFORMAT_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 PIXELFORMAT_UNCOMPRESSED_R5G5B5A1: + { + unsigned short pixel = ((unsigned short *)image.data)[i]; + + 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); + + } break; + case PIXELFORMAT_UNCOMPRESSED_R5G6B5: + { + unsigned short pixel = ((unsigned short *)image.data)[i]; + + 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; + + } break; + case PIXELFORMAT_UNCOMPRESSED_R4G4B4A4: + { + unsigned short pixel = ((unsigned short *)image.data)[i]; + + 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)); + + } break; + case PIXELFORMAT_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 PIXELFORMAT_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; + case PIXELFORMAT_UNCOMPRESSED_R32: + { + pixels[i].r = (unsigned char)(((float *)image.data)[k]*255.0f); + pixels[i].g = 0; + pixels[i].b = 0; + pixels[i].a = 255; + + } break; + case PIXELFORMAT_UNCOMPRESSED_R32G32B32: + { + pixels[i].r = (unsigned char)(((float *)image.data)[k]*255.0f); + pixels[i].g = (unsigned char)(((float *)image.data)[k + 1]*255.0f); + pixels[i].b = (unsigned char)(((float *)image.data)[k + 2]*255.0f); + pixels[i].a = 255; + + k += 3; + } break; + case PIXELFORMAT_UNCOMPRESSED_R32G32B32A32: + { + pixels[i].r = (unsigned char)(((float *)image.data)[k]*255.0f); + pixels[i].g = (unsigned char)(((float *)image.data)[k]*255.0f); + pixels[i].b = (unsigned char)(((float *)image.data)[k]*255.0f); + pixels[i].a = (unsigned char)(((float *)image.data)[k]*255.0f); + + k += 4; + } break; + default: break; + } + } + } + + return pixels; } -#if defined(SUPPORT_IMAGE_GENERATION) -// Generate image: vertical gradient -Image GenImageGradientV(int width, int height, Color top, Color bottom) +// Load colors palette from image as a Color array (RGBA - 32bit) +// NOTE: Memory allocated should be freed using UnloadImagePalette() +Color *LoadImagePalette(Image image, int maxPaletteSize, int *colorsCount) { - Color *pixels = (Color *)RL_MALLOC(width*height*sizeof(Color)); + #define COLOR_EQUAL(col1, col2) ((col1.r == col2.r)&&(col1.g == col2.g)&&(col1.b == col2.b)&&(col1.a == col2.a)) - for (int j = 0; j < height; j++) + int palCount = 0; + Color *palette = NULL; + Color *pixels = LoadImageColors(image); + + if (pixels != NULL) { - float factor = (float)j/(float)height; - for (int i = 0; i < width; i++) + palette = (Color *)RL_MALLOC(maxPaletteSize*sizeof(Color)); + + for (int i = 0; i < maxPaletteSize; i++) palette[i] = BLANK; // Set all colors to BLANK + + for (int i = 0; i < image.width*image.height; i++) { - pixels[j*width + i].r = (int)((float)bottom.r*factor + (float)top.r*(1.f - factor)); - pixels[j*width + i].g = (int)((float)bottom.g*factor + (float)top.g*(1.f - factor)); - pixels[j*width + i].b = (int)((float)bottom.b*factor + (float)top.b*(1.f - factor)); - pixels[j*width + i].a = (int)((float)bottom.a*factor + (float)top.a*(1.f - factor)); + if (pixels[i].a > 0) + { + bool colorInPalette = false; + + // Check if the color is already on palette + for (int j = 0; j < maxPaletteSize; j++) + { + if (COLOR_EQUAL(pixels[i], palette[j])) + { + colorInPalette = true; + break; + } + } + + // Store color if not on the palette + if (!colorInPalette) + { + palette[palCount] = pixels[i]; // Add pixels[i] to palette + palCount++; + + // We reached the limit of colors supported by palette + if (palCount >= maxPaletteSize) + { + i = image.width*image.height; // Finish palette get + TRACELOG(LOG_WARNING, "IMAGE: Palette is greater than %i colors", maxPaletteSize); + } + } + } } + + UnloadImageColors(pixels); } - Image image = LoadImageEx(pixels, width, height); - RL_FREE(pixels); + *colorsCount = palCount; - return image; + return palette; } -// Generate image: horizontal gradient -Image GenImageGradientH(int width, int height, Color left, Color right) +// Unload color data loaded with LoadImageColors() +void UnloadImageColors(Color *colors) { - Color *pixels = (Color *)RL_MALLOC(width*height*sizeof(Color)); + RL_FREE(colors); +} - for (int i = 0; i < width; i++) +// Unload colors palette loaded with LoadImagePalette() +void UnloadImagePalette(Color *colors) +{ + RL_FREE(colors); +} + +// Get pixel data from image as Vector4 array (float normalized) +static Vector4 *LoadImageDataNormalized(Image image) +{ + Vector4 *pixels = (Vector4 *)RL_MALLOC(image.width*image.height*sizeof(Vector4)); + + if (image.format >= PIXELFORMAT_COMPRESSED_DXT1_RGB) TRACELOG(LOG_WARNING, "IMAGE: Pixel data retrieval not supported for compressed image formats"); + else { - float factor = (float)i/(float)width; - for (int j = 0; j < height; j++) + for (int i = 0, k = 0; i < image.width*image.height; i++) { - pixels[j*width + i].r = (int)((float)right.r*factor + (float)left.r*(1.f - factor)); - pixels[j*width + i].g = (int)((float)right.g*factor + (float)left.g*(1.f - factor)); - pixels[j*width + i].b = (int)((float)right.b*factor + (float)left.b*(1.f - factor)); - pixels[j*width + i].a = (int)((float)right.a*factor + (float)left.a*(1.f - factor)); + switch (image.format) + { + case PIXELFORMAT_UNCOMPRESSED_GRAYSCALE: + { + pixels[i].x = (float)((unsigned char *)image.data)[i]/255.0f; + pixels[i].y = (float)((unsigned char *)image.data)[i]/255.0f; + pixels[i].z = (float)((unsigned char *)image.data)[i]/255.0f; + pixels[i].w = 1.0f; + + } break; + case PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA: + { + pixels[i].x = (float)((unsigned char *)image.data)[k]/255.0f; + pixels[i].y = (float)((unsigned char *)image.data)[k]/255.0f; + pixels[i].z = (float)((unsigned char *)image.data)[k]/255.0f; + pixels[i].w = (float)((unsigned char *)image.data)[k + 1]/255.0f; + + k += 2; + } break; + case PIXELFORMAT_UNCOMPRESSED_R5G5B5A1: + { + unsigned short pixel = ((unsigned short *)image.data)[i]; + + pixels[i].x = (float)((pixel & 0b1111100000000000) >> 11)*(1.0f/31); + pixels[i].y = (float)((pixel & 0b0000011111000000) >> 6)*(1.0f/31); + pixels[i].z = (float)((pixel & 0b0000000000111110) >> 1)*(1.0f/31); + pixels[i].w = ((pixel & 0b0000000000000001) == 0)? 0.0f : 1.0f; + + } break; + case PIXELFORMAT_UNCOMPRESSED_R5G6B5: + { + unsigned short pixel = ((unsigned short *)image.data)[i]; + + pixels[i].x = (float)((pixel & 0b1111100000000000) >> 11)*(1.0f/31); + pixels[i].y = (float)((pixel & 0b0000011111100000) >> 5)*(1.0f/63); + pixels[i].z = (float)(pixel & 0b0000000000011111)*(1.0f/31); + pixels[i].w = 1.0f; + + } break; + case PIXELFORMAT_UNCOMPRESSED_R4G4B4A4: + { + unsigned short pixel = ((unsigned short *)image.data)[i]; + + pixels[i].x = (float)((pixel & 0b1111000000000000) >> 12)*(1.0f/15); + pixels[i].y = (float)((pixel & 0b0000111100000000) >> 8)*(1.0f/15); + pixels[i].z = (float)((pixel & 0b0000000011110000) >> 4)*(1.0f/15); + pixels[i].w = (float)(pixel & 0b0000000000001111)*(1.0f/15); + + } break; + case PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: + { + pixels[i].x = (float)((unsigned char *)image.data)[k]/255.0f; + pixels[i].y = (float)((unsigned char *)image.data)[k + 1]/255.0f; + pixels[i].z = (float)((unsigned char *)image.data)[k + 2]/255.0f; + pixels[i].w = (float)((unsigned char *)image.data)[k + 3]/255.0f; + + k += 4; + } break; + case PIXELFORMAT_UNCOMPRESSED_R8G8B8: + { + pixels[i].x = (float)((unsigned char *)image.data)[k]/255.0f; + pixels[i].y = (float)((unsigned char *)image.data)[k + 1]/255.0f; + pixels[i].z = (float)((unsigned char *)image.data)[k + 2]/255.0f; + pixels[i].w = 1.0f; + + k += 3; + } break; + case PIXELFORMAT_UNCOMPRESSED_R32: + { + pixels[i].x = ((float *)image.data)[k]; + pixels[i].y = 0.0f; + pixels[i].z = 0.0f; + pixels[i].w = 1.0f; + + } break; + case PIXELFORMAT_UNCOMPRESSED_R32G32B32: + { + pixels[i].x = ((float *)image.data)[k]; + pixels[i].y = ((float *)image.data)[k + 1]; + pixels[i].z = ((float *)image.data)[k + 2]; + pixels[i].w = 1.0f; + + k += 3; + } break; + case PIXELFORMAT_UNCOMPRESSED_R32G32B32A32: + { + pixels[i].x = ((float *)image.data)[k]; + pixels[i].y = ((float *)image.data)[k + 1]; + pixels[i].z = ((float *)image.data)[k + 2]; + pixels[i].w = ((float *)image.data)[k + 3]; + + k += 4; + } + default: break; + } } } - Image image = LoadImageEx(pixels, width, height); - RL_FREE(pixels); + return pixels; +} - return image; +// Get image alpha border rectangle +// NOTE: Threshold is defined as a percentatge: 0.0f -> 1.0f +Rectangle GetImageAlphaBorder(Image image, float threshold) +{ + Rectangle crop = { 0 }; + + Color *pixels = LoadImageColors(image); + + if (pixels != NULL) + { + int xMin = 65536; // Define a big enough number + int xMax = 0; + int yMin = 65536; + int yMax = 0; + + for (int y = 0; y < image.height; y++) + { + for (int x = 0; x < image.width; x++) + { + if (pixels[y*image.width + x].a > (unsigned char)(threshold*255.0f)) + { + if (x < xMin) xMin = x; + if (x > xMax) xMax = x; + if (y < yMin) yMin = y; + if (y > yMax) yMax = y; + } + } + } + + // Check for empty blank image + if ((xMin != 65536) && (xMax != 65536)) + { + crop = (Rectangle){ (float)xMin, (float)yMin, (float)((xMax + 1) - xMin), (float)((yMax + 1) - yMin) }; + } + + UnloadImageColors(pixels); + } + + return crop; } -// Generate image: radial gradient -Image GenImageGradientRadial(int width, int height, float density, Color inner, Color outer) +//------------------------------------------------------------------------------------ +// Image drawing functions +//------------------------------------------------------------------------------------ +// Clear image background with given color +void ImageClearBackground(Image *dst, Color color) { - Color *pixels = (Color *)RL_MALLOC(width*height*sizeof(Color)); - float radius = (width < height)? (float)width/2.0f : (float)height/2.0f; + for (int i = 0; i < dst->width*dst->height; ++i) ImageDrawPixel(dst, i%dst->width, i/dst->width, color); +} - float centerX = (float)width/2.0f; - float centerY = (float)height/2.0f; +// Draw pixel within an image +// NOTE: Compressed image formats not supported +void ImageDrawPixel(Image *dst, int x, int y, Color color) +{ + // Security check to avoid program crash + if ((dst->data == NULL) || (x < 0) || (x >= dst->width) || (y < 0) || (y >= dst->height)) return; - for (int y = 0; y < height; y++) + switch (dst->format) { - for (int x = 0; x < width; x++) + case PIXELFORMAT_UNCOMPRESSED_GRAYSCALE: { - float dist = hypotf((float)x - centerX, (float)y - centerY); - float factor = (dist - radius*density)/(radius*(1.0f - density)); + // NOTE: Calculate grayscale equivalent color + Vector3 coln = { (float)color.r/255.0f, (float)color.g/255.0f, (float)color.b/255.0f }; + unsigned char gray = (unsigned char)((coln.x*0.299f + coln.y*0.587f + coln.z*0.114f)*255.0f); - factor = (float)fmax(factor, 0.f); - factor = (float)fmin(factor, 1.f); // dist can be bigger than radius so we have to check + ((unsigned char *)dst->data)[y*dst->width + x] = gray; - pixels[y*width + x].r = (int)((float)outer.r*factor + (float)inner.r*(1.0f - factor)); - pixels[y*width + x].g = (int)((float)outer.g*factor + (float)inner.g*(1.0f - factor)); - pixels[y*width + x].b = (int)((float)outer.b*factor + (float)inner.b*(1.0f - factor)); - pixels[y*width + x].a = (int)((float)outer.a*factor + (float)inner.a*(1.0f - factor)); - } + } break; + case PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA: + { + // NOTE: Calculate grayscale equivalent color + Vector3 coln = { (float)color.r/255.0f, (float)color.g/255.0f, (float)color.b/255.0f }; + unsigned char gray = (unsigned char)((coln.x*0.299f + coln.y*0.587f + coln.z*0.114f)*255.0f); + + ((unsigned char *)dst->data)[(y*dst->width + x)*2] = gray; + ((unsigned char *)dst->data)[(y*dst->width + x)*2 + 1] = color.a; + + } break; + case PIXELFORMAT_UNCOMPRESSED_R5G6B5: + { + // NOTE: Calculate R5G6B5 equivalent color + Vector3 coln = { (float)color.r/255.0f, (float)color.g/255.0f, (float)color.b/255.0f }; + + unsigned char r = (unsigned char)(round(coln.x*31.0f)); + unsigned char g = (unsigned char)(round(coln.y*63.0f)); + unsigned char b = (unsigned char)(round(coln.z*31.0f)); + + ((unsigned short *)dst->data)[y*dst->width + x] = (unsigned short)r << 11 | (unsigned short)g << 5 | (unsigned short)b; + + } break; + case PIXELFORMAT_UNCOMPRESSED_R5G5B5A1: + { + // NOTE: Calculate R5G5B5A1 equivalent color + Vector4 coln = { (float)color.r/255.0f, (float)color.g/255.0f, (float)color.b/255.0f, (float)color.a/255.0f }; + + unsigned char r = (unsigned char)(round(coln.x*31.0f)); + unsigned char g = (unsigned char)(round(coln.y*31.0f)); + unsigned char b = (unsigned char)(round(coln.z*31.0f)); + unsigned char a = (coln.w > ((float)PIXELFORMAT_UNCOMPRESSED_R5G5B5A1_ALPHA_THRESHOLD/255.0f))? 1 : 0;; + + ((unsigned short *)dst->data)[y*dst->width + x] = (unsigned short)r << 11 | (unsigned short)g << 6 | (unsigned short)b << 1 | (unsigned short)a; + + } break; + case PIXELFORMAT_UNCOMPRESSED_R4G4B4A4: + { + // NOTE: Calculate R5G5B5A1 equivalent color + Vector4 coln = { (float)color.r/255.0f, (float)color.g/255.0f, (float)color.b/255.0f, (float)color.a/255.0f }; + + unsigned char r = (unsigned char)(round(coln.x*15.0f)); + unsigned char g = (unsigned char)(round(coln.y*15.0f)); + unsigned char b = (unsigned char)(round(coln.z*15.0f)); + unsigned char a = (unsigned char)(round(coln.w*15.0f)); + + ((unsigned short *)dst->data)[y*dst->width + x] = (unsigned short)r << 12 | (unsigned short)g << 8 | (unsigned short)b << 4 | (unsigned short)a; + + } break; + case PIXELFORMAT_UNCOMPRESSED_R8G8B8: + { + ((unsigned char *)dst->data)[(y*dst->width + x)*3] = color.r; + ((unsigned char *)dst->data)[(y*dst->width + x)*3 + 1] = color.g; + ((unsigned char *)dst->data)[(y*dst->width + x)*3 + 2] = color.b; + + } break; + case PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: + { + ((unsigned char *)dst->data)[(y*dst->width + x)*4] = color.r; + ((unsigned char *)dst->data)[(y*dst->width + x)*4 + 1] = color.g; + ((unsigned char *)dst->data)[(y*dst->width + x)*4 + 2] = color.b; + ((unsigned char *)dst->data)[(y*dst->width + x)*4 + 3] = color.a; + + } break; + case PIXELFORMAT_UNCOMPRESSED_R32: + { + // NOTE: Calculate grayscale equivalent color (normalized to 32bit) + Vector3 coln = { (float)color.r/255.0f, (float)color.g/255.0f, (float)color.b/255.0f }; + + ((float *)dst->data)[y*dst->width + x] = coln.x*0.299f + coln.y*0.587f + coln.z*0.114f; + + } break; + case PIXELFORMAT_UNCOMPRESSED_R32G32B32: + { + // NOTE: Calculate R32G32B32 equivalent color (normalized to 32bit) + Vector3 coln = { (float)color.r/255.0f, (float)color.g/255.0f, (float)color.b/255.0f }; + + ((float *)dst->data)[(y*dst->width + x)*3] = coln.x; + ((float *)dst->data)[(y*dst->width + x)*3 + 1] = coln.y; + ((float *)dst->data)[(y*dst->width + x)*3 + 2] = coln.z; + } break; + case PIXELFORMAT_UNCOMPRESSED_R32G32B32A32: + { + // NOTE: Calculate R32G32B32A32 equivalent color (normalized to 32bit) + Vector4 coln = { (float)color.r/255.0f, (float)color.g/255.0f, (float)color.b/255.0f, (float)color.a/255.0f }; + + ((float *)dst->data)[(y*dst->width + x)*4] = coln.x; + ((float *)dst->data)[(y*dst->width + x)*4 + 1] = coln.y; + ((float *)dst->data)[(y*dst->width + x)*4 + 2] = coln.z; + ((float *)dst->data)[(y*dst->width + x)*4 + 3] = coln.w; + + } break; + default: break; } +} - Image image = LoadImageEx(pixels, width, height); - RL_FREE(pixels); +// Draw pixel within an image (Vector version) +void ImageDrawPixelV(Image *dst, Vector2 position, Color color) +{ + ImageDrawPixel(dst, (int)position.x, (int)position.y, color); +} - return image; +// Draw line within an image +void ImageDrawLine(Image *dst, int startPosX, int startPosY, int endPosX, int endPosY, Color color) +{ + int m = 2*(endPosY - startPosY); + int slopeError = m - (endPosX - startPosX); + + for (int x = startPosX, y = startPosY; x <= endPosX; x++) + { + ImageDrawPixel(dst, x, y, color); + slopeError += m; + + if (slopeError >= 0) + { + y++; + slopeError -= 2*(endPosX - startPosX); + } + } } -// Generate image: checked -Image GenImageChecked(int width, int height, int checksX, int checksY, Color col1, Color col2) +// Draw line within an image (Vector version) +void ImageDrawLineV(Image *dst, Vector2 start, Vector2 end, Color color) { - Color *pixels = (Color *)RL_MALLOC(width*height*sizeof(Color)); + ImageDrawLine(dst, (int)start.x, (int)start.y, (int)end.x, (int)end.y, color); +} - for (int y = 0; y < height; y++) +// Draw circle within an image +void ImageDrawCircle(Image *dst, int centerX, int centerY, int radius, Color color) +{ + int x = 0, y = radius; + int decesionParameter = 3 - 2*radius; + + while (y >= x) { - for (int x = 0; x < width; x++) + ImageDrawPixel(dst, centerX + x, centerY + y, color); + ImageDrawPixel(dst, centerX - x, centerY + y, color); + ImageDrawPixel(dst, centerX + x, centerY - y, color); + ImageDrawPixel(dst, centerX - x, centerY - y, color); + ImageDrawPixel(dst, centerX + y, centerY + x, color); + ImageDrawPixel(dst, centerX - y, centerY + x, color); + ImageDrawPixel(dst, centerX + y, centerY - x, color); + ImageDrawPixel(dst, centerX - y, centerY - x, color); + x++; + + if (decesionParameter > 0) { - if ((x/checksX + y/checksY)%2 == 0) pixels[y*width + x] = col1; - else pixels[y*width + x] = col2; + y--; + decesionParameter = decesionParameter + 4*(x - y) + 10; } + else decesionParameter = decesionParameter + 4*x + 6; } +} - Image image = LoadImageEx(pixels, width, height); - RL_FREE(pixels); +// Draw circle within an image (Vector version) +void ImageDrawCircleV(Image *dst, Vector2 center, int radius, Color color) +{ + ImageDrawCircle(dst, (int)center.x, (int)center.y, radius, color); +} - return image; +// Draw rectangle within an image +void ImageDrawRectangle(Image *dst, int posX, int posY, int width, int height, Color color) +{ + ImageDrawRectangleRec(dst, (Rectangle){ (float)posX, (float)posY, (float)width, (float)height }, color); } -// Generate image: white noise -Image GenImageWhiteNoise(int width, int height, float factor) +// Draw rectangle within an image (Vector version) +void ImageDrawRectangleV(Image *dst, Vector2 position, Vector2 size, Color color) { - Color *pixels = (Color *)RL_MALLOC(width*height*sizeof(Color)); + ImageDrawRectangle(dst, (int)position.x, (int)position.y, (int)size.x, (int)size.y, color); +} - for (int i = 0; i < width*height; i++) +// Draw rectangle within an image +void ImageDrawRectangleRec(Image *dst, Rectangle rec, Color color) +{ + // Security check to avoid program crash + if ((dst->data == NULL) || (dst->width == 0) || (dst->height == 0)) return; + + int sy = (int)rec.y; + int ey = sy + (int)rec.height; + + int sx = (int)rec.x; + int ex = sx + (int)rec.width; + + for (int y = sy; y < ey; y++) { - if (GetRandomValue(0, 99) < (int)(factor*100.0f)) pixels[i] = WHITE; - else pixels[i] = BLACK; + for (int x = sx; x < ex; x++) + { + ImageDrawPixel(dst, x, y, color); + } } +} - Image image = LoadImageEx(pixels, width, height); - RL_FREE(pixels); - - return image; +// Draw rectangle lines within an image +void ImageDrawRectangleLines(Image *dst, Rectangle rec, int thick, Color color) +{ + ImageDrawRectangle(dst, (int)rec.x, (int)rec.y, (int)rec.width, thick, color); + ImageDrawRectangle(dst, (int)rec.x, (int)(rec.y + thick), thick, (int)(rec.height - thick*2), color); + ImageDrawRectangle(dst, (int)(rec.x + rec.width - thick), (int)(rec.y + thick), thick, (int)(rec.height - thick*2), color); + ImageDrawRectangle(dst, (int)rec.x, (int)(rec.y + rec.height - thick), (int)rec.width, thick, color); } -// Generate image: perlin noise -Image GenImagePerlinNoise(int width, int height, int offsetX, int offsetY, float scale) +// Draw an image (source) within an image (destination) +// NOTE: Color tint is applied to source image +void ImageDraw(Image *dst, Image src, Rectangle srcRec, Rectangle dstRec, Color tint) { - Color *pixels = (Color *)RL_MALLOC(width*height*sizeof(Color)); + // Security check to avoid program crash + if ((dst->data == NULL) || (dst->width == 0) || (dst->height == 0) || + (src.data == NULL) || (src.width == 0) || (src.height == 0)) return; - for (int y = 0; y < height; y++) + if (dst->mipmaps > 1) TRACELOG(LOG_WARNING, "Image drawing only applied to base mipmap level"); + if (dst->format >= PIXELFORMAT_COMPRESSED_DXT1_RGB) TRACELOG(LOG_WARNING, "Image drawing not supported for compressed formats"); + else { - for (int x = 0; x < width; x++) + Image srcMod = { 0 }; // Source copy (in case it was required) + Image *srcPtr = &src; // Pointer to source image + bool useSrcMod = false; // Track source copy required + + // Source rectangle out-of-bounds security checks + if (srcRec.x < 0) { srcRec.width += srcRec.x; srcRec.x = 0; } + if (srcRec.y < 0) { srcRec.height += srcRec.y; srcRec.y = 0; } + if ((srcRec.x + srcRec.width) > src.width) srcRec.width = src.width - srcRec.x; + if ((srcRec.y + srcRec.height) > src.height) srcRec.height = src.height - srcRec.y; + + // Check if source rectangle needs to be resized to destination rectangle + // In that case, we make a copy of source and we apply all required transform + if (((int)srcRec.width != (int)dstRec.width) || ((int)srcRec.height != (int)dstRec.height)) { - float nx = (float)(x + offsetX)*scale/(float)width; - float ny = (float)(y + offsetY)*scale/(float)height; + srcMod = ImageFromImage(src, srcRec); // Create image from another image + ImageResize(&srcMod, (int)dstRec.width, (int)dstRec.height); // Resize to destination rectangle + srcRec = (Rectangle){ 0, 0, (float)srcMod.width, (float)srcMod.height }; - // Typical values to start playing with: - // lacunarity = ~2.0 -- spacing between successive octaves (use exactly 2.0 for wrapping output) - // gain = 0.5 -- relative weighting applied to each successive octave - // octaves = 6 -- number of "octaves" of noise3() to sum + srcPtr = &srcMod; + useSrcMod = true; + } - // NOTE: We need to translate the data from [-1..1] to [0..1] - float p = (stb_perlin_fbm_noise3(nx, ny, 1.0f, 2.0f, 0.5f, 6) + 1.0f)/2.0f; + // Destination rectangle out-of-bounds security checks + if (dstRec.x < 0) + { + srcRec.x = -dstRec.x; + srcRec.width += dstRec.x; + dstRec.x = 0; + } + else if ((dstRec.x + srcRec.width) > dst->width) srcRec.width = dst->width - dstRec.x; - int intensity = (int)(p*255.0f); - pixels[y*width + x] = (Color){intensity, intensity, intensity, 255}; + if (dstRec.y < 0) + { + srcRec.y = -dstRec.y; + srcRec.height += dstRec.y; + dstRec.y = 0; + } + else if ((dstRec.y + srcRec.height) > dst->height) srcRec.height = dst->height - dstRec.y; + + if (dst->width < srcRec.width) srcRec.width = (float)dst->width; + if (dst->height < srcRec.height) srcRec.height = (float)dst->height; + + // This blitting method is quite fast! The process followed is: + // for every pixel -> [get_src_format/get_dst_format -> blend -> format_to_dst] + // Some optimization ideas: + // [x] Avoid creating source copy if not required (no resize required) + // [x] Optimize ImageResize() for pixel format (alternative: ImageResizeNN()) + // [x] Optimize ColorAlphaBlend() to avoid processing (alpha = 0) and (alpha = 1) + // [x] Optimize ColorAlphaBlend() for faster operations (maybe avoiding divs?) + // [x] Consider fast path: no alpha blending required cases (src has no alpha) + // [x] Consider fast path: same src/dst format with no alpha -> direct line copy + // [-] GetPixelColor(): Return Vector4 instead of Color, easier for ColorAlphaBlend() + + Color colSrc, colDst, blend; + bool blendRequired = true; + + // Fast path: Avoid blend if source has no alpha to blend + if ((tint.a == 255) && ((srcPtr->format == PIXELFORMAT_UNCOMPRESSED_GRAYSCALE) || (srcPtr->format == PIXELFORMAT_UNCOMPRESSED_R8G8B8) || (srcPtr->format == PIXELFORMAT_UNCOMPRESSED_R5G6B5))) blendRequired = false; + + int strideDst = GetPixelDataSize(dst->width, 1, dst->format); + int bytesPerPixelDst = strideDst/(dst->width); + + int strideSrc = GetPixelDataSize(srcPtr->width, 1, srcPtr->format); + int bytesPerPixelSrc = strideSrc/(srcPtr->width); + + unsigned char *pSrcBase = (unsigned char *)srcPtr->data + ((int)srcRec.y*srcPtr->width + (int)srcRec.x)*bytesPerPixelSrc; + unsigned char *pDstBase = (unsigned char *)dst->data + ((int)dstRec.y*dst->width + (int)dstRec.x)*bytesPerPixelDst; + + for (int y = 0; y < (int)srcRec.height; y++) + { + unsigned char *pSrc = pSrcBase; + unsigned char *pDst = pDstBase; + + // Fast path: Avoid moving pixel by pixel if no blend required and same format + if (!blendRequired && (srcPtr->format == dst->format)) memcpy(pDst, pSrc, (int)(srcRec.width)*bytesPerPixelSrc); + else + { + for (int x = 0; x < (int)srcRec.width; x++) + { + colSrc = GetPixelColor(pSrc, srcPtr->format); + colDst = GetPixelColor(pDst, dst->format); + + // Fast path: Avoid blend if source has no alpha to blend + if (blendRequired) blend = ColorAlphaBlend(colDst, colSrc, tint); + else blend = colSrc; + + SetPixelColor(pDst, blend, dst->format); + + pDst += bytesPerPixelDst; + pSrc += bytesPerPixelSrc; + } + } + + pSrcBase += strideSrc; + pDstBase += strideDst; } + + if (useSrcMod) UnloadImage(srcMod); // Unload source modified image } +} - Image image = LoadImageEx(pixels, width, height); - RL_FREE(pixels); +// Draw text (default font) within an image (destination) +void ImageDrawText(Image *dst, const char *text, int posX, int posY, int fontSize, Color color) +{ + Vector2 position = { (float)posX, (float)posY }; - return image; + // NOTE: For default font, sapcing is set to desired font size / default font size (10) + ImageDrawTextEx(dst, GetFontDefault(), text, position, (float)fontSize, (float)fontSize/10, color); } -// Generate image: cellular algorithm. Bigger tileSize means bigger cells -Image GenImageCellular(int width, int height, int tileSize) +// Draw text (custom sprite font) within an image (destination) +void ImageDrawTextEx(Image *dst, Font font, const char *text, Vector2 position, float fontSize, float spacing, Color tint) { - Color *pixels = (Color *)RL_MALLOC(width*height*sizeof(Color)); + Image imText = ImageTextEx(font, text, fontSize, spacing, tint); - int seedsPerRow = width/tileSize; - int seedsPerCol = height/tileSize; - int seedsCount = seedsPerRow * seedsPerCol; + Rectangle srcRec = { 0.0f, 0.0f, (float)imText.width, (float)imText.height }; + Rectangle dstRec = { position.x, position.y, (float)imText.width, (float)imText.height }; - Vector2 *seeds = (Vector2 *)RL_MALLOC(seedsCount*sizeof(Vector2)); + ImageDraw(dst, imText, srcRec, dstRec, WHITE); - for (int i = 0; i < seedsCount; i++) + UnloadImage(imText); +} + +//------------------------------------------------------------------------------------ +// Texture loading functions +//------------------------------------------------------------------------------------ +// Load texture from file into GPU memory (VRAM) +Texture2D LoadTexture(const char *fileName) +{ + Texture2D texture = { 0 }; + + Image image = LoadImage(fileName); + + if (image.data != NULL) { - int y = (i/seedsPerRow)*tileSize + GetRandomValue(0, tileSize - 1); - int x = (i%seedsPerRow)*tileSize + GetRandomValue(0, tileSize - 1); - seeds[i] = (Vector2){ (float)x, (float)y}; + texture = LoadTextureFromImage(image); + UnloadImage(image); } - for (int y = 0; y < height; y++) + return texture; +} + +// Load a texture from image data +// NOTE: image is not unloaded, it must be done manually +Texture2D LoadTextureFromImage(Image image) +{ + Texture2D texture = { 0 }; + + if ((image.data != NULL) && (image.width != 0) && (image.height != 0)) { - int tileY = y/tileSize; + texture.id = rlLoadTexture(image.data, image.width, image.height, image.format, image.mipmaps); + } + else TRACELOG(LOG_WARNING, "IMAGE: Data is not valid to load texture"); - for (int x = 0; x < width; x++) + texture.width = image.width; + texture.height = image.height; + texture.mipmaps = image.mipmaps; + texture.format = image.format; + + return texture; +} + +// Load cubemap from image, multiple image cubemap layouts supported +TextureCubemap LoadTextureCubemap(Image image, int layout) +{ + TextureCubemap cubemap = { 0 }; + + if (layout == CUBEMAP_LAYOUT_AUTO_DETECT) // Try to automatically guess layout type + { + // Check image width/height to determine the type of cubemap provided + if (image.width > image.height) { - int tileX = x/tileSize; + if ((image.width/6) == image.height) { layout = CUBEMAP_LAYOUT_LINE_HORIZONTAL; cubemap.width = image.width/6; } + else if ((image.width/4) == (image.height/3)) { layout = CUBEMAP_LAYOUT_CROSS_FOUR_BY_THREE; cubemap.width = image.width/4; } + else if (image.width >= (int)((float)image.height*1.85f)) { layout = CUBEMAP_LAYOUT_PANORAMA; cubemap.width = image.width/4; } + } + else if (image.height > image.width) + { + if ((image.height/6) == image.width) { layout = CUBEMAP_LAYOUT_LINE_VERTICAL; cubemap.width = image.height/6; } + else if ((image.width/3) == (image.height/4)) { layout = CUBEMAP_LAYOUT_CROSS_THREE_BY_FOUR; cubemap.width = image.width/3; } + } - float minDistance = (float)strtod("Inf", NULL); + cubemap.height = cubemap.width; + } - // Check all adjacent tiles - for (int i = -1; i < 2; i++) + if (layout != CUBEMAP_LAYOUT_AUTO_DETECT) + { + int size = cubemap.width; + + Image faces = { 0 }; // Vertical column image + Rectangle faceRecs[6] = { 0 }; // Face source rectangles + for (int i = 0; i < 6; i++) faceRecs[i] = (Rectangle){ 0, 0, (float)size, (float)size }; + + if (layout == CUBEMAP_LAYOUT_LINE_VERTICAL) + { + faces = image; + for (int i = 0; i < 6; i++) faceRecs[i].y = (float)size*i; + } + else if (layout == CUBEMAP_LAYOUT_PANORAMA) + { + // TODO: Convert panorama image to square faces... + // Ref: https://github.com/denivip/panorama/blob/master/panorama.cpp + } + else + { + if (layout == CUBEMAP_LAYOUT_LINE_HORIZONTAL) for (int i = 0; i < 6; i++) faceRecs[i].x = (float)size*i; + else if (layout == CUBEMAP_LAYOUT_CROSS_THREE_BY_FOUR) { - if ((tileX + i < 0) || (tileX + i >= seedsPerRow)) continue; + faceRecs[0].x = (float)size; faceRecs[0].y = (float)size; + faceRecs[1].x = (float)size; faceRecs[1].y = (float)size*3; + faceRecs[2].x = (float)size; faceRecs[2].y = 0; + faceRecs[3].x = (float)size; faceRecs[3].y = (float)size*2; + faceRecs[4].x = 0; faceRecs[4].y = (float)size; + faceRecs[5].x = (float)size*2; faceRecs[5].y = (float)size; + } + else if (layout == CUBEMAP_LAYOUT_CROSS_FOUR_BY_THREE) + { + faceRecs[0].x = (float)size*2; faceRecs[0].y = (float)size; + faceRecs[1].x = 0; faceRecs[1].y = (float)size; + faceRecs[2].x = (float)size; faceRecs[2].y = 0; + faceRecs[3].x = (float)size; faceRecs[3].y = (float)size*2; + faceRecs[4].x = (float)size; faceRecs[4].y = (float)size; + faceRecs[5].x = (float)size*3; faceRecs[5].y = (float)size; + } - for (int j = -1; j < 2; j++) - { - if ((tileY + j < 0) || (tileY + j >= seedsPerCol)) continue; + // Convert image data to 6 faces in a vertical column, that's the optimum layout for loading + faces = GenImageColor(size, size*6, MAGENTA); + ImageFormat(&faces, image.format); - Vector2 neighborSeed = seeds[(tileY + j)*seedsPerRow + tileX + i]; + // TODO: Image formating does not work with compressed textures! + } - float dist = (float)hypot(x - (int)neighborSeed.x, y - (int)neighborSeed.y); - minDistance = (float)fmin(minDistance, dist); - } - } + for (int i = 0; i < 6; i++) ImageDraw(&faces, image, faceRecs[i], (Rectangle){ 0, (float)size*i, (float)size, (float)size }, WHITE); - // I made this up but it seems to give good results at all tile sizes - int intensity = (int)(minDistance*256.0f/tileSize); - if (intensity > 255) intensity = 255; + cubemap.id = rlLoadTextureCubemap(faces.data, size, faces.format); + if (cubemap.id == 0) TRACELOG(LOG_WARNING, "IMAGE: Failed to load cubemap image"); - pixels[y*width + x] = (Color){ intensity, intensity, intensity, 255 }; + UnloadImage(faces); + } + else TRACELOG(LOG_WARNING, "IMAGE: Failed to detect cubemap image layout"); + + return cubemap; +} + +// Load texture for rendering (framebuffer) +// NOTE: Render texture is loaded by default with RGBA color attachment and depth RenderBuffer +RenderTexture2D LoadRenderTexture(int width, int height) +{ + RenderTexture2D target = { 0 }; + + target.id = rlLoadFramebuffer(width, height); // Load an empty framebuffer + + if (target.id > 0) + { + rlEnableFramebuffer(target.id); + + // Create color texture (default to RGBA) + target.texture.id = rlLoadTexture(NULL, width, height, PIXELFORMAT_UNCOMPRESSED_R8G8B8A8, 1); + target.texture.width = width; + target.texture.height = height; + target.texture.format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8; + target.texture.mipmaps = 1; + + // Create depth renderbuffer/texture + target.depth.id = rlLoadTextureDepth(width, height, true); + target.depth.width = width; + target.depth.height = height; + target.depth.format = 19; //DEPTH_COMPONENT_24BIT? + target.depth.mipmaps = 1; + + // Attach color texture and depth renderbuffer/texture to FBO + rlFramebufferAttach(target.id, target.texture.id, RL_ATTACHMENT_COLOR_CHANNEL0, RL_ATTACHMENT_TEXTURE2D, 0); + rlFramebufferAttach(target.id, target.depth.id, RL_ATTACHMENT_DEPTH, RL_ATTACHMENT_RENDERBUFFER, 0); + + // Check if fbo is complete with attachments (valid) + if (rlFramebufferComplete(target.id)) TRACELOG(LOG_INFO, "FBO: [ID %i] Framebuffer object created successfully", target.id); + + rlDisableFramebuffer(); + } + else TRACELOG(LOG_WARNING, "FBO: Framebuffer object can not be created"); + + return target; +} + +// Unload texture from GPU memory (VRAM) +void UnloadTexture(Texture2D texture) +{ + if (texture.id > 0) + { + rlUnloadTexture(texture.id); + + TRACELOG(LOG_INFO, "TEXTURE: [ID %i] Unloaded texture data from VRAM (GPU)", texture.id); + } +} + +// Unload render texture from GPU memory (VRAM) +void UnloadRenderTexture(RenderTexture2D target) +{ + if (target.id > 0) + { + // Color texture attached to FBO is deleted + rlUnloadTexture(target.texture.id); + + // NOTE: Depth texture/renderbuffer is automatically + // queried and deleted before deleting framebuffer + rlUnloadFramebuffer(target.id); + } +} + +// Update GPU texture with new data +// NOTE: pixels data must match texture.format +void UpdateTexture(Texture2D texture, const void *pixels) +{ + rlUpdateTexture(texture.id, 0, 0, texture.width, texture.height, texture.format, pixels); +} + +// Update GPU texture rectangle with new data +// NOTE: pixels data must match texture.format +void UpdateTextureRec(Texture2D texture, Rectangle rec, const void *pixels) +{ + rlUpdateTexture(texture.id, (int)rec.x, (int)rec.y, (int)rec.width, (int)rec.height, texture.format, pixels); +} + +// Get pixel data from GPU texture and return an Image +// NOTE: Compressed texture formats not supported +Image GetTextureData(Texture2D texture) +{ + Image image = { 0 }; + + if (texture.format < PIXELFORMAT_COMPRESSED_DXT1_RGB) + { + image.data = rlReadTexturePixels(texture); + + if (image.data != NULL) + { + image.width = texture.width; + image.height = texture.height; + image.format = texture.format; + image.mipmaps = 1; + +#if defined(GRAPHICS_API_OPENGL_ES2) + // NOTE: Data retrieved on OpenGL ES 2.0 should be RGBA, + // coming from FBO color buffer attachment, but it seems + // original texture format is retrieved on RPI... + image.format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8; +#endif + TRACELOG(LOG_INFO, "TEXTURE: [ID %i] Pixel data retrieved successfully", texture.id); } + else TRACELOG(LOG_WARNING, "TEXTURE: [ID %i] Failed to retrieve pixel data", texture.id); } + else TRACELOG(LOG_WARNING, "TEXTURE: [ID %i] Failed to retrieve compressed pixel data", texture.id); - RL_FREE(seeds); + return image; +} - Image image = LoadImageEx(pixels, width, height); - RL_FREE(pixels); +// Get pixel data from GPU frontbuffer and return an Image (screenshot) +Image GetScreenData(void) +{ + Image image = { 0 }; + + image.width = GetScreenWidth(); + image.height = GetScreenHeight(); + image.mipmaps = 1; + image.format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8; + image.data = rlReadScreenPixels(image.width, image.height); return image; } -#endif // SUPPORT_IMAGE_GENERATION +//------------------------------------------------------------------------------------ +// Texture configuration functions +//------------------------------------------------------------------------------------ // Generate GPU mipmaps for a texture void GenTextureMipmaps(Texture2D *texture) { @@ -2661,100 +2962,103 @@ void GenTextureMipmaps(Texture2D *texture) } // Set texture scaling filter mode -void SetTextureFilter(Texture2D texture, int filterMode) +void SetTextureFilter(Texture2D texture, int filter) { - switch (filterMode) + switch (filter) { - case FILTER_POINT: + case TEXTURE_FILTER_POINT: { if (texture.mipmaps > 1) { - // RL_FILTER_MIP_NEAREST - tex filter: POINT, mipmaps filter: POINT (sharp switching between mipmaps) - rlTextureParameters(texture.id, RL_TEXTURE_MIN_FILTER, RL_FILTER_MIP_NEAREST); + // RL_TEXTURE_FILTER_MIP_NEAREST - tex filter: POINT, mipmaps filter: POINT (sharp switching between mipmaps) + rlTextureParameters(texture.id, RL_TEXTURE_MIN_FILTER, RL_TEXTURE_FILTER_MIP_NEAREST); - // RL_FILTER_NEAREST - tex filter: POINT (no filter), no mipmaps - rlTextureParameters(texture.id, RL_TEXTURE_MAG_FILTER, RL_FILTER_NEAREST); + // RL_TEXTURE_FILTER_NEAREST - tex filter: POINT (no filter), no mipmaps + rlTextureParameters(texture.id, RL_TEXTURE_MAG_FILTER, RL_TEXTURE_FILTER_NEAREST); } else { - // RL_FILTER_NEAREST - tex filter: POINT (no filter), no mipmaps - rlTextureParameters(texture.id, RL_TEXTURE_MIN_FILTER, RL_FILTER_NEAREST); - rlTextureParameters(texture.id, RL_TEXTURE_MAG_FILTER, RL_FILTER_NEAREST); + // RL_TEXTURE_FILTER_NEAREST - tex filter: POINT (no filter), no mipmaps + rlTextureParameters(texture.id, RL_TEXTURE_MIN_FILTER, RL_TEXTURE_FILTER_NEAREST); + rlTextureParameters(texture.id, RL_TEXTURE_MAG_FILTER, RL_TEXTURE_FILTER_NEAREST); } } break; - case FILTER_BILINEAR: + case TEXTURE_FILTER_BILINEAR: { if (texture.mipmaps > 1) { - // RL_FILTER_LINEAR_MIP_NEAREST - tex filter: BILINEAR, mipmaps filter: POINT (sharp switching between mipmaps) - // Alternative: RL_FILTER_NEAREST_MIP_LINEAR - tex filter: POINT, mipmaps filter: BILINEAR (smooth transition between mipmaps) - rlTextureParameters(texture.id, RL_TEXTURE_MIN_FILTER, RL_FILTER_LINEAR_MIP_NEAREST); + // RL_TEXTURE_FILTER_LINEAR_MIP_NEAREST - tex filter: BILINEAR, mipmaps filter: POINT (sharp switching between mipmaps) + // Alternative: RL_TEXTURE_FILTER_NEAREST_MIP_LINEAR - tex filter: POINT, mipmaps filter: BILINEAR (smooth transition between mipmaps) + rlTextureParameters(texture.id, RL_TEXTURE_MIN_FILTER, RL_TEXTURE_FILTER_LINEAR_MIP_NEAREST); - // RL_FILTER_LINEAR - tex filter: BILINEAR, no mipmaps - rlTextureParameters(texture.id, RL_TEXTURE_MAG_FILTER, RL_FILTER_LINEAR); + // RL_TEXTURE_FILTER_LINEAR - tex filter: BILINEAR, no mipmaps + rlTextureParameters(texture.id, RL_TEXTURE_MAG_FILTER, RL_TEXTURE_FILTER_LINEAR); } else { - // RL_FILTER_LINEAR - tex filter: BILINEAR, no mipmaps - rlTextureParameters(texture.id, RL_TEXTURE_MIN_FILTER, RL_FILTER_LINEAR); - rlTextureParameters(texture.id, RL_TEXTURE_MAG_FILTER, RL_FILTER_LINEAR); + // RL_TEXTURE_FILTER_LINEAR - tex filter: BILINEAR, no mipmaps + rlTextureParameters(texture.id, RL_TEXTURE_MIN_FILTER, RL_TEXTURE_FILTER_LINEAR); + rlTextureParameters(texture.id, RL_TEXTURE_MAG_FILTER, RL_TEXTURE_FILTER_LINEAR); } } break; - case FILTER_TRILINEAR: + case TEXTURE_FILTER_TRILINEAR: { if (texture.mipmaps > 1) { - // RL_FILTER_MIP_LINEAR - tex filter: BILINEAR, mipmaps filter: BILINEAR (smooth transition between mipmaps) - rlTextureParameters(texture.id, RL_TEXTURE_MIN_FILTER, RL_FILTER_MIP_LINEAR); + // RL_TEXTURE_FILTER_MIP_LINEAR - tex filter: BILINEAR, mipmaps filter: BILINEAR (smooth transition between mipmaps) + rlTextureParameters(texture.id, RL_TEXTURE_MIN_FILTER, RL_TEXTURE_FILTER_MIP_LINEAR); - // RL_FILTER_LINEAR - tex filter: BILINEAR, no mipmaps - rlTextureParameters(texture.id, RL_TEXTURE_MAG_FILTER, RL_FILTER_LINEAR); + // RL_TEXTURE_FILTER_LINEAR - tex filter: BILINEAR, no mipmaps + rlTextureParameters(texture.id, RL_TEXTURE_MAG_FILTER, RL_TEXTURE_FILTER_LINEAR); } else { TRACELOG(LOG_WARNING, "TEXTURE: [ID %i] No mipmaps available for TRILINEAR texture filtering", texture.id); - // RL_FILTER_LINEAR - tex filter: BILINEAR, no mipmaps - rlTextureParameters(texture.id, RL_TEXTURE_MIN_FILTER, RL_FILTER_LINEAR); - rlTextureParameters(texture.id, RL_TEXTURE_MAG_FILTER, RL_FILTER_LINEAR); + // RL_TEXTURE_FILTER_LINEAR - tex filter: BILINEAR, no mipmaps + rlTextureParameters(texture.id, RL_TEXTURE_MIN_FILTER, RL_TEXTURE_FILTER_LINEAR); + rlTextureParameters(texture.id, RL_TEXTURE_MAG_FILTER, RL_TEXTURE_FILTER_LINEAR); } } break; - case FILTER_ANISOTROPIC_4X: rlTextureParameters(texture.id, RL_TEXTURE_ANISOTROPIC_FILTER, 4); break; - case FILTER_ANISOTROPIC_8X: rlTextureParameters(texture.id, RL_TEXTURE_ANISOTROPIC_FILTER, 8); break; - case FILTER_ANISOTROPIC_16X: rlTextureParameters(texture.id, RL_TEXTURE_ANISOTROPIC_FILTER, 16); break; + case TEXTURE_FILTER_ANISOTROPIC_4X: rlTextureParameters(texture.id, RL_TEXTURE_FILTER_ANISOTROPIC, 4); break; + case TEXTURE_FILTER_ANISOTROPIC_8X: rlTextureParameters(texture.id, RL_TEXTURE_FILTER_ANISOTROPIC, 8); break; + case TEXTURE_FILTER_ANISOTROPIC_16X: rlTextureParameters(texture.id, RL_TEXTURE_FILTER_ANISOTROPIC, 16); break; default: break; } } // Set texture wrapping mode -void SetTextureWrap(Texture2D texture, int wrapMode) +void SetTextureWrap(Texture2D texture, int wrap) { - switch (wrapMode) + switch (wrap) { - case WRAP_REPEAT: + case TEXTURE_WRAP_REPEAT: { - rlTextureParameters(texture.id, RL_TEXTURE_WRAP_S, RL_WRAP_REPEAT); - rlTextureParameters(texture.id, RL_TEXTURE_WRAP_T, RL_WRAP_REPEAT); + rlTextureParameters(texture.id, RL_TEXTURE_WRAP_S, RL_TEXTURE_WRAP_REPEAT); + rlTextureParameters(texture.id, RL_TEXTURE_WRAP_T, RL_TEXTURE_WRAP_REPEAT); } break; - case WRAP_CLAMP: + case TEXTURE_WRAP_CLAMP: { - rlTextureParameters(texture.id, RL_TEXTURE_WRAP_S, RL_WRAP_CLAMP); - rlTextureParameters(texture.id, RL_TEXTURE_WRAP_T, RL_WRAP_CLAMP); + rlTextureParameters(texture.id, RL_TEXTURE_WRAP_S, RL_TEXTURE_WRAP_CLAMP); + rlTextureParameters(texture.id, RL_TEXTURE_WRAP_T, RL_TEXTURE_WRAP_CLAMP); } break; - case WRAP_MIRROR_REPEAT: + case TEXTURE_WRAP_MIRROR_REPEAT: { - rlTextureParameters(texture.id, RL_TEXTURE_WRAP_S, RL_WRAP_MIRROR_REPEAT); - rlTextureParameters(texture.id, RL_TEXTURE_WRAP_T, RL_WRAP_MIRROR_REPEAT); + rlTextureParameters(texture.id, RL_TEXTURE_WRAP_S, RL_TEXTURE_WRAP_MIRROR_REPEAT); + rlTextureParameters(texture.id, RL_TEXTURE_WRAP_T, RL_TEXTURE_WRAP_MIRROR_REPEAT); } break; - case WRAP_MIRROR_CLAMP: + case TEXTURE_WRAP_MIRROR_CLAMP: { - rlTextureParameters(texture.id, RL_TEXTURE_WRAP_S, RL_WRAP_MIRROR_CLAMP); - rlTextureParameters(texture.id, RL_TEXTURE_WRAP_T, RL_WRAP_MIRROR_CLAMP); + rlTextureParameters(texture.id, RL_TEXTURE_WRAP_S, RL_TEXTURE_WRAP_MIRROR_CLAMP); + rlTextureParameters(texture.id, RL_TEXTURE_WRAP_T, RL_TEXTURE_WRAP_MIRROR_CLAMP); } break; default: break; } } +//------------------------------------------------------------------------------------ +// Texture drawing functions +//------------------------------------------------------------------------------------ // Draw a Texture2D void DrawTexture(Texture2D texture, int posX, int posY, Color tint) { @@ -2770,20 +3074,20 @@ void DrawTextureV(Texture2D texture, Vector2 position, Color tint) // Draw a Texture2D with extended parameters void DrawTextureEx(Texture2D texture, Vector2 position, float rotation, float scale, Color tint) { - Rectangle sourceRec = { 0.0f, 0.0f, (float)texture.width, (float)texture.height }; - Rectangle destRec = { position.x, position.y, (float)texture.width*scale, (float)texture.height*scale }; + Rectangle source = { 0.0f, 0.0f, (float)texture.width, (float)texture.height }; + Rectangle dest = { position.x, position.y, (float)texture.width*scale, (float)texture.height*scale }; Vector2 origin = { 0.0f, 0.0f }; - DrawTexturePro(texture, sourceRec, destRec, origin, rotation, tint); + DrawTexturePro(texture, source, dest, origin, rotation, tint); } // Draw a part of a texture (defined by a rectangle) -void DrawTextureRec(Texture2D texture, Rectangle sourceRec, Vector2 position, Color tint) +void DrawTextureRec(Texture2D texture, Rectangle source, Vector2 position, Color tint) { - Rectangle destRec = { position.x, position.y, fabsf(sourceRec.width), fabsf(sourceRec.height) }; + Rectangle dest = { position.x, position.y, fabsf(source.width), fabsf(source.height) }; Vector2 origin = { 0.0f, 0.0f }; - DrawTexturePro(texture, sourceRec, destRec, origin, 0.0f, tint); + DrawTexturePro(texture, source, dest, origin, 0.0f, tint); } // Draw texture quad with tiling and offset parameters @@ -2797,9 +3101,93 @@ void DrawTextureQuad(Texture2D texture, Vector2 tiling, Vector2 offset, Rectangl DrawTexturePro(texture, source, quad, origin, 0.0f, tint); } +// Draw part of a texture (defined by a rectangle) with rotation and scale tiled into dest. +// NOTE: For tilling a whole texture DrawTextureQuad() is better +void DrawTextureTiled(Texture2D texture, Rectangle source, Rectangle dest, Vector2 origin, float rotation, float scale, Color tint) +{ + if ((texture.id <= 0) || (scale <= 0.0f)) return; // Wanna see a infinite loop?!...just delete this line! + + int tileWidth = (int)(source.width*scale), tileHeight = (int)(source.height*scale); + if ((dest.width < tileWidth) && (dest.height < tileHeight)) + { + // Can fit only one tile + DrawTexturePro(texture, (Rectangle){source.x, source.y, ((float)dest.width/tileWidth)*source.width, ((float)dest.height/tileHeight)*source.height}, + (Rectangle){dest.x, dest.y, dest.width, dest.height}, origin, rotation, tint); + } + else if (dest.width <= tileWidth) + { + // Tiled vertically (one column) + int dy = 0; + for (;dy+tileHeight < dest.height; dy += tileHeight) + { + DrawTexturePro(texture, (Rectangle){source.x, source.y, ((float)dest.width/tileWidth)*source.width, source.height}, (Rectangle){dest.x, dest.y + dy, dest.width, (float)tileHeight}, origin, rotation, tint); + } + + // Fit last tile + if (dy < dest.height) + { + DrawTexturePro(texture, (Rectangle){source.x, source.y, ((float)dest.width/tileWidth)*source.width, ((float)(dest.height - dy)/tileHeight)*source.height}, + (Rectangle){dest.x, dest.y + dy, dest.width, dest.height - dy}, origin, rotation, tint); + } + } + else if (dest.height <= tileHeight) + { + // Tiled horizontally (one row) + int dx = 0; + for (;dx+tileWidth < dest.width; dx += tileWidth) + { + DrawTexturePro(texture, (Rectangle){source.x, source.y, source.width, ((float)dest.height/tileHeight)*source.height}, (Rectangle){dest.x + dx, dest.y, (float)tileWidth, dest.height}, origin, rotation, tint); + } + + // Fit last tile + if (dx < dest.width) + { + DrawTexturePro(texture, (Rectangle){source.x, source.y, ((float)(dest.width - dx)/tileWidth)*source.width, ((float)dest.height/tileHeight)*source.height}, + (Rectangle){dest.x + dx, dest.y, dest.width - dx, dest.height}, origin, rotation, tint); + } + } + else + { + // Tiled both horizontally and vertically (rows and columns) + int dx = 0; + for (;dx+tileWidth < dest.width; dx += tileWidth) + { + int dy = 0; + for (;dy+tileHeight < dest.height; dy += tileHeight) + { + DrawTexturePro(texture, source, (Rectangle){dest.x + dx, dest.y + dy, (float)tileWidth, (float)tileHeight}, origin, rotation, tint); + } + + if (dy < dest.height) + { + DrawTexturePro(texture, (Rectangle){source.x, source.y, source.width, ((float)(dest.height - dy)/tileHeight)*source.height}, + (Rectangle){dest.x + dx, dest.y + dy, (float)tileWidth, dest.height - dy}, origin, rotation, tint); + } + } + + // Fit last column of tiles + if (dx < dest.width) + { + int dy = 0; + for (;dy+tileHeight < dest.height; dy += tileHeight) + { + DrawTexturePro(texture, (Rectangle){source.x, source.y, ((float)(dest.width - dx)/tileWidth)*source.width, source.height}, + (Rectangle){dest.x + dx, dest.y + dy, dest.width - dx, (float)tileHeight}, origin, rotation, tint); + } + + // Draw final tile in the bottom right corner + if (dy < dest.height) + { + DrawTexturePro(texture, (Rectangle){source.x, source.y, ((float)(dest.width - dx)/tileWidth)*source.width, ((float)(dest.height - dy)/tileHeight)*source.height}, + (Rectangle){dest.x + dx, dest.y + dy, dest.width - dx, dest.height - dy}, origin, rotation, tint); + } + } + } +} + // Draw a part of a texture (defined by a rectangle) with 'pro' parameters // NOTE: origin is relative to destination rectangle size -void DrawTexturePro(Texture2D texture, Rectangle sourceRec, Rectangle destRec, Vector2 origin, float rotation, Color tint) +void DrawTexturePro(Texture2D texture, Rectangle source, Rectangle dest, Vector2 origin, float rotation, Color tint) { // Check if texture is valid if (texture.id > 0) @@ -2809,14 +3197,87 @@ void DrawTexturePro(Texture2D texture, Rectangle sourceRec, Rectangle destRec, V bool flipX = false; - if (sourceRec.width < 0) { flipX = true; sourceRec.width *= -1; } - if (sourceRec.height < 0) sourceRec.y -= sourceRec.height; + if (source.width < 0) { flipX = true; source.width *= -1; } + if (source.height < 0) source.y -= source.height; + + Vector2 topLeft = { 0 }; + Vector2 topRight = { 0 }; + Vector2 bottomLeft = { 0 }; + Vector2 bottomRight = { 0 }; + + // Only calculate rotation if needed + if (rotation == 0.0f) + { + float x = dest.x - origin.x; + float y = dest.y - origin.y; + topLeft = (Vector2){ x, y }; + topRight = (Vector2){ x + dest.width, y }; + bottomLeft = (Vector2){ x, y + dest.height }; + bottomRight = (Vector2){ x + dest.width, y + dest.height }; + } + else + { + float sinRotation = sinf(rotation*DEG2RAD); + float cosRotation = cosf(rotation*DEG2RAD); + float x = dest.x; + float y = dest.y; + float dx = -origin.x; + float dy = -origin.y; + + topLeft.x = x + dx*cosRotation - dy*sinRotation; + topLeft.y = y + dx*sinRotation + dy*cosRotation; + + topRight.x = x + (dx + dest.width)*cosRotation - dy*sinRotation; + topRight.y = y + (dx + dest.width)*sinRotation + dy*cosRotation; + + bottomLeft.x = x + dx*cosRotation - (dy + dest.height)*sinRotation; + bottomLeft.y = y + dx*sinRotation + (dy + dest.height)*cosRotation; + + bottomRight.x = x + (dx + dest.width)*cosRotation - (dy + dest.height)*sinRotation; + bottomRight.y = y + (dx + dest.width)*sinRotation + (dy + dest.height)*cosRotation; + } + + rlCheckRenderBatchLimit(4); // Make sure there is enough free space on the batch buffer + + rlSetTexture(texture.id); + rlBegin(RL_QUADS); + + rlColor4ub(tint.r, tint.g, tint.b, tint.a); + rlNormal3f(0.0f, 0.0f, 1.0f); // Normal vector pointing towards viewer + + // Top-left corner for texture and quad + if (flipX) rlTexCoord2f((source.x + source.width)/width, source.y/height); + else rlTexCoord2f(source.x/width, source.y/height); + rlVertex2f(topLeft.x, topLeft.y); + + // Bottom-left corner for texture and quad + if (flipX) rlTexCoord2f((source.x + source.width)/width, (source.y + source.height)/height); + else rlTexCoord2f(source.x/width, (source.y + source.height)/height); + rlVertex2f(bottomLeft.x, bottomLeft.y); + + // Bottom-right corner for texture and quad + if (flipX) rlTexCoord2f(source.x/width, (source.y + source.height)/height); + else rlTexCoord2f((source.x + source.width)/width, (source.y + source.height)/height); + rlVertex2f(bottomRight.x, bottomRight.y); - rlEnableTexture(texture.id); + // Top-right corner for texture and quad + if (flipX) rlTexCoord2f(source.x/width, source.y/height); + else rlTexCoord2f((source.x + source.width)/width, source.y/height); + rlVertex2f(topRight.x, topRight.y); + rlEnd(); + rlSetTexture(0); + + // NOTE: Vertex position can be transformed using matrices + // but the process is way more costly than just calculating + // the vertex positions manually, like done above. + // I leave here the old implementation for educational pourposes, + // just in case someone wants to do some performance test + /* + rlSetTexture(texture.id); rlPushMatrix(); - rlTranslatef(destRec.x, destRec.y, 0.0f); - rlRotatef(rotation, 0.0f, 0.0f, 1.0f); + rlTranslatef(dest.x, dest.y, 0.0f); + if (rotation != 0.0f) rlRotatef(rotation, 0.0f, 0.0f, 1.0f); rlTranslatef(-origin.x, -origin.y, 0.0f); rlBegin(RL_QUADS); @@ -2824,46 +3285,46 @@ void DrawTexturePro(Texture2D texture, Rectangle sourceRec, Rectangle destRec, V rlNormal3f(0.0f, 0.0f, 1.0f); // Normal vector pointing towards viewer // Bottom-left corner for texture and quad - if (flipX) rlTexCoord2f((sourceRec.x + sourceRec.width)/width, sourceRec.y/height); - else rlTexCoord2f(sourceRec.x/width, sourceRec.y/height); + if (flipX) rlTexCoord2f((source.x + source.width)/width, source.y/height); + else rlTexCoord2f(source.x/width, source.y/height); rlVertex2f(0.0f, 0.0f); // Bottom-right corner for texture and quad - if (flipX) rlTexCoord2f((sourceRec.x + sourceRec.width)/width, (sourceRec.y + sourceRec.height)/height); - else rlTexCoord2f(sourceRec.x/width, (sourceRec.y + sourceRec.height)/height); - rlVertex2f(0.0f, destRec.height); + if (flipX) rlTexCoord2f((source.x + source.width)/width, (source.y + source.height)/height); + else rlTexCoord2f(source.x/width, (source.y + source.height)/height); + rlVertex2f(0.0f, dest.height); // Top-right corner for texture and quad - if (flipX) rlTexCoord2f(sourceRec.x/width, (sourceRec.y + sourceRec.height)/height); - else rlTexCoord2f((sourceRec.x + sourceRec.width)/width, (sourceRec.y + sourceRec.height)/height); - rlVertex2f(destRec.width, destRec.height); + if (flipX) rlTexCoord2f(source.x/width, (source.y + source.height)/height); + else rlTexCoord2f((source.x + source.width)/width, (source.y + source.height)/height); + rlVertex2f(dest.width, dest.height); // Top-left corner for texture and quad - if (flipX) rlTexCoord2f(sourceRec.x/width, sourceRec.y/height); - else rlTexCoord2f((sourceRec.x + sourceRec.width)/width, sourceRec.y/height); - rlVertex2f(destRec.width, 0.0f); + if (flipX) rlTexCoord2f(source.x/width, source.y/height); + else rlTexCoord2f((source.x + source.width)/width, source.y/height); + rlVertex2f(dest.width, 0.0f); rlEnd(); rlPopMatrix(); - - rlDisableTexture(); + rlSetTexture(0); + */ } } // Draws a texture (or part of it) that stretches or shrinks nicely using n-patch info -void DrawTextureNPatch(Texture2D texture, NPatchInfo nPatchInfo, Rectangle destRec, Vector2 origin, float rotation, Color tint) +void DrawTextureNPatch(Texture2D texture, NPatchInfo nPatchInfo, Rectangle dest, Vector2 origin, float rotation, Color tint) { if (texture.id > 0) { float width = (float)texture.width; float height = (float)texture.height; - float patchWidth = (destRec.width <= 0.0f)? 0.0f : destRec.width; - float patchHeight = (destRec.height <= 0.0f)? 0.0f : destRec.height; + float patchWidth = (dest.width <= 0.0f)? 0.0f : dest.width; + float patchHeight = (dest.height <= 0.0f)? 0.0f : dest.height; - if (nPatchInfo.sourceRec.width < 0) nPatchInfo.sourceRec.x -= nPatchInfo.sourceRec.width; - if (nPatchInfo.sourceRec.height < 0) nPatchInfo.sourceRec.y -= nPatchInfo.sourceRec.height; - if (nPatchInfo.type == NPT_3PATCH_HORIZONTAL) patchHeight = nPatchInfo.sourceRec.height; - if (nPatchInfo.type == NPT_3PATCH_VERTICAL) patchWidth = nPatchInfo.sourceRec.width; + if (nPatchInfo.source.width < 0) nPatchInfo.source.x -= nPatchInfo.source.width; + if (nPatchInfo.source.height < 0) nPatchInfo.source.y -= nPatchInfo.source.height; + if (nPatchInfo.layout == NPATCH_THREE_PATCH_HORIZONTAL) patchHeight = nPatchInfo.source.height; + if (nPatchInfo.layout == NPATCH_THREE_PATCH_VERTICAL) patchWidth = nPatchInfo.source.width; bool drawCenter = true; bool drawMiddle = true; @@ -2873,17 +3334,17 @@ void DrawTextureNPatch(Texture2D texture, NPatchInfo nPatchInfo, Rectangle destR float bottomBorder = (float)nPatchInfo.bottom; // adjust the lateral (left and right) border widths in case patchWidth < texture.width - if (patchWidth <= (leftBorder + rightBorder) && nPatchInfo.type != NPT_3PATCH_VERTICAL) + if (patchWidth <= (leftBorder + rightBorder) && nPatchInfo.layout != NPATCH_THREE_PATCH_VERTICAL) { drawCenter = false; - leftBorder = (leftBorder / (leftBorder + rightBorder)) * patchWidth; + leftBorder = (leftBorder/(leftBorder + rightBorder))*patchWidth; rightBorder = patchWidth - leftBorder; } // adjust the lateral (top and bottom) border heights in case patchHeight < texture.height - if (patchHeight <= (topBorder + bottomBorder) && nPatchInfo.type != NPT_3PATCH_HORIZONTAL) + if (patchHeight <= (topBorder + bottomBorder) && nPatchInfo.layout != NPATCH_THREE_PATCH_HORIZONTAL) { drawMiddle = false; - topBorder = (topBorder / (topBorder + bottomBorder)) * patchHeight; + topBorder = (topBorder/(topBorder + bottomBorder))*patchHeight; bottomBorder = patchHeight - topBorder; } @@ -2898,27 +3359,27 @@ void DrawTextureNPatch(Texture2D texture, NPatchInfo nPatchInfo, Rectangle destR vertD.y = patchHeight; // outer bottom Vector2 coordA, coordB, coordC, coordD; - coordA.x = nPatchInfo.sourceRec.x / width; - coordA.y = nPatchInfo.sourceRec.y / height; - coordB.x = (nPatchInfo.sourceRec.x + leftBorder) / width; - coordB.y = (nPatchInfo.sourceRec.y + topBorder) / height; - coordC.x = (nPatchInfo.sourceRec.x + nPatchInfo.sourceRec.width - rightBorder) / width; - coordC.y = (nPatchInfo.sourceRec.y + nPatchInfo.sourceRec.height - bottomBorder) / height; - coordD.x = (nPatchInfo.sourceRec.x + nPatchInfo.sourceRec.width) / width; - coordD.y = (nPatchInfo.sourceRec.y + nPatchInfo.sourceRec.height) / height; + coordA.x = nPatchInfo.source.x/width; + coordA.y = nPatchInfo.source.y/height; + coordB.x = (nPatchInfo.source.x + leftBorder)/width; + coordB.y = (nPatchInfo.source.y + topBorder)/height; + coordC.x = (nPatchInfo.source.x + nPatchInfo.source.width - rightBorder)/width; + coordC.y = (nPatchInfo.source.y + nPatchInfo.source.height - bottomBorder)/height; + coordD.x = (nPatchInfo.source.x + nPatchInfo.source.width)/width; + coordD.y = (nPatchInfo.source.y + nPatchInfo.source.height)/height; - rlEnableTexture(texture.id); + rlSetTexture(texture.id); rlPushMatrix(); - rlTranslatef(destRec.x, destRec.y, 0.0f); + rlTranslatef(dest.x, dest.y, 0.0f); rlRotatef(rotation, 0.0f, 0.0f, 1.0f); rlTranslatef(-origin.x, -origin.y, 0.0f); rlBegin(RL_QUADS); rlColor4ub(tint.r, tint.g, tint.b, tint.a); - rlNormal3f(0.0f, 0.0f, 1.0f); // Normal vector pointing towards viewer + rlNormal3f(0.0f, 0.0f, 1.0f); // Normal vector pointing towards viewer - if (nPatchInfo.type == NPT_9PATCH) + if (nPatchInfo.layout == NPATCH_NINE_PATCH) { // ------------------------------------------------------------ // TOP-LEFT QUAD @@ -2984,7 +3445,7 @@ void DrawTextureNPatch(Texture2D texture, NPatchInfo nPatchInfo, Rectangle destR rlTexCoord2f(coordD.x, coordC.y); rlVertex2f(vertD.x, vertC.y); // Top-right corner for texture and quad rlTexCoord2f(coordC.x, coordC.y); rlVertex2f(vertC.x, vertC.y); // Top-left corner for texture and quad } - else if (nPatchInfo.type == NPT_3PATCH_VERTICAL) + else if (nPatchInfo.layout == NPATCH_THREE_PATCH_VERTICAL) { // TOP QUAD // ----------------------------------------------------------- @@ -3011,7 +3472,7 @@ void DrawTextureNPatch(Texture2D texture, NPatchInfo nPatchInfo, Rectangle destR rlTexCoord2f(coordD.x, coordC.y); rlVertex2f(vertD.x, vertC.y); // Top-right corner for texture and quad rlTexCoord2f(coordA.x, coordC.y); rlVertex2f(vertA.x, vertC.y); // Top-left corner for texture and quad } - else if (nPatchInfo.type == NPT_3PATCH_HORIZONTAL) + else if (nPatchInfo.layout == NPATCH_THREE_PATCH_HORIZONTAL) { // LEFT QUAD // ----------------------------------------------------------- @@ -3041,46 +3502,429 @@ void DrawTextureNPatch(Texture2D texture, NPatchInfo nPatchInfo, Rectangle destR rlEnd(); rlPopMatrix(); - rlDisableTexture(); - + rlSetTexture(0); } } -//---------------------------------------------------------------------------------- -// Module specific Functions Definition -//---------------------------------------------------------------------------------- -#if defined(SUPPORT_FILEFORMAT_GIF) -// Load animated GIF data -// - Image.data buffer includes all frames: [image#0][image#1][image#2][...] -// - Number of frames is returned through 'frames' parameter -// - Frames delay is returned through 'delays' parameter (int array) -// - All frames are returned in RGBA format -static Image LoadAnimatedGIF(const char *fileName, int *frames, int **delays) +// Draw textured polygon, defined by vertex and texturecoordinates +// NOTE: Polygon center must have straight line path to all points +// without crossing perimeter, points must be in anticlockwise order +void DrawTexturePoly(Texture2D texture, Vector2 center, Vector2 *points, Vector2 *texcoords, int pointsCount, Color tint) { - Image image = { 0 }; + rlCheckRenderBatchLimit((pointsCount - 1)*4); - unsigned int dataSize = 0; - unsigned char *fileData = LoadFileData(fileName, &dataSize); + rlSetTexture(texture.id); - if (fileData != NULL) + // Texturing is only supported on QUADs + rlBegin(RL_QUADS); + + rlColor4ub(tint.r, tint.g, tint.b, tint.a); + + for (int i = 0; i < pointsCount - 1; i++) + { + rlTexCoord2f(0.5f, 0.5f); + rlVertex2f(center.x, center.y); + + rlTexCoord2f(texcoords[i].x, texcoords[i].y); + rlVertex2f(points[i].x + center.x, points[i].y + center.y); + + rlTexCoord2f(texcoords[i + 1].x, texcoords[i + 1].y); + rlVertex2f(points[i + 1].x + center.x, points[i + 1].y + center.y); + + rlTexCoord2f(texcoords[i + 1].x, texcoords[i + 1].y); + rlVertex2f(points[i + 1].x + center.x, points[i + 1].y + center.y); + } + rlEnd(); + + rlSetTexture(0); +} + +// Returns color with alpha applied, alpha goes from 0.0f to 1.0f +Color Fade(Color color, float alpha) +{ + if (alpha < 0.0f) alpha = 0.0f; + else if (alpha > 1.0f) alpha = 1.0f; + + return (Color){color.r, color.g, color.b, (unsigned char)(255.0f*alpha)}; +} + +// Returns hexadecimal value for a Color +int ColorToInt(Color color) +{ + return (((int)color.r << 24) | ((int)color.g << 16) | ((int)color.b << 8) | (int)color.a); +} + +// Returns color normalized as float [0..1] +Vector4 ColorNormalize(Color color) +{ + Vector4 result; + + result.x = (float)color.r/255.0f; + result.y = (float)color.g/255.0f; + result.z = (float)color.b/255.0f; + result.w = (float)color.a/255.0f; + + return result; +} + +// Returns color from normalized values [0..1] +Color ColorFromNormalized(Vector4 normalized) +{ + Color result; + + result.r = (unsigned char)(normalized.x*255.0f); + result.g = (unsigned char)(normalized.y*255.0f); + result.b = (unsigned char)(normalized.z*255.0f); + result.a = (unsigned char)(normalized.w*255.0f); + + return result; +} + +// Returns HSV values for a Color +// NOTE: Hue is returned as degrees [0..360] +Vector3 ColorToHSV(Color color) +{ + Vector3 hsv = { 0 }; + Vector3 rgb = { (float)color.r/255.0f, (float)color.g/255.0f, (float)color.b/255.0f }; + float min, max, delta; + + min = rgb.x < rgb.y? rgb.x : rgb.y; + min = min < rgb.z? min : rgb.z; + + max = rgb.x > rgb.y? rgb.x : rgb.y; + max = max > rgb.z? max : rgb.z; + + hsv.z = max; // Value + delta = max - min; + + if (delta < 0.00001f) { - int comp = 0; - image.data = stbi_load_gif_from_memory(fileData, dataSize, delays, &image.width, &image.height, frames, &comp, 4); + hsv.y = 0.0f; + hsv.x = 0.0f; // Undefined, maybe NAN? + return hsv; + } - image.mipmaps = 1; - image.format = UNCOMPRESSED_R8G8B8A8; + if (max > 0.0f) + { + // NOTE: If max is 0, this divide would cause a crash + hsv.y = (delta/max); // Saturation + } + else + { + // NOTE: If max is 0, then r = g = b = 0, s = 0, h is undefined + hsv.y = 0.0f; + hsv.x = NAN; // Undefined + return hsv; + } - RL_FREE(fileData); + // NOTE: Comparing float values could not work properly + if (rgb.x >= max) hsv.x = (rgb.y - rgb.z)/delta; // Between yellow & magenta + else + { + if (rgb.y >= max) hsv.x = 2.0f + (rgb.z - rgb.x)/delta; // Between cyan & yellow + else hsv.x = 4.0f + (rgb.x - rgb.y)/delta; // Between magenta & cyan } - return image; + hsv.x *= 60.0f; // Convert to degrees + + if (hsv.x < 0.0f) hsv.x += 360.0f; + + return hsv; +} + +// Returns a Color from HSV values +// Implementation reference: https://en.wikipedia.org/wiki/HSL_and_HSV#Alternative_HSV_conversion +// NOTE: Color->HSV->Color conversion will not yield exactly the same color due to rounding errors +// Hue is provided in degrees: [0..360] +// Saturation/Value are provided normalized: [0.0f..1.0f] +Color ColorFromHSV(float hue, float saturation, float value) +{ + Color color = { 0, 0, 0, 255 }; + + // Red channel + float k = fmodf((5.0f + hue/60.0f), 6); + float t = 4.0f - k; + k = (t < k)? t : k; + k = (k < 1)? k : 1; + k = (k > 0)? k : 0; + color.r = (unsigned char)((value - value*saturation*k)*255.0f); + + // Green channel + k = fmodf((3.0f + hue/60.0f), 6); + t = 4.0f - k; + k = (t < k)? t : k; + k = (k < 1)? k : 1; + k = (k > 0)? k : 0; + color.g = (unsigned char)((value - value*saturation*k)*255.0f); + + // Blue channel + k = fmodf((1.0f + hue/60.0f), 6); + t = 4.0f - k; + k = (t < k)? t : k; + k = (k < 1)? k : 1; + k = (k > 0)? k : 0; + color.b = (unsigned char)((value - value*saturation*k)*255.0f); + + return color; +} + +// Returns color with alpha applied, alpha goes from 0.0f to 1.0f +Color ColorAlpha(Color color, float alpha) +{ + if (alpha < 0.0f) alpha = 0.0f; + else if (alpha > 1.0f) alpha = 1.0f; + + return (Color){color.r, color.g, color.b, (unsigned char)(255.0f*alpha)}; } + +// Returns src alpha-blended into dst color with tint +Color ColorAlphaBlend(Color dst, Color src, Color tint) +{ + Color out = WHITE; + + // Apply color tint to source color + src.r = (unsigned char)(((unsigned int)src.r*(unsigned int)tint.r) >> 8); + src.g = (unsigned char)(((unsigned int)src.g*(unsigned int)tint.g) >> 8); + src.b = (unsigned char)(((unsigned int)src.b*(unsigned int)tint.b) >> 8); + src.a = (unsigned char)(((unsigned int)src.a*(unsigned int)tint.a) >> 8); + +//#define COLORALPHABLEND_FLOAT +#define COLORALPHABLEND_INTEGERS +#if defined(COLORALPHABLEND_INTEGERS) + if (src.a == 0) out = dst; + else if (src.a == 255) out = src; + else + { + unsigned int alpha = (unsigned int)src.a + 1; // We are shifting by 8 (dividing by 256), so we need to take that excess into account + out.a = (unsigned char)(((unsigned int)alpha*256 + (unsigned int)dst.a*(256 - alpha)) >> 8); + + if (out.a > 0) + { + out.r = (unsigned char)((((unsigned int)src.r*alpha*256 + (unsigned int)dst.r*(unsigned int)dst.a*(256 - alpha))/out.a) >> 8); + out.g = (unsigned char)((((unsigned int)src.g*alpha*256 + (unsigned int)dst.g*(unsigned int)dst.a*(256 - alpha))/out.a) >> 8); + out.b = (unsigned char)((((unsigned int)src.b*alpha*256 + (unsigned int)dst.b*(unsigned int)dst.a*(256 - alpha))/out.a) >> 8); + } + } +#endif +#if defined(COLORALPHABLEND_FLOAT) + if (src.a == 0) out = dst; + else if (src.a == 255) out = src; + else + { + Vector4 fdst = ColorNormalize(dst); + Vector4 fsrc = ColorNormalize(src); + Vector4 ftint = ColorNormalize(tint); + Vector4 fout = { 0 }; + + fout.w = fsrc.w + fdst.w*(1.0f - fsrc.w); + + if (fout.w > 0.0f) + { + fout.x = (fsrc.x*fsrc.w + fdst.x*fdst.w*(1 - fsrc.w))/fout.w; + fout.y = (fsrc.y*fsrc.w + fdst.y*fdst.w*(1 - fsrc.w))/fout.w; + fout.z = (fsrc.z*fsrc.w + fdst.z*fdst.w*(1 - fsrc.w))/fout.w; + } + + out = (Color){ (unsigned char)(fout.x*255.0f), (unsigned char)(fout.y*255.0f), (unsigned char)(fout.z*255.0f), (unsigned char)(fout.w*255.0f) }; + } #endif + return out; +} + +// Returns a Color struct from hexadecimal value +Color GetColor(int hexValue) +{ + Color color; + + color.r = (unsigned char)(hexValue >> 24) & 0xFF; + color.g = (unsigned char)(hexValue >> 16) & 0xFF; + color.b = (unsigned char)(hexValue >> 8) & 0xFF; + color.a = (unsigned char)hexValue & 0xFF; + + return color; +} + +// Get color from a pixel from certain format +Color GetPixelColor(void *srcPtr, int format) +{ + Color col = { 0 }; + + switch (format) + { + case PIXELFORMAT_UNCOMPRESSED_GRAYSCALE: col = (Color){ ((unsigned char *)srcPtr)[0], ((unsigned char *)srcPtr)[0], ((unsigned char *)srcPtr)[0], 255 }; break; + case PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA: col = (Color){ ((unsigned char *)srcPtr)[0], ((unsigned char *)srcPtr)[0], ((unsigned char *)srcPtr)[0], ((unsigned char *)srcPtr)[1] }; break; + case PIXELFORMAT_UNCOMPRESSED_R5G6B5: + { + col.r = (unsigned char)((((unsigned short *)srcPtr)[0] >> 11)*255/31); + col.g = (unsigned char)(((((unsigned short *)srcPtr)[0] >> 5) & 0b0000000000111111)*255/63); + col.b = (unsigned char)((((unsigned short *)srcPtr)[0] & 0b0000000000011111)*255/31); + col.a = 255; + + } break; + case PIXELFORMAT_UNCOMPRESSED_R5G5B5A1: + { + col.r = (unsigned char)((((unsigned short *)srcPtr)[0] >> 11)*255/31); + col.g = (unsigned char)(((((unsigned short *)srcPtr)[0] >> 6) & 0b0000000000011111)*255/31); + col.b = (unsigned char)((((unsigned short *)srcPtr)[0] & 0b0000000000011111)*255/31); + col.a = (((unsigned short *)srcPtr)[0] & 0b0000000000000001)? 255 : 0; + + } break; + case PIXELFORMAT_UNCOMPRESSED_R4G4B4A4: + { + col.r = (unsigned char)((((unsigned short *)srcPtr)[0] >> 12)*255/15); + col.g = (unsigned char)(((((unsigned short *)srcPtr)[0] >> 8) & 0b0000000000001111)*255/15); + col.b = (unsigned char)(((((unsigned short *)srcPtr)[0] >> 4) & 0b0000000000001111)*255/15); + col.a = (unsigned char)((((unsigned short *)srcPtr)[0] & 0b0000000000001111)*255/15); + + } break; + case PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: col = (Color){ ((unsigned char *)srcPtr)[0], ((unsigned char *)srcPtr)[1], ((unsigned char *)srcPtr)[2], ((unsigned char *)srcPtr)[3] }; break; + case PIXELFORMAT_UNCOMPRESSED_R8G8B8: col = (Color){ ((unsigned char *)srcPtr)[0], ((unsigned char *)srcPtr)[1], ((unsigned char *)srcPtr)[2], 255 }; break; + // TODO: case PIXELFORMAT_UNCOMPRESSED_R32: break; + // TODO: case PIXELFORMAT_UNCOMPRESSED_R32G32B32: break; + // TODO: case PIXELFORMAT_UNCOMPRESSED_R32G32B32A32: break; + default: break; + } + + return col; +} + +// Set pixel color formatted into destination pointer +void SetPixelColor(void *dstPtr, Color color, int format) +{ + switch (format) + { + case PIXELFORMAT_UNCOMPRESSED_GRAYSCALE: + { + // NOTE: Calculate grayscale equivalent color + Vector3 coln = { (float)color.r/255.0f, (float)color.g/255.0f, (float)color.b/255.0f }; + unsigned char gray = (unsigned char)((coln.x*0.299f + coln.y*0.587f + coln.z*0.114f)*255.0f); + + ((unsigned char *)dstPtr)[0] = gray; + + } break; + case PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA: + { + // NOTE: Calculate grayscale equivalent color + Vector3 coln = { (float)color.r/255.0f, (float)color.g/255.0f, (float)color.b/255.0f }; + unsigned char gray = (unsigned char)((coln.x*0.299f + coln.y*0.587f + coln.z*0.114f)*255.0f); + + ((unsigned char *)dstPtr)[0] = gray; + ((unsigned char *)dstPtr)[1] = color.a; + + } break; + case PIXELFORMAT_UNCOMPRESSED_R5G6B5: + { + // NOTE: Calculate R5G6B5 equivalent color + Vector3 coln = { (float)color.r/255.0f, (float)color.g/255.0f, (float)color.b/255.0f }; + + unsigned char r = (unsigned char)(round(coln.x*31.0f)); + unsigned char g = (unsigned char)(round(coln.y*63.0f)); + unsigned char b = (unsigned char)(round(coln.z*31.0f)); + + ((unsigned short *)dstPtr)[0] = (unsigned short)r << 11 | (unsigned short)g << 5 | (unsigned short)b; + + } break; + case PIXELFORMAT_UNCOMPRESSED_R5G5B5A1: + { + // NOTE: Calculate R5G5B5A1 equivalent color + Vector4 coln = { (float)color.r/255.0f, (float)color.g/255.0f, (float)color.b/255.0f, (float)color.a/255.0f }; + + unsigned char r = (unsigned char)(round(coln.x*31.0f)); + unsigned char g = (unsigned char)(round(coln.y*31.0f)); + unsigned char b = (unsigned char)(round(coln.z*31.0f)); + unsigned char a = (coln.w > ((float)PIXELFORMAT_UNCOMPRESSED_R5G5B5A1_ALPHA_THRESHOLD/255.0f))? 1 : 0;; + + ((unsigned short *)dstPtr)[0] = (unsigned short)r << 11 | (unsigned short)g << 6 | (unsigned short)b << 1 | (unsigned short)a; + + } break; + case PIXELFORMAT_UNCOMPRESSED_R4G4B4A4: + { + // NOTE: Calculate R5G5B5A1 equivalent color + Vector4 coln = { (float)color.r/255.0f, (float)color.g/255.0f, (float)color.b/255.0f, (float)color.a/255.0f }; + + unsigned char r = (unsigned char)(round(coln.x*15.0f)); + unsigned char g = (unsigned char)(round(coln.y*15.0f)); + unsigned char b = (unsigned char)(round(coln.z*15.0f)); + unsigned char a = (unsigned char)(round(coln.w*15.0f)); + + ((unsigned short *)dstPtr)[0] = (unsigned short)r << 12 | (unsigned short)g << 8 | (unsigned short)b << 4 | (unsigned short)a; + + } break; + case PIXELFORMAT_UNCOMPRESSED_R8G8B8: + { + ((unsigned char *)dstPtr)[0] = color.r; + ((unsigned char *)dstPtr)[1] = color.g; + ((unsigned char *)dstPtr)[2] = color.b; + + } break; + case PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: + { + ((unsigned char *)dstPtr)[0] = color.r; + ((unsigned char *)dstPtr)[1] = color.g; + ((unsigned char *)dstPtr)[2] = color.b; + ((unsigned char *)dstPtr)[3] = color.a; + + } break; + default: break; + } +} + +// Get pixel data size in bytes for certain format +// NOTE: Size can be requested for Image or Texture data +int GetPixelDataSize(int width, int height, int format) +{ + int dataSize = 0; // Size in bytes + int bpp = 0; // Bits per pixel + + switch (format) + { + case PIXELFORMAT_UNCOMPRESSED_GRAYSCALE: bpp = 8; break; + case PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA: + case PIXELFORMAT_UNCOMPRESSED_R5G6B5: + case PIXELFORMAT_UNCOMPRESSED_R5G5B5A1: + case PIXELFORMAT_UNCOMPRESSED_R4G4B4A4: bpp = 16; break; + case PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: bpp = 32; break; + case PIXELFORMAT_UNCOMPRESSED_R8G8B8: bpp = 24; break; + case PIXELFORMAT_UNCOMPRESSED_R32: bpp = 32; break; + case PIXELFORMAT_UNCOMPRESSED_R32G32B32: bpp = 32*3; break; + case PIXELFORMAT_UNCOMPRESSED_R32G32B32A32: bpp = 32*4; break; + case PIXELFORMAT_COMPRESSED_DXT1_RGB: + case PIXELFORMAT_COMPRESSED_DXT1_RGBA: + case PIXELFORMAT_COMPRESSED_ETC1_RGB: + case PIXELFORMAT_COMPRESSED_ETC2_RGB: + case PIXELFORMAT_COMPRESSED_PVRT_RGB: + case PIXELFORMAT_COMPRESSED_PVRT_RGBA: bpp = 4; break; + case PIXELFORMAT_COMPRESSED_DXT3_RGBA: + case PIXELFORMAT_COMPRESSED_DXT5_RGBA: + case PIXELFORMAT_COMPRESSED_ETC2_EAC_RGBA: + case PIXELFORMAT_COMPRESSED_ASTC_4x4_RGBA: bpp = 8; break; + case PIXELFORMAT_COMPRESSED_ASTC_8x8_RGBA: bpp = 2; break; + default: break; + } + + dataSize = width*height*bpp/8; // Total data size in bytes + + // Most compressed formats works on 4x4 blocks, + // if texture is smaller, minimum dataSize is 8 or 16 + if ((width < 4) && (height < 4)) + { + if ((format >= PIXELFORMAT_COMPRESSED_DXT1_RGB) && (format < PIXELFORMAT_COMPRESSED_DXT3_RGBA)) dataSize = 8; + else if ((format >= PIXELFORMAT_COMPRESSED_DXT3_RGBA) && (format < PIXELFORMAT_COMPRESSED_ASTC_8x8_RGBA)) dataSize = 16; + } + + return dataSize; +} + +//---------------------------------------------------------------------------------- +// Module specific Functions Definition +//---------------------------------------------------------------------------------- #if defined(SUPPORT_FILEFORMAT_DDS) // Loading DDS image data (compressed or uncompressed) -static Image LoadDDS(const char *fileName) +static Image LoadDDS(const unsigned char *fileData, unsigned int fileSize) { + unsigned char *fileDataPtr = (unsigned char *)fileData; + // Required extension: // GL_EXT_texture_compression_s3tc @@ -3126,58 +3970,54 @@ static Image LoadDDS(const char *fileName) Image image = { 0 }; - FILE *ddsFile = fopen(fileName, "rb"); - - if (ddsFile == NULL) - { - TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to open DDS file", fileName); - } - else + if (fileDataPtr != NULL) { // Verify the type of file - char ddsHeaderId[4] = { 0 }; - - fread(ddsHeaderId, 4, 1, ddsFile); + unsigned char *ddsHeaderId = fileDataPtr; + fileDataPtr += 4; if ((ddsHeaderId[0] != 'D') || (ddsHeaderId[1] != 'D') || (ddsHeaderId[2] != 'S') || (ddsHeaderId[3] != ' ')) { - TRACELOG(LOG_WARNING, "IMAGE: [%s] DDS file not a valid image", fileName); + TRACELOG(LOG_WARNING, "IMAGE: DDS file data not valid"); } else { - DDSHeader ddsHeader = { 0 }; + DDSHeader *ddsHeader = (DDSHeader *)fileDataPtr; - // Get the image header - fread(&ddsHeader, sizeof(DDSHeader), 1, ddsFile); + TRACELOGD("IMAGE: DDS file data info:"); + TRACELOGD(" > Header size: %i", sizeof(DDSHeader)); + TRACELOGD(" > Pixel format size: %i", ddsHeader->ddspf.size); + TRACELOGD(" > Pixel format flags: 0x%x", ddsHeader->ddspf.flags); + TRACELOGD(" > File format: 0x%x", ddsHeader->ddspf.fourCC); + TRACELOGD(" > File bit count: 0x%x", ddsHeader->ddspf.rgbBitCount); - TRACELOGD("IMAGE: [%s] DDS file info:", fileName); - TRACELOGD(" > Header size: %i", fileName, sizeof(DDSHeader)); - TRACELOGD(" > Pixel format size: %i", fileName, ddsHeader.ddspf.size); - TRACELOGD(" > Pixel format flags: 0x%x", fileName, ddsHeader.ddspf.flags); - TRACELOGD(" > File format: 0x%x", fileName, ddsHeader.ddspf.fourCC); - TRACELOGD(" > File bit count: 0x%x", fileName, ddsHeader.ddspf.rgbBitCount); + fileDataPtr += sizeof(DDSHeader); // Skip header - image.width = ddsHeader.width; - image.height = ddsHeader.height; + image.width = ddsHeader->width; + image.height = ddsHeader->height; - if (ddsHeader.mipmapCount == 0) image.mipmaps = 1; // Parameter not used - else image.mipmaps = ddsHeader.mipmapCount; + if (ddsHeader->mipmapCount == 0) image.mipmaps = 1; // Parameter not used + 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)); - fread(image.data, image.width*image.height*sizeof(unsigned short), 1, ddsFile); + int dataSize = image.width*image.height*sizeof(unsigned short); + image.data = (unsigned short *)RL_MALLOC(dataSize); - image.format = UNCOMPRESSED_R5G6B5; + memcpy(image.data, fileDataPtr, dataSize); + + image.format = PIXELFORMAT_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)); - fread(image.data, image.width*image.height*sizeof(unsigned short), 1, ddsFile); + int dataSize = image.width*image.height*sizeof(unsigned short); + image.data = (unsigned short *)RL_MALLOC(dataSize); + + memcpy(image.data, fileDataPtr, dataSize); unsigned char alpha = 0; @@ -3189,12 +4029,14 @@ static Image LoadDDS(const char *fileName) ((unsigned short *)image.data)[i] += alpha; } - image.format = UNCOMPRESSED_R5G5B5A1; + image.format = PIXELFORMAT_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)); - fread(image.data, image.width*image.height*sizeof(unsigned short), 1, ddsFile); + int dataSize = image.width*image.height*sizeof(unsigned short); + image.data = (unsigned short *)RL_MALLOC(dataSize); + + memcpy(image.data, fileDataPtr, dataSize); unsigned char alpha = 0; @@ -3206,22 +4048,25 @@ static Image LoadDDS(const char *fileName) ((unsigned short *)image.data)[i] += alpha; } - image.format = UNCOMPRESSED_R4G4B4A4; + image.format = PIXELFORMAT_UNCOMPRESSED_R4G4B4A4; } } } - 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... - image.data = (unsigned char *)RL_MALLOC(image.width*image.height*3*sizeof(unsigned char)); - fread(image.data, image.width*image.height*3, 1, ddsFile); + int dataSize = image.width*image.height*3*sizeof(unsigned char); + image.data = (unsigned short *)RL_MALLOC(dataSize); + + memcpy(image.data, fileDataPtr, dataSize); - image.format = UNCOMPRESSED_R8G8B8; + image.format = PIXELFORMAT_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)); - fread(image.data, image.width*image.height*4, 1, ddsFile); + int dataSize = image.width*image.height*4*sizeof(unsigned char); + image.data = (unsigned short *)RL_MALLOC(dataSize); + + memcpy(image.data, fileDataPtr, dataSize); unsigned char blue = 0; @@ -3235,35 +4080,33 @@ static Image LoadDDS(const char *fileName) ((unsigned char *)image.data)[i + 2] = blue; } - image.format = UNCOMPRESSED_R8G8B8A8; + image.format = PIXELFORMAT_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 - if (ddsHeader.mipmapCount > 1) size = ddsHeader.pitchOrLinearSize*2; - else size = ddsHeader.pitchOrLinearSize; + if (ddsHeader->mipmapCount > 1) dataSize = ddsHeader->pitchOrLinearSize*2; + 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: { - if (ddsHeader.ddspf.flags == 0x04) image.format = COMPRESSED_DXT1_RGB; - else image.format = COMPRESSED_DXT1_RGBA; + if (ddsHeader->ddspf.flags == 0x04) image.format = PIXELFORMAT_COMPRESSED_DXT1_RGB; + else image.format = PIXELFORMAT_COMPRESSED_DXT1_RGBA; } break; - case FOURCC_DXT3: image.format = COMPRESSED_DXT3_RGBA; break; - case FOURCC_DXT5: image.format = COMPRESSED_DXT5_RGBA; break; + case FOURCC_DXT3: image.format = PIXELFORMAT_COMPRESSED_DXT3_RGBA; break; + case FOURCC_DXT5: image.format = PIXELFORMAT_COMPRESSED_DXT5_RGBA; break; default: break; } } } - - fclose(ddsFile); // Close file pointer } return image; @@ -3274,8 +4117,10 @@ static Image LoadDDS(const char *fileName) // 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) -static Image LoadPKM(const char *fileName) +static Image LoadPKM(const unsigned char *fileData, unsigned int fileSize) { + unsigned char *fileDataPtr = (unsigned char *)fileData; + // Required extensions: // GL_OES_compressed_ETC1_RGB8_texture (ETC1) (OpenGL ES 2.0) // GL_ARB_ES3_compatibility (ETC2/EAC) (OpenGL ES 3.0) @@ -3305,54 +4150,45 @@ static Image LoadPKM(const char *fileName) Image image = { 0 }; - FILE *pkmFile = fopen(fileName, "rb"); - - if (pkmFile == NULL) - { - TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to open PKM file", fileName); - } - else + if (fileDataPtr != NULL) { - PKMHeader pkmHeader = { 0 }; + PKMHeader *pkmHeader = (PKMHeader *)fileDataPtr; - // Get the image header - fread(&pkmHeader, sizeof(PKMHeader), 1, pkmFile); - - if ((pkmHeader.id[0] != 'P') || (pkmHeader.id[1] != 'K') || (pkmHeader.id[2] != 'M') || (pkmHeader.id[3] != ' ')) + 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: PKM file data not valid"); } else { + fileDataPtr += sizeof(PKMHeader); // Skip header + // 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.width = ((pkmHeader.width & 0x00FF) << 8) | ((pkmHeader.width & 0xFF00) >> 8); - pkmHeader.height = ((pkmHeader.height & 0x00FF) << 8) | ((pkmHeader.height & 0xFF00) >> 8); + pkmHeader->format = ((pkmHeader->format & 0x00FF) << 8) | ((pkmHeader->format & 0xFF00) >> 8); + pkmHeader->width = ((pkmHeader->width & 0x00FF) << 8) | ((pkmHeader->width & 0xFF00) >> 8); + pkmHeader->height = ((pkmHeader->height & 0x00FF) << 8) | ((pkmHeader->height & 0xFF00) >> 8); - TRACELOGD("IMAGE: [%s] PKM file info:", fileName); - TRACELOGD(" > Image width: %i", pkmHeader.width); - TRACELOGD(" > Image height: %i", pkmHeader.height); - TRACELOGD(" > Image format: %i", pkmHeader.format); + TRACELOGD("IMAGE: PKM file data info:"); + TRACELOGD(" > Image width: %i", pkmHeader->width); + TRACELOGD(" > Image height: %i", pkmHeader->height); + TRACELOGD(" > Image format: %i", pkmHeader->format); - image.width = pkmHeader.width; - image.height = pkmHeader.height; + image.width = pkmHeader->width; + image.height = pkmHeader->height; image.mipmaps = 1; 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(dataSize*sizeof(unsigned char)); - fread(image.data, size, 1, pkmFile); + memcpy(image.data, fileDataPtr, dataSize); - if (pkmHeader.format == 0) image.format = COMPRESSED_ETC1_RGB; - else if (pkmHeader.format == 1) image.format = COMPRESSED_ETC2_RGB; - else if (pkmHeader.format == 3) image.format = COMPRESSED_ETC2_EAC_RGBA; + if (pkmHeader->format == 0) image.format = PIXELFORMAT_COMPRESSED_ETC1_RGB; + else if (pkmHeader->format == 1) image.format = PIXELFORMAT_COMPRESSED_ETC2_RGB; + else if (pkmHeader->format == 3) image.format = PIXELFORMAT_COMPRESSED_ETC2_EAC_RGBA; } - - fclose(pkmFile); // Close file pointer } return image; @@ -3361,8 +4197,10 @@ static Image LoadPKM(const char *fileName) #if defined(SUPPORT_FILEFORMAT_KTX) // Load KTX compressed image data (ETC1/ETC2 compression) -static Image LoadKTX(const char *fileName) +static Image LoadKTX(const unsigned char *fileData, unsigned int fileSize) { + unsigned char *fileDataPtr = (unsigned char *)fileData; + // Required extensions: // GL_OES_compressed_ETC1_RGB8_texture (ETC1) // GL_ARB_ES3_compatibility (ETC2/EAC) @@ -3399,55 +4237,41 @@ static Image LoadKTX(const char *fileName) Image image = { 0 }; - FILE *ktxFile = fopen(fileName, "rb"); - - if (ktxFile == NULL) - { - TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to load KTX file", fileName); - } - else + if (fileDataPtr != NULL) { - KTXHeader ktxHeader = { 0 }; + KTXHeader *ktxHeader = (KTXHeader *)fileDataPtr; - // Get the image header - fread(&ktxHeader, sizeof(KTXHeader), 1, ktxFile); - - if ((ktxHeader.id[1] != 'K') || (ktxHeader.id[2] != 'T') || (ktxHeader.id[3] != 'X') || - (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: KTX file data not valid"); } else { - image.width = ktxHeader.width; - image.height = ktxHeader.height; - image.mipmaps = ktxHeader.mipmapLevels; + fileDataPtr += sizeof(KTXHeader); // Move file data pointer - TRACELOGD("IMAGE: [%s] KTX file info:", fileName); - TRACELOGD(" > Image width: %i", ktxHeader.width); - TRACELOGD(" > Image height: %i", ktxHeader.height); - TRACELOGD(" > Image format: 0x%x", ktxHeader.glInternalFormat); + image.width = ktxHeader->width; + image.height = ktxHeader->height; + image.mipmaps = ktxHeader->mipmapLevels; - unsigned char unused; + TRACELOGD("IMAGE: KTX file data info:"); + TRACELOGD(" > Image width: %i", ktxHeader->width); + TRACELOGD(" > Image height: %i", ktxHeader->height); + TRACELOGD(" > Image format: 0x%x", ktxHeader->glInternalFormat); - if (ktxHeader.keyValueDataSize > 0) - { - for (unsigned int i = 0; i < ktxHeader.keyValueDataSize; i++) fread(&unused, sizeof(unsigned char), 1U, ktxFile); - } + fileDataPtr += ktxHeader->keyValueDataSize; // Skip value data size - 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)); - fread(image.data, dataSize, 1, ktxFile); + memcpy(image.data, fileDataPtr, dataSize); - if (ktxHeader.glInternalFormat == 0x8D64) image.format = COMPRESSED_ETC1_RGB; - else if (ktxHeader.glInternalFormat == 0x9274) image.format = COMPRESSED_ETC2_RGB; - else if (ktxHeader.glInternalFormat == 0x9278) image.format = COMPRESSED_ETC2_EAC_RGBA; + if (ktxHeader->glInternalFormat == 0x8D64) image.format = PIXELFORMAT_COMPRESSED_ETC1_RGB; + else if (ktxHeader->glInternalFormat == 0x9274) image.format = PIXELFORMAT_COMPRESSED_ETC2_RGB; + else if (ktxHeader->glInternalFormat == 0x9278) image.format = PIXELFORMAT_COMPRESSED_ETC2_EAC_RGBA; } - - fclose(ktxFile); // Close file pointer } return image; @@ -3457,12 +4281,9 @@ static Image LoadKTX(const char *fileName) // NOTE: By default KTX 1.1 spec is used, 2.0 is still on draft (01Oct2018) static int SaveKTX(Image image, const char *fileName) { - int success = 0; - // KTX file Header (64 bytes) // 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 - typedef struct { 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 @@ -3482,67 +4303,76 @@ static int SaveKTX(Image image, const char *fileName) // KTX 2.0 defines additional header elements... } KTXHeader; - // NOTE: Before start of every mipmap data block, we have: unsigned int dataSize - - FILE *ktxFile = fopen(fileName, "wb"); + // Calculate file dataSize required + int dataSize = sizeof(KTXHeader); - if (ktxFile == NULL) TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to open KTX file", fileName); - else + for (int i = 0, width = image.width, height = image.height; i < image.mipmaps; i++) { - 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 }; + unsigned char *fileData = RL_CALLOC(dataSize, 1); + unsigned char *fileDataPtr = fileData; - const char ktxIdentifier[12] = { 0xAB, 'K', 'T', 'X', ' ', '1', '1', 0xBB, '\r', '\n', 0x1A, '\n' }; + KTXHeader ktxHeader = { 0 }; - // 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 + // 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 }; - rlGetGlTextureFormats(image.format, &ktxHeader.glInternalFormat, &ktxHeader.glFormat, &ktxHeader.glType); // rlgl module function - ktxHeader.glBaseInternalFormat = ktxHeader.glFormat; // KTX 1.1 only + const char ktxIdentifier[12] = { 0xAB, 'K', 'T', 'X', ' ', '1', '1', 0xBB, '\r', '\n', 0x1A, '\n' }; - // NOTE: We can save into a .ktx all PixelFormats supported by raylib, including compressed formats like DXT, ETC or ASTC + // 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 - if (ktxHeader.glFormat == -1) TRACELOG(LOG_WARNING, "IMAGE: GL format not supported for KTX export (%i)", ktxHeader.glFormat); - else - { - success = fwrite(&ktxHeader, sizeof(KTXHeader), 1, ktxFile); + rlGetGlTextureFormats(image.format, &ktxHeader.glInternalFormat, &ktxHeader.glFormat, &ktxHeader.glType); // rlgl module function + ktxHeader.glBaseInternalFormat = ktxHeader.glFormat; // KTX 1.1 only - int width = image.width; - int height = image.height; - int dataOffset = 0; + // NOTE: We can save into a .ktx all PixelFormats supported by raylib, including compressed formats like DXT, ETC or ASTC - // Save all mipmaps data - for (int i = 0; i < image.mipmaps; i++) - { - unsigned int dataSize = GetPixelDataSize(width, height, image.format); - success = fwrite(&dataSize, sizeof(unsigned int), 1, ktxFile); - success = fwrite((unsigned char *)image.data + dataOffset, dataSize, 1, ktxFile); + 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); - width /= 2; - height /= 2; - dataOffset += dataSize; - } - } + 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); - fclose(ktxFile); // Close file pointer + width /= 2; + height /= 2; + dataOffset += dataSize; + fileDataPtr += (4 + dataSize); + } } + int success = SaveFileData(fileName, fileData, dataSize); + + RL_FREE(fileData); // Free file data buffer + // If all data has been written correctly to file, success = 1 return success; } @@ -3551,8 +4381,10 @@ static int SaveKTX(Image image, const char *fileName) #if defined(SUPPORT_FILEFORMAT_PVR) // Loading PVR image data (uncompressed or PVRT compression) // NOTE: PVR v2 not supported, use PVR v3 instead -static Image LoadPVR(const char *fileName) +static Image LoadPVR(const unsigned char *fileData, unsigned int fileSize) { + unsigned char *fileDataPtr = (unsigned char *)fileData; + // Required extension: // GL_IMG_texture_compression_pvrtc @@ -3609,93 +4441,73 @@ static Image LoadPVR(const char *fileName) Image image = { 0 }; - FILE *pvrFile = fopen(fileName, "rb"); - - if (pvrFile == NULL) - { - TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to load PVR file", fileName); - } - else + if (fileDataPtr != NULL) { // Check PVR image version - unsigned char pvrVersion = 0; - fread(&pvrVersion, sizeof(unsigned char), 1, pvrFile); - fseek(pvrFile, 0, SEEK_SET); + unsigned char pvrVersion = fileDataPtr[0]; // Load different PVR data formats if (pvrVersion == 0x50) { - PVRHeaderV3 pvrHeader = { 0 }; - - // Get PVR image header - fread(&pvrHeader, sizeof(PVRHeaderV3), 1, pvrFile); + PVRHeaderV3 *pvrHeader = (PVRHeaderV3 *)fileDataPtr; - if ((pvrHeader.id[0] != 'P') || (pvrHeader.id[1] != 'V') || (pvrHeader.id[2] != 'R') || (pvrHeader.id[3] != 3)) + 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: PVR file data not valid"); } else { - image.width = pvrHeader.width; - image.height = pvrHeader.height; - image.mipmaps = pvrHeader.numMipmaps; + fileDataPtr += sizeof(PVRHeaderV3); // Skip header + + image.width = pvrHeader->width; + image.height = pvrHeader->height; + image.mipmaps = pvrHeader->numMipmaps; // Check data format - if (((pvrHeader.channels[0] == 'l') && (pvrHeader.channels[1] == 0)) && (pvrHeader.channelDepth[0] == 8)) - 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] == 'r') && (pvrHeader.channels[1] == 'g') && (pvrHeader.channels[2] == 'b')) + if (((pvrHeader->channels[0] == 'l') && (pvrHeader->channels[1] == 0)) && (pvrHeader->channelDepth[0] == 8)) image.format = PIXELFORMAT_UNCOMPRESSED_GRAYSCALE; + else if (((pvrHeader->channels[0] == 'l') && (pvrHeader->channels[1] == 'a')) && ((pvrHeader->channelDepth[0] == 8) && (pvrHeader->channelDepth[1] == 8))) image.format = PIXELFORMAT_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)) - 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] == 8) && (pvrHeader.channelDepth[1] == 8) && (pvrHeader.channelDepth[2] == 8) && (pvrHeader.channelDepth[3] == 8)) - image.format = UNCOMPRESSED_R8G8B8A8; + if ((pvrHeader->channelDepth[0] == 5) && (pvrHeader->channelDepth[1] == 5) && (pvrHeader->channelDepth[2] == 5) && (pvrHeader->channelDepth[3] == 1)) image.format = PIXELFORMAT_UNCOMPRESSED_R5G5B5A1; + else if ((pvrHeader->channelDepth[0] == 4) && (pvrHeader->channelDepth[1] == 4) && (pvrHeader->channelDepth[2] == 4) && (pvrHeader->channelDepth[3] == 4)) image.format = PIXELFORMAT_UNCOMPRESSED_R4G4B4A4; + else if ((pvrHeader->channelDepth[0] == 8) && (pvrHeader->channelDepth[1] == 8) && (pvrHeader->channelDepth[2] == 8) && (pvrHeader->channelDepth[3] == 8)) image.format = PIXELFORMAT_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; - else if ((pvrHeader.channelDepth[0] == 8) && (pvrHeader.channelDepth[1] == 8) && (pvrHeader.channelDepth[2] == 8)) image.format = UNCOMPRESSED_R8G8B8; + if ((pvrHeader->channelDepth[0] == 5) && (pvrHeader->channelDepth[1] == 6) && (pvrHeader->channelDepth[2] == 5)) image.format = PIXELFORMAT_UNCOMPRESSED_R5G6B5; + else if ((pvrHeader->channelDepth[0] == 8) && (pvrHeader->channelDepth[1] == 8) && (pvrHeader->channelDepth[2] == 8)) image.format = PIXELFORMAT_UNCOMPRESSED_R8G8B8; } } - 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] == 2) image.format = PIXELFORMAT_COMPRESSED_PVRT_RGB; + else if (pvrHeader->channels[0] == 3) image.format = PIXELFORMAT_COMPRESSED_PVRT_RGBA; - // Skip meta data header - unsigned char unused = 0; - for (int i = 0; i < pvrHeader.metaDataSize; i++) fread(&unused, sizeof(unsigned char), 1, pvrFile); + fileDataPtr += pvrHeader->metaDataSize; // Skip meta data header // 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; + case PIXELFORMAT_UNCOMPRESSED_GRAYSCALE: bpp = 8; break; + case PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA: + case PIXELFORMAT_UNCOMPRESSED_R5G5B5A1: + case PIXELFORMAT_UNCOMPRESSED_R5G6B5: + case PIXELFORMAT_UNCOMPRESSED_R4G4B4A4: bpp = 16; break; + case PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: bpp = 32; break; + case PIXELFORMAT_UNCOMPRESSED_R8G8B8: bpp = 24; break; + case PIXELFORMAT_COMPRESSED_PVRT_RGB: + case PIXELFORMAT_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 *)RL_MALLOC(dataSize*sizeof(unsigned char)); - // Read data from file - fread(image.data, dataSize, 1, pvrFile); + memcpy(image.data, fileDataPtr, dataSize); } } - else if (pvrVersion == 52) TRACELOG(LOG_INFO, "IMAGE: [%s] PVRv2 format not supported, update your files to PVRv3", fileName); - - fclose(pvrFile); // Close file pointer + else if (pvrVersion == 52) TRACELOG(LOG_INFO, "IMAGE: PVRv2 format not supported, update your files to PVRv3"); } return image; @@ -3704,8 +4516,10 @@ static Image LoadPVR(const char *fileName) #if defined(SUPPORT_FILEFORMAT_ASTC) // Load ASTC compressed image data (ASTC compression) -static Image LoadASTC(const char *fileName) +static Image LoadASTC(const unsigned char *fileData, unsigned int fileSize) { + unsigned char *fileDataPtr = (unsigned char *)fileData; + // Required extensions: // GL_KHR_texture_compression_astc_hdr // GL_KHR_texture_compression_astc_ldr @@ -3727,38 +4541,31 @@ static Image LoadASTC(const char *fileName) Image image = { 0 }; - FILE *astcFile = fopen(fileName, "rb"); - - if (astcFile == NULL) - { - TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to load ASTC file", fileName); - } - else + if (fileDataPtr != NULL) { - ASTCHeader astcHeader = { 0 }; + ASTCHeader *astcHeader = (ASTCHeader *)fileDataPtr; - // Get ASTC image header - fread(&astcHeader, sizeof(ASTCHeader), 1, astcFile); - - if ((astcHeader.id[3] != 0x5c) || (astcHeader.id[2] != 0xa1) || (astcHeader.id[1] != 0xab) || (astcHeader.id[0] != 0x13)) + 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: ASTC file data not valid"); } else { + fileDataPtr += sizeof(ASTCHeader); // Skip header + // 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.height = 0x00000000 | ((int)astcHeader.height[2] << 16) | ((int)astcHeader.height[1] << 8) | ((int)astcHeader.height[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]); - TRACELOGD("IMAGE: [%s] ASTC file info:", fileName); + TRACELOGD("IMAGE: ASTC file data info:"); TRACELOGD(" > Image width: %i", image.width); 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 // 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 if ((bpp == 8) || (bpp == 2)) @@ -3766,15 +4573,14 @@ static Image LoadASTC(const char *fileName) int dataSize = image.width*image.height*bpp/8; // Data size in bytes image.data = (unsigned char *)RL_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_8x8_RGBA; + memcpy(image.data, fileDataPtr, dataSize); + + if (bpp == 8) image.format = PIXELFORMAT_COMPRESSED_ASTC_4x4_RGBA; + else if (bpp == 2) image.format = PIXELFORMAT_COMPRESSED_ASTC_8x8_RGBA; } - else TRACELOG(LOG_WARNING, "IMAGE: [%s] ASTC block size configuration not supported", fileName); + else TRACELOG(LOG_WARNING, "IMAGE: ASTC block size configuration not supported"); } - - fclose(astcFile); } return image; |