text: /**********************************************************************************************
  *
  *   rtextures - Basic functions to load and draw textures
  *
  *   CONFIGURATION:
  *
  *   #define SUPPORT_MODULE_RTEXTURES
  *       rtextures module is included in the build
  *
  *   #define SUPPORT_FILEFORMAT_BMP
  *   #define SUPPORT_FILEFORMAT_PNG
  *   #define SUPPORT_FILEFORMAT_TGA
  *   #define SUPPORT_FILEFORMAT_JPG
  *   #define SUPPORT_FILEFORMAT_GIF
  *   #define SUPPORT_FILEFORMAT_QOI
  *   #define SUPPORT_FILEFORMAT_PSD
  *   #define SUPPORT_FILEFORMAT_PIC
  *   #define SUPPORT_FILEFORMAT_HDR
  *   #define SUPPORT_FILEFORMAT_DDS
  *   #define SUPPORT_FILEFORMAT_PKM
  *   #define SUPPORT_FILEFORMAT_KTX
  *   #define SUPPORT_FILEFORMAT_PVR
  *   #define SUPPORT_FILEFORMAT_ASTC
  *       Select desired fileformats to be supported for image data loading. Some of those formats are
  *       supported by default, to remove support, just comment unrequired #define in this module
  *
  *   #define SUPPORT_IMAGE_EXPORT
  *       Support image export in multiple file formats
  *
  *   #define SUPPORT_IMAGE_MANIPULATION
  *       Support multiple image editing functions to scale, adjust colors, flip, draw on images, crop...
  *       If not defined only three image editing functions supported: ImageFormat(), ImageAlphaMask(), ImageToPOT()
  *
  *   #define SUPPORT_IMAGE_GENERATION
  *       Support procedural image generation functionality (gradient, spot, perlin-noise, cellular)
  *
  *   DEPENDENCIES:
  *       stb_image        - Multiple image formats loading (JPEG, PNG, BMP, TGA, PSD, GIF, PIC)
  *                          NOTE: stb_image has been slightly modified to support Android platform.
  *       stb_image_resize - Multiple image resize algorythms
  *
  *
  *   LICENSE: zlib/libpng
  *
  *   Copyright (c) 2013-2022 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.
  *
  *   Permission is granted to anyone to use this software for any purpose, including commercial
  *   applications, and to alter it and redistribute it freely, subject to the following restrictions:
  *
  *     1. The origin of this software must not be misrepresented; you must not claim that you
  *     wrote the original software. If you use this software in a product, an acknowledgment
  *     in the product documentation would be appreciated but is not required.
  *
  *     2. Altered source versions must be plainly marked as such, and must not be misrepresented
  *     as being the original software.
  *
  *     3. This notice may not be removed or altered from any source distribution.
  *
  **********************************************************************************************/
  
  #include "raylib.h"             // Declares module functions
  
  // Check if config flags have been externally provided on compilation line
  #if !defined(EXTERNAL_CONFIG_FLAGS)
      #include "config.h"         // Defines module configuration flags
  #endif
  
  #if defined(SUPPORT_MODULE_RTEXTURES)
  
  #include "utils.h"              // Required for: TRACELOG() and fopen() Android mapping
  #include "rlgl.h"               // OpenGL abstraction layer to OpenGL 1.1, 3.3 or ES2
  
  #include <stdlib.h>             // Required for: malloc(), free()
  #include <string.h>             // Required for: strlen() [Used in ImageTextEx()], strcmp() [Used in LoadImageFromMemory()]
  #include <math.h>               // Required for: fabsf()
  #include <stdio.h>              // Required for: sprintf() [Used in ExportImageAsCode()]
  
  // Support only desired texture formats on stb_image
  #if !defined(SUPPORT_FILEFORMAT_BMP)
      #define STBI_NO_BMP
  #endif
  #if !defined(SUPPORT_FILEFORMAT_PNG)
      #define STBI_NO_PNG
  #endif
  #if !defined(SUPPORT_FILEFORMAT_TGA)
      #define STBI_NO_TGA
  #endif
  #if !defined(SUPPORT_FILEFORMAT_JPG)
      #define STBI_NO_JPEG        // Image format .jpg and .jpeg
  #endif
  #if !defined(SUPPORT_FILEFORMAT_PSD)
      #define STBI_NO_PSD
  #endif
  #if !defined(SUPPORT_FILEFORMAT_GIF)
      #define STBI_NO_GIF
  #endif
  #if !defined(SUPPORT_FILEFORMAT_PIC)
      #define STBI_NO_PIC
  #endif
  #if !defined(SUPPORT_FILEFORMAT_HDR)
      #define STBI_NO_HDR
  #endif
  
  // Image fileformats not supported by default
  #define STBI_NO_PIC
  #define STBI_NO_PNM             // Image format .ppm and .pgm
  
  #if defined(__TINYC__)
      #define STBI_NO_SIMD
  #endif
  
  #if (defined(SUPPORT_FILEFORMAT_BMP) || \
       defined(SUPPORT_FILEFORMAT_PNG) || \
       defined(SUPPORT_FILEFORMAT_TGA) || \
       defined(SUPPORT_FILEFORMAT_JPG) || \
       defined(SUPPORT_FILEFORMAT_PSD) || \
       defined(SUPPORT_FILEFORMAT_GIF) || \
       defined(SUPPORT_FILEFORMAT_PIC) || \
       defined(SUPPORT_FILEFORMAT_HDR))
  
      #define STBI_MALLOC RL_MALLOC
      #define STBI_FREE RL_FREE
      #define STBI_REALLOC RL_REALLOC
  
      #define STB_IMAGE_IMPLEMENTATION
      #include "external/stb_image.h"         // Required for: stbi_load_from_file()
                                              // NOTE: Used to read image data (multiple formats support)
  #endif
  
  #if defined(SUPPORT_FILEFORMAT_QOI)
      #define QOI_MALLOC RL_MALLOC
      #define QOI_FREE RL_FREE
  
      #define QOI_IMPLEMENTATION
      #include "external/qoi.h"
  #endif
  
  #if defined(SUPPORT_IMAGE_EXPORT)
      #define STBIW_MALLOC RL_MALLOC
      #define STBIW_FREE RL_FREE
      #define STBIW_REALLOC RL_REALLOC
  
      #define STB_IMAGE_WRITE_IMPLEMENTATION
      #include "external/stb_image_write.h"   // Required for: stbi_write_*()
  #endif
  
  #if defined(SUPPORT_IMAGE_MANIPULATION)
      #define STBIR_MALLOC(size,c) ((void)(c), RL_MALLOC(size))
      #define STBIR_FREE(ptr,c) ((void)(c), RL_FREE(ptr))
  
      #define STB_IMAGE_RESIZE_IMPLEMENTATION
      #include "external/stb_image_resize.h"  // Required for: stbir_resize_uint8() [ImageResize()]
  #endif
  
  //----------------------------------------------------------------------------------
  // Defines and Macros
  //----------------------------------------------------------------------------------
  #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
  //----------------------------------------------------------------------------------
  // ...
  
  //----------------------------------------------------------------------------------
  // Global Variables Definition
  //----------------------------------------------------------------------------------
  // It's lonely here...
  
  //----------------------------------------------------------------------------------
  // Other Modules Functions Declaration (required by text)
  //----------------------------------------------------------------------------------
  // ...
  
  //----------------------------------------------------------------------------------
  // Module specific Functions Declaration
  //----------------------------------------------------------------------------------
  #if defined(SUPPORT_FILEFORMAT_DDS)
  static Image LoadDDS(const unsigned char *fileData, unsigned int fileSize);   // Load DDS file data
  #endif
  #if defined(SUPPORT_FILEFORMAT_PKM)
  static Image LoadPKM(const unsigned char *fileData, unsigned int fileSize);   // Load PKM file data
  #endif
  #if defined(SUPPORT_FILEFORMAT_KTX)
  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 unsigned char *fileData, unsigned int fileSize);   // Load PVR file data
  #endif
  #if defined(SUPPORT_FILEFORMAT_ASTC)
  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
  //----------------------------------------------------------------------------------
  
  // Load image from file into CPU memory (RAM)
  Image LoadImage(const char *fileName)
  {
      Image image = { 0 };
  
  #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) || \
      defined(SUPPORT_FILEFORMAT_PSD)
  
      #define STBI_REQUIRED
  #endif
  
      // Loading file to memory
      unsigned int fileSize = 0;
      unsigned char *fileData = LoadFileData(fileName, &fileSize);
  
      // Loading image from memory data
      if (fileData != NULL) image = LoadImageFromMemory(GetFileExtension(fileName), fileData, fileSize);
  
      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 frameCount = 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, &frameCount, &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 = frameCount;
      return image;
  }
  
  // Load image from memory buffer, fileType refers to extension: i.e. ".png"
  // WARNING: File extension must be provided in lower-case
  Image LoadImageFromMemory(const char *fileType, const unsigned char *fileData, int dataSize)
  {
      Image image = { 0 };
  
      if ((false)
  #if defined(SUPPORT_FILEFORMAT_PNG)
          || (strcmp(fileType, ".png") == 0)
  #endif
  #if defined(SUPPORT_FILEFORMAT_BMP)
          || (strcmp(fileType, ".bmp") == 0)
  #endif
  #if defined(SUPPORT_FILEFORMAT_TGA)
          || (strcmp(fileType, ".tga") == 0)
  #endif
  #if defined(SUPPORT_FILEFORMAT_JPG)
          || ((strcmp(fileType, ".jpg") == 0) || (strcmp(fileType, ".jpeg") == 0))
  #endif
  #if defined(SUPPORT_FILEFORMAT_GIF)
          || (strcmp(fileType, ".gif") == 0)
  #endif
  #if defined(SUPPORT_FILEFORMAT_PIC)
          || (strcmp(fileType, ".pic") == 0)
  #endif
  #if defined(SUPPORT_FILEFORMAT_PSD)
          || (strcmp(fileType, ".psd") == 0)
  #endif
         )
      {
  #if defined(STBI_REQUIRED)
          // NOTE: Using stb_image to load images (Supports multiple image formats)
  
          if (fileData != NULL)
          {
              int comp = 0;
              image.data = stbi_load_from_memory(fileData, dataSize, &image.width, &image.height, &comp, 0);
  
              if (image.data != NULL)
              {
                  image.mipmaps = 1;
  
                  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 (strcmp(fileType, ".hdr") == 0)
      {
  #if defined(STBI_REQUIRED)
          if (fileData != NULL)
          {
              int comp = 0;
              image.data = stbi_loadf_from_memory(fileData, dataSize, &image.width, &image.height, &comp, 0);
  
              image.mipmaps = 1;
  
              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: HDR file format not supported");
                  UnloadImage(image);
              }
          }
  #endif
      }
  #endif
  #if defined(SUPPORT_FILEFORMAT_QOI)
      else if (strcmp(fileType, ".qoi") == 0)
      {
          qoi_desc desc = { 0 };
          image.data = qoi_decode(fileData, dataSize, &desc, 4);
          image.width = desc.width;
          image.height = desc.height;
          image.format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8;
          image.mipmaps = 1;
      }
  #endif
  #if defined(SUPPORT_FILEFORMAT_DDS)
      else if (strcmp(fileType, ".dds") == 0) image = LoadDDS(fileData, dataSize);
  #endif
  #if defined(SUPPORT_FILEFORMAT_PKM)
      else if (strcmp(fileType, ".pkm") == 0) image = LoadPKM(fileData, dataSize);
  #endif
  #if defined(SUPPORT_FILEFORMAT_KTX)
      else if (strcmp(fileType, ".ktx") == 0) image = LoadKTX(fileData, dataSize);
  #endif
  #if defined(SUPPORT_FILEFORMAT_PVR)
      else if (strcmp(fileType, ".pvr") == 0) image = LoadPVR(fileData, dataSize);
  #endif
  #if defined(SUPPORT_FILEFORMAT_ASTC)
      else if (strcmp(fileType, ".astc") == 0) image = LoadASTC(fileData, dataSize);
  #endif
      else TRACELOG(LOG_WARNING, "IMAGE: Data format not supported");
  
      if (image.data != NULL) TRACELOG(LOG_INFO, "IMAGE: Data loaded successfully (%ix%i | %s | %i mipmaps)", image.width, image.height, rlGetPixelFormatName(image.format), image.mipmaps);
      else TRACELOG(LOG_WARNING, "IMAGE: Failed to load image data");
  
      return image;
  }
  
  // Load image from GPU texture data
  // NOTE: Compressed texture formats not supported
  Image LoadImageFromTexture(Texture2D texture)
  {
      Image image = { 0 };
  
      if (texture.format < PIXELFORMAT_COMPRESSED_DXT1_RGB)
      {
          image.data = rlReadTexturePixels(texture.id, texture.width, texture.height, texture.format);
  
          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);
  
      return image;
  }
  
  // Load image from screen buffer and (screenshot)
  Image LoadImageFromScreen(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;
  }
  
  // Unload image from CPU memory (RAM)
  void UnloadImage(Image image)
  {
      RL_FREE(image.data);
  }
  
  // Export image data to file
  // NOTE: File format depends on fileName extension
  bool ExportImage(Image image, const char *fileName)
  {
      int success = 0;
  
  #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
      {
          // NOTE: Getting Color array as RGBA unsigned char values
          imgData = (unsigned char *)LoadImageColors(image);
          allocatedData = true;
      }
  
  #if defined(SUPPORT_FILEFORMAT_PNG)
      if (IsFileExtension(fileName, ".png"))
      {
          int dataSize = 0;
          unsigned char *fileData = stbi_write_png_to_mem((const unsigned char *)imgData, image.width*channels, image.width, image.height, channels, &dataSize);
          success = SaveFileData(fileName, fileData, dataSize);
          RL_FREE(fileData);
      }
  #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") ||
               IsFileExtension(fileName, ".jpeg")) success = stbi_write_jpg(fileName, image.width, image.height, channels, imgData, 90);  // JPG quality: between 1 and 100
  #endif
  #if defined(SUPPORT_FILEFORMAT_QOI)
      else if (IsFileExtension(fileName, ".qoi"))
      {
          channels = 0;
          if (image.format == PIXELFORMAT_UNCOMPRESSED_R8G8B8) channels = 3;
          else if (image.format == PIXELFORMAT_UNCOMPRESSED_R8G8B8A8) channels = 4;
          else TRACELOG(LOG_WARNING, "IMAGE: Image pixel format must be R8G8B8 or R8G8B8A8");
  
          if ((channels == 3) || (channels == 4))
          {
              qoi_desc desc = { 0 };
              desc.width = image.width;
              desc.height = image.height;
              desc.channels = channels;
              desc.colorspace = QOI_SRGB;
  
              success = qoi_write(fileName, imgData, &desc);
          }
      }
  #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));
      }
  
      if (allocatedData) RL_FREE(imgData);
  #endif      // SUPPORT_IMAGE_EXPORT
  
      if (success != 0) TRACELOG(LOG_INFO, "FILEIO: [%s] Image exported successfully", fileName);
      else TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to export image", fileName);
  
      return success;
  }
  
  // Export image as code file (.h) defining an array of bytes
  bool ExportImageAsCode(Image image, const char *fileName)
  {
      bool success = false;
  
  #if defined(SUPPORT_IMAGE_EXPORT)
  
  #ifndef TEXT_BYTES_PER_LINE
      #define TEXT_BYTES_PER_LINE     20
  #endif
  
      int dataSize = GetPixelDataSize(image.width, image.height, image.format);
  
      // 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(dataSize*6 + 2000, sizeof(char));
  
      int byteCount = 0;
      byteCount += sprintf(txtData + byteCount, "////////////////////////////////////////////////////////////////////////////////////////\n");
      byteCount += sprintf(txtData + byteCount, "//                                                                                    //\n");
      byteCount += sprintf(txtData + byteCount, "// ImageAsCode exporter v1.0 - Image pixel data exported as an array of bytes         //\n");
      byteCount += sprintf(txtData + byteCount, "//                                                                                    //\n");
      byteCount += sprintf(txtData + byteCount, "// more info and bugs-report:  github.com/raysan5/raylib                              //\n");
      byteCount += sprintf(txtData + byteCount, "// feedback and support:       ray[at]raylib.com                                      //\n");
      byteCount += sprintf(txtData + byteCount, "//                                                                                    //\n");
      byteCount += sprintf(txtData + byteCount, "// Copyright (c) 2018-2022 Ramon Santamaria (@raysan5)                                //\n");
      byteCount += sprintf(txtData + byteCount, "//                                                                                    //\n");
      byteCount += sprintf(txtData + byteCount, "////////////////////////////////////////////////////////////////////////////////////////\n\n");
  
      // 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; }
  
      // Add image information
      byteCount += sprintf(txtData + byteCount, "// Image data information\n");
      byteCount += sprintf(txtData + byteCount, "#define %s_WIDTH    %i\n", varFileName, image.width);
      byteCount += sprintf(txtData + byteCount, "#define %s_HEIGHT   %i\n", varFileName, image.height);
      byteCount += sprintf(txtData + byteCount, "#define %s_FORMAT   %i          // raylib internal pixel format\n\n", varFileName, image.format);
  
      byteCount += sprintf(txtData + byteCount, "static unsigned char %s_DATA[%i] = { ", varFileName, dataSize);
      for (int i = 0; i < dataSize - 1; i++) byteCount += sprintf(txtData + byteCount, ((i%TEXT_BYTES_PER_LINE == 0)? "0x%x,\n" : "0x%x, "), ((unsigned char *)image.data)[i]);
      byteCount += sprintf(txtData + byteCount, "0x%x };\n", ((unsigned char *)image.data)[dataSize - 1]);
  
      // NOTE: Text data size exported is determined by '\0' (NULL) character
      success = SaveFileText(fileName, txtData);
  
      RL_FREE(txtData);
  
  #endif      // SUPPORT_IMAGE_EXPORT
  
      if (success != 0) TRACELOG(LOG_INFO, "FILEIO: [%s] Image as code exported successfully", fileName);
      else TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to export image as code", fileName);
  
      return success;
  }
  
  //------------------------------------------------------------------------------------
  // Image generation functions
  //------------------------------------------------------------------------------------
  // Generate image: plain color
  Image GenImageColor(int width, int height, Color color)
  {
      Color *pixels = (Color *)RL_CALLOC(width*height, sizeof(Color));
  
      for (int i = 0; i < width*height; i++) pixels[i] = color;
  
      Image image = {
          .data = pixels,
          .width = width,
          .height = height,
          .format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8,
          .mipmaps = 1
      };
  
      return image;
  }
  
  #if defined(SUPPORT_IMAGE_GENERATION)
  // Generate image: vertical gradient
  Image GenImageGradientV(int width, int height, Color top, Color bottom)
  {
      Color *pixels = (Color *)RL_MALLOC(width*height*sizeof(Color));
  
      for (int j = 0; j < height; j++)
      {
          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));
          }
      }
  
      Image image = {
          .data = pixels,
          .width = width,
          .height = height,
          .format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8,
          .mipmaps = 1
      };
  
      return image;
  }
  
  // Generate image: horizontal gradient
  Image GenImageGradientH(int width, int height, Color left, Color right)
  {
      Color *pixels = (Color *)RL_MALLOC(width*height*sizeof(Color));
  
      for (int i = 0; i < width; i++)
      {
          float factor = (float)i/(float)width;
          for (int j = 0; j < height; j++)
          {
              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));
          }
      }
  
      Image image = {
          .data = pixels,
          .width = width,
          .height = height,
          .format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8,
          .mipmaps = 1
      };
  
      return image;
  }
  
  // Generate image: radial gradient
  Image GenImageGradientRadial(int width, int height, float density, Color inner, Color outer)
  {
      Color *pixels = (Color *)RL_MALLOC(width*height*sizeof(Color));
      float radius = (width < height)? (float)width/2.0f : (float)height/2.0f;
  
      float centerX = (float)width/2.0f;
      float centerY = (float)height/2.0f;
  
      for (int y = 0; y < height; y++)
      {
          for (int x = 0; x < width; x++)
          {
              float dist = hypotf((float)x - centerX, (float)y - centerY);
              float factor = (dist - radius*density)/(radius*(1.0f - density));
  
              factor = (float)fmax(factor, 0.0f);
              factor = (float)fmin(factor, 1.f); // dist can be bigger than radius so we have to check
  
              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));
          }
      }
  
      Image image = {
          .data = pixels,
          .width = width,
          .height = height,
          .format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8,
          .mipmaps = 1
      };
  
      return image;
  }
  
  // Generate image: checked
  Image GenImageChecked(int width, int height, int checksX, int checksY, Color col1, Color col2)
  {
      Color *pixels = (Color *)RL_MALLOC(width*height*sizeof(Color));
  
      for (int y = 0; y < height; y++)
      {
          for (int x = 0; x < width; x++)
          {
              if ((x/checksX + y/checksY)%2 == 0) pixels[y*width + x] = col1;
              else pixels[y*width + x] = col2;
          }
      }
  
      Image image = {
          .data = pixels,
          .width = width,
          .height = height,
          .format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8,
          .mipmaps = 1
      };
  
      return image;
  }
  
  // Generate image: white noise
  Image GenImageWhiteNoise(int width, int height, float factor)
  {
      Color *pixels = (Color *)RL_MALLOC(width*height*sizeof(Color));
  
      for (int i = 0; i < width*height; i++)
      {
          if (GetRandomValue(0, 99) < (int)(factor*100.0f)) pixels[i] = WHITE;
          else pixels[i] = BLACK;
      }
  
      Image image = {
          .data = pixels,
          .width = width,
          .height = height,
          .format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8,
          .mipmaps = 1
      };
  
      return image;
  }
  
  // Generate image: cellular algorithm. Bigger tileSize means bigger cells
  Image GenImageCellular(int width, int height, int tileSize)
  {
      Color *pixels = (Color *)RL_MALLOC(width*height*sizeof(Color));
  
      int seedsPerRow = width/tileSize;
      int seedsPerCol = height/tileSize;
      int seedCount = seedsPerRow*seedsPerCol;
  
      Vector2 *seeds = (Vector2 *)RL_MALLOC(seedCount*sizeof(Vector2));
  
      for (int i = 0; i < seedCount; i++)
      {
          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 };
      }
  
      for (int y = 0; y < height; y++)
      {
          int tileY = y/tileSize;
  
          for (int x = 0; x < width; x++)
          {
              int tileX = x/tileSize;
  
              float minDistance = 65536.0f; //(float)strtod("Inf", NULL);
  
              // Check all adjacent tiles
              for (int i = -1; i < 2; i++)
              {
                  if ((tileX + i < 0) || (tileX + i >= seedsPerRow)) continue;
  
                  for (int j = -1; j < 2; j++)
                  {
                      if ((tileY + j < 0) || (tileY + j >= seedsPerCol)) continue;
  
                      Vector2 neighborSeed = seeds[(tileY + j)*seedsPerRow + tileX + i];
  
                      float dist = (float)hypot(x - (int)neighborSeed.x, y - (int)neighborSeed.y);
                      minDistance = (float)fmin(minDistance, dist);
                  }
              }
  
              // 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;
  
              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)
  {
      Image newImage = { 0 };
  
      int width = image.width;
      int height = image.height;
      int size = 0;
  
      for (int i = 0; i < image.mipmaps; i++)
      {
          size += GetPixelDataSize(width, height, image.format);
  
          width /= 2;
          height /= 2;
  
          // Security check for NPOT textures
          if (width < 1) width = 1;
          if (height < 1) height = 1;
      }
  
      newImage.data = RL_MALLOC(size);
  
      if (newImage.data != NULL)
      {
          // NOTE: Size must be provided in bytes
          memcpy(newImage.data, image.data, size);
  
          newImage.width = image.width;
          newImage.height = image.height;
          newImage.mipmaps = image.mipmaps;
          newImage.format = image.format;
      }
  
      return newImage;
  }
  
  // Create an image from another image piece
  Image ImageFromImage(Image image, Rectangle rec)
  {
      Image result = { 0 };
  
      int bytesPerPixel = GetPixelDataSize(1, 1, image.format);
  
      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;
  }
  
  // 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;
  
      // 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;
      }
  
      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
      {
          int bytesPerPixel = GetPixelDataSize(1, 1, image->format);
  
          unsigned char *croppedData = (unsigned char *)RL_MALLOC((int)(crop.width*crop.height)*bytesPerPixel);
  
          // 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);
          }
  
          /*
          // 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 x = (int)crop.x; x < (int)(crop.x + crop.width); x++)
              {
                  //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(image->data);
          image->data = croppedData;
          image->width = (int)crop.width;
          image->height = (int)crop.height;
      }
  }
  
  // Convert image data to desired format
  void ImageFormat(Image *image, int newFormat)
  {
      // Security check to avoid program crash
      if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return;
  
      if ((newFormat != 0) && (image->format != newFormat))
      {
          if ((image->format < PIXELFORMAT_COMPRESSED_DXT1_RGB) && (newFormat < PIXELFORMAT_COMPRESSED_DXT1_RGB))
          {
              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;
              image->format = newFormat;
  
              int k = 0;
  
              switch (image->format)
              {
                  case PIXELFORMAT_UNCOMPRESSED_GRAYSCALE:
                  {
                      image->data = (unsigned char *)RL_MALLOC(image->width*image->height*sizeof(unsigned char));
  
                      for (int i = 0; i < image->width*image->height; i++)
                      {
                          ((unsigned char *)image->data)[i] = (unsigned char)((pixels[i].x*0.299f + pixels[i].y*0.587f + pixels[i].z*0.114f)*255.0f);
                      }
  
                  } break;
                  case PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA:
                  {
                      image->data = (unsigned char *)RL_MALLOC(image->width*image->height*2*sizeof(unsigned char));
  
                      for (int i = 0; i < image->width*image->height*2; i += 2, k++)
                      {
                          ((unsigned char *)image->data)[i] = (unsigned char)((pixels[k].x*0.299f + (float)pixels[k].y*0.587f + (float)pixels[k].z*0.114f)*255.0f);
                          ((unsigned char *)image->data)[i + 1] = (unsigned char)(pixels[k].w*255.0f);
                      }
  
                  } break;
                  case PIXELFORMAT_UNCOMPRESSED_R5G6B5:
                  {
                      image->data = (unsigned short *)RL_MALLOC(image->width*image->height*sizeof(unsigned short));
  
                      unsigned char r = 0;
                      unsigned char g = 0;
                      unsigned char b = 0;
  
                      for (int i = 0; i < image->width*image->height; i++)
                      {
                          r = (unsigned char)(round(pixels[i].x*31.0f));
                          g = (unsigned char)(round(pixels[i].y*63.0f));
                          b = (unsigned char)(round(pixels[i].z*31.0f));
  
                          ((unsigned short *)image->data)[i] = (unsigned short)r << 11 | (unsigned short)g << 5 | (unsigned short)b;
                      }
  
                  } break;
                  case PIXELFORMAT_UNCOMPRESSED_R8G8B8:
                  {
                      image->data = (unsigned char *)RL_MALLOC(image->width*image->height*3*sizeof(unsigned char));
  
                      for (int i = 0, k = 0; i < image->width*image->height*3; i += 3, k++)
                      {
                          ((unsigned char *)image->data)[i] = (unsigned char)(pixels[k].x*255.0f);
                          ((unsigned char *)image->data)[i + 1] = (unsigned char)(pixels[k].y*255.0f);
                          ((unsigned char *)image->data)[i + 2] = (unsigned char)(pixels[k].z*255.0f);
                      }
                  } break;
                  case PIXELFORMAT_UNCOMPRESSED_R5G5B5A1:
                  {
                      image->data = (unsigned short *)RL_MALLOC(image->width*image->height*sizeof(unsigned short));
  
                      unsigned char r = 0;
                      unsigned char g = 0;
                      unsigned char b = 0;
                      unsigned char a = 0;
  
                      for (int i = 0; i < image->width*image->height; i++)
                      {
                          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)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 PIXELFORMAT_UNCOMPRESSED_R4G4B4A4:
                  {
                      image->data = (unsigned short *)RL_MALLOC(image->width*image->height*sizeof(unsigned short));
  
                      unsigned char r = 0;
                      unsigned char g = 0;
                      unsigned char b = 0;
                      unsigned char a = 0;
  
                      for (int i = 0; i < image->width*image->height; i++)
                      {
                          r = (unsigned char)(round(pixels[i].x*15.0f));
                          g = (unsigned char)(round(pixels[i].y*15.0f));
                          b = (unsigned char)(round(pixels[i].z*15.0f));
                          a = (unsigned char)(round(pixels[i].w*15.0f));
  
                          ((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:
                  {
                      image->data = (unsigned char *)RL_MALLOC(image->width*image->height*4*sizeof(unsigned char));
  
                      for (int i = 0, k = 0; i < image->width*image->height*4; i += 4, k++)
                      {
                          ((unsigned char *)image->data)[i] = (unsigned char)(pixels[k].x*255.0f);
                          ((unsigned char *)image->data)[i + 1] = (unsigned char)(pixels[k].y*255.0f);
                          ((unsigned char *)image->data)[i + 2] = (unsigned char)(pixels[k].z*255.0f);
                          ((unsigned char *)image->data)[i + 3] = (unsigned char)(pixels[k].w*255.0f);
                      }
                  } break;
                  case PIXELFORMAT_UNCOMPRESSED_R32:
                  {
                      // WARNING: Image is converted to GRAYSCALE eqeuivalent 32bit
  
                      image->data = (float *)RL_MALLOC(image->width*image->height*sizeof(float));
  
                      for (int i = 0; i < image->width*image->height; i++)
                      {
                          ((float *)image->data)[i] = (float)(pixels[i].x*0.299f + pixels[i].y*0.587f + pixels[i].z*0.114f);
                      }
                  } break;
                  case PIXELFORMAT_UNCOMPRESSED_R32G32B32:
                  {
                      image->data = (float *)RL_MALLOC(image->width*image->height*3*sizeof(float));
  
                      for (int i = 0, k = 0; i < image->width*image->height*3; i += 3, k++)
                      {
                          ((float *)image->data)[i] = pixels[k].x;
                          ((float *)image->data)[i + 1] = pixels[k].y;
                          ((float *)image->data)[i + 2] = pixels[k].z;
                      }
                  } break;
                  case PIXELFORMAT_UNCOMPRESSED_R32G32B32A32:
                  {
                      image->data = (float *)RL_MALLOC(image->width*image->height*4*sizeof(float));
  
                      for (int i = 0, k = 0; i < image->width*image->height*4; i += 4, k++)
                      {
                          ((float *)image->data)[i] = pixels[k].x;
                          ((float *)image->data)[i + 1] = pixels[k].y;
                          ((float *)image->data)[i + 2] = pixels[k].z;
                          ((float *)image->data)[i + 3] = pixels[k].w;
                      }
                  } break;
                  default: break;
              }
  
              RL_FREE(pixels);
              pixels = NULL;
  
              // In case original image had mipmaps, generate mipmaps for formated image
              // NOTE: Original mipmaps are replaced by new ones, if custom mipmaps were used, they are lost
              if (image->mipmaps > 1)
              {
                  image->mipmaps = 1;
              #if defined(SUPPORT_IMAGE_MANIPULATION)
                  if (image->data != NULL) ImageMipmaps(image);
              #endif
              }
          }
          else TRACELOG(LOG_WARNING, "IMAGE: Data format is compressed, can not be converted");
      }
  }
  
  // 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)
  {
      Image imText = { 0 };
  #if defined(SUPPORT_MODULE_RTEXT)
      int defaultFontSize = 10;   // Default Font chars height in pixel
      if (fontSize < defaultFontSize) fontSize = defaultFontSize;
      int spacing = fontSize/defaultFontSize;
      imText = ImageTextEx(GetFontDefault(), text, (float)fontSize, (float)spacing, color);   // WARNING: Module required: rtext
  #else
      imText = GenImageColor(200, 60, BLACK);     // Generating placeholder black image rectangle
      TRACELOG(LOG_WARNING, "IMAGE: ImageTextEx() requires module: rtext");
  #endif
      return imText;
  }
  
  // Create an image from text (custom sprite font)
  Image ImageTextEx(Font font, const char *text, float fontSize, float spacing, Color tint)
  {
      Image imText = { 0 };
  #if defined(SUPPORT_MODULE_RTEXT)
      int size = (int)strlen(text);   // Get size in bytes of 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);  // WARNING: Module required: rtext
  
      // Create image to store text
      imText = GenImageColor((int)imSize.x, (int)imSize.y, BLANK);
  
      for (int i = 0; i < size; i++)
      {
          // Get next codepoint from byte string and glyph index in font
          int codepointByteCount = 0;
          int codepoint = GetCodepoint(&text[i], &codepointByteCount);    // WARNING: Module required: rtext
          int index = GetGlyphIndex(font, codepoint);                     // WARNING: Module required: rtext
  
          // 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.glyphs[index].offsetX), (float)(textOffsetY + font.glyphs[index].offsetY), (float)font.recs[index].width, (float)font.recs[index].height };
                  ImageDraw(&imText, font.glyphs[index].image, (Rectangle){ 0, 0, (float)font.glyphs[index].image.width, (float)font.glyphs[index].image.height }, rec, tint);
              }
  
              if (font.glyphs[index].advanceX == 0) textOffsetX += (int)(font.recs[index].width + spacing);
              else textOffsetX += font.glyphs[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
          // WARNING: Module required: rtext
          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));
      }
  #else
      imText = GenImageColor(200, 60, BLACK);     // Generating placeholder black image rectangle
      TRACELOG(LOG_WARNING, "IMAGE: ImageTextEx() requires module: rtext");
  #endif
      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
  void ImageAlphaMask(Image *image, Image alphaMask)
  {
      if ((image->width != alphaMask.width) || (image->height != alphaMask.height))
      {
          TRACELOG(LOG_WARNING, "IMAGE: Alpha mask must be same size as image");
      }
      else if (image->format >= PIXELFORMAT_COMPRESSED_DXT1_RGB)
      {
          TRACELOG(LOG_WARNING, "IMAGE: Alpha mask can not be applied to compressed data formats");
      }
      else
      {
          // Force mask to be Grayscale
          Image mask = ImageCopy(alphaMask);
          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 == PIXELFORMAT_UNCOMPRESSED_GRAYSCALE)
          {
              unsigned char *data = (unsigned char *)RL_MALLOC(image->width*image->height*2);
  
              // Apply alpha mask to alpha channel
              for (int i = 0, k = 0; (i < mask.width*mask.height) || (i < image->width*image->height); i++, k += 2)
              {
                  data[k] = ((unsigned char *)image->data)[i];
                  data[k + 1] = ((unsigned char *)mask.data)[i];
              }
  
              RL_FREE(image->data);
              image->data = data;
              image->format = PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA;
          }
          else
          {
              // Convert image to RGBA
              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)
              {
                  ((unsigned char *)image->data)[k] = ((unsigned char *)mask.data)[i];
              }
          }
  
          UnloadImage(mask);
      }
  }
  
  // Premultiply alpha channel
  void ImageAlphaPremultiply(Image *image)
  {
      // Security check to avoid program crash
      if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return;
  
      float alpha = 0.0f;
      Color *pixels = LoadImageColors(*image);
  
      for (int i = 0; i < image->width*image->height; i++)
      {
          if (pixels[i].a == 0)
          {
              pixels[i].r = 0;
              pixels[i].g = 0;
              pixels[i].b = 0;
          }
          else if (pixels[i].a < 255)
          {
              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);
          }
      }
  
      RL_FREE(image->data);
  
      int format = image->format;
      image->data = pixels;
      image->format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8;
  
      ImageFormat(image, format);
  }
  
  // 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;
  
      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 (fastPath)
      {
          int bytesPerPixel = GetPixelDataSize(1, 1, image->format);
          unsigned char *output = (unsigned char *)RL_MALLOC(newWidth*newHeight*bytesPerPixel);
  
          switch (image->format)
          {
              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(image->data);
          image->data = output;
          image->width = newWidth;
          image->height = newHeight;
      }
      else
      {
          // Get data as Color pixels array to work with it
          Color *pixels = LoadImageColors(*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;
  
          UnloadImageColors(pixels);
          RL_FREE(image->data);
  
          image->data = output;
          image->width = newWidth;
          image->height = newHeight;
          image->format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8;
  
          ImageFormat(image, format);  // Reformat 32bit RGBA image to original format
      }
  }
  
  // Resize and image to new size using Nearest-Neighbor scaling algorithm
  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 = LoadImageColors(*image);
      Color *output = (Color *)RL_MALLOC(newWidth*newHeight*sizeof(Color));
  
      // EDIT: added +1 to account for an early rounding problem
      int xRatio = (int)((image->width << 16)/newWidth) + 1;
      int yRatio = (int)((image->height << 16)/newHeight) + 1;
  
      int x2, y2;
      for (int y = 0; y < newHeight; y++)
      {
          for (int x = 0; x < newWidth; x++)
          {
              x2 = ((x*xRatio) >> 16);
              y2 = ((y*yRatio) >> 16);
  
              output[(y*newWidth) + x] = pixels[(y2*image->width) + x2] ;
          }
      }
  
      int format = image->format;
  
      RL_FREE(image->data);
  
      image->data = output;
      image->width = newWidth;
      image->height = newHeight;
      image->format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8;
  
      ImageFormat(image, format);  // Reformat 32bit RGBA image to original format
  
      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 fill)
  {
      // 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 if ((newWidth != image->width) || (newHeight != image->height))
      {
          Rectangle srcRec = { 0, 0, (float)image->width, (float)image->height };
          Vector2 dstPos = { (float)offsetX, (float)offsetY };
  
          if (offsetX < 0)
          {
              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)
          {
              srcRec.y = (float)-offsetY;
              srcRec.height += (float)offsetY;
              dstPos.y = 0;
          }
          else if ((offsetY + image->height) > newHeight) srcRec.height = (float)(newHeight - offsetY);
  
          if (newWidth < srcRec.width) srcRec.width = (float)newWidth;
          if (newHeight < srcRec.height) srcRec.height = (float)newHeight;
  
          int bytesPerPixel = GetPixelDataSize(1, 1, image->format);
          unsigned char *resizedData = (unsigned char *)RL_CALLOC(newWidth*newHeight*bytesPerPixel, 1);
  
          // TODO: Fill resized canvas with fill color (must be formatted to image->format)
  
          int dstOffsetSize = ((int)dstPos.y*newWidth + (int)dstPos.x)*bytesPerPixel;
  
          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;
      }
  }
  
  // Generate all mipmap levels for a provided image
  // NOTE 1: Supports POT and NPOT images
  // NOTE 2: image.data is scaled to include mipmap levels
  // NOTE 3: Mipmaps format is the same as base image
  void ImageMipmaps(Image *image)
  {
      // Security check to avoid program crash
      if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return;
  
      int mipCount = 1;                   // Required mipmap levels count (including base level)
      int mipWidth = image->width;        // Base image width
      int mipHeight = image->height;      // Base image height
      int mipSize = GetPixelDataSize(mipWidth, mipHeight, image->format);  // Image data size (in bytes)
  
      // Count mipmap levels required
      while ((mipWidth != 1) || (mipHeight != 1))
      {
          if (mipWidth != 1) mipWidth /= 2;
          if (mipHeight != 1) mipHeight /= 2;
  
          // Security check for NPOT textures
          if (mipWidth < 1) mipWidth = 1;
          if (mipHeight < 1) mipHeight = 1;
  
          TRACELOGD("IMAGE: Next mipmap level: %i x %i - current size %i", mipWidth, mipHeight, mipSize);
  
          mipCount++;
          mipSize += GetPixelDataSize(mipWidth, mipHeight, image->format);       // Add mipmap size (in bytes)
      }
  
      if (image->mipmaps < mipCount)
      {
          void *temp = RL_REALLOC(image->data, mipSize);
  
          if (temp != NULL) image->data = temp;      // Assign new pointer (new size) to store mipmaps data
          else TRACELOG(LOG_WARNING, "IMAGE: Mipmaps required memory could not be allocated");
  
          // Pointer to allocated memory point where store next mipmap level data
          unsigned char *nextmip = (unsigned char *)image->data + GetPixelDataSize(image->width, image->height, image->format);
  
          mipWidth = image->width/2;
          mipHeight = image->height/2;
          mipSize = GetPixelDataSize(mipWidth, mipHeight, image->format);
          Image imCopy = ImageCopy(*image);
  
          for (int i = 1; i < mipCount; i++)
          {
              TRACELOGD("IMAGE: Generating mipmap level: %i (%i x %i) - size: %i - offset: 0x%x", i, mipWidth, mipHeight, mipSize, nextmip);
  
              ImageResize(&imCopy, mipWidth, mipHeight);  // Uses internally Mitchell cubic downscale filter
  
              memcpy(nextmip, imCopy.data, mipSize);
              nextmip += mipSize;
              image->mipmaps++;
  
              mipWidth /= 2;
              mipHeight /= 2;
  
              // Security check for NPOT textures
              if (mipWidth < 1) mipWidth = 1;
              if (mipHeight < 1) mipHeight = 1;
  
              mipSize = GetPixelDataSize(mipWidth, mipHeight, image->format);
          }
  
          UnloadImage(imCopy);
      }
      else TRACELOG(LOG_WARNING, "IMAGE: Mipmaps already available");
  }
  
  // Dither image data to 16bpp or lower (Floyd-Steinberg dithering)
  // NOTE: In case selected bpp do not represent an known 16bit format,
  // dithered data is stored in the LSB part of the unsigned short
  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 >= PIXELFORMAT_COMPRESSED_DXT1_RGB)
      {
          TRACELOG(LOG_WARNING, "IMAGE: Compressed data formats can not be dithered");
          return;
      }
  
      if ((rBpp + gBpp + bBpp + aBpp) > 16)
      {
          TRACELOG(LOG_WARNING, "IMAGE: Unsupported dithering bpps (%ibpp), only 16bpp or lower modes supported", (rBpp+gBpp+bBpp+aBpp));
      }
      else
      {
          Color *pixels = LoadImageColors(*image);
  
          RL_FREE(image->data);      // free old image data
  
          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 = 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;
              TRACELOG(LOG_WARNING, "IMAGE: Unsupported dithered OpenGL internal format: %ibpp (R%iG%iB%iA%i)", (rBpp+gBpp+bBpp+aBpp), rBpp, gBpp, bBpp, aBpp);
          }
  
          // NOTE: We will store the dithered data as unsigned short (16bpp)
          image->data = (unsigned short *)RL_MALLOC(image->width*image->height*sizeof(unsigned short));
  
          Color oldPixel = WHITE;
          Color newPixel = WHITE;
  
          int rError, gError, bError;
          unsigned short rPixel, gPixel, bPixel, aPixel;   // Used for 16bit pixel composition
  
          #define MIN(a,b) (((a)<(b))?(a):(b))
  
          for (int y = 0; y < image->height; y++)
          {
              for (int x = 0; x < image->width; x++)
              {
                  oldPixel = pixels[y*image->width + x];
  
                  // NOTE: New pixel obtained by bits truncate, it would be better to round values (check ImageFormat())
                  newPixel.r = oldPixel.r >> (8 - rBpp);     // R bits
                  newPixel.g = oldPixel.g >> (8 - gBpp);     // G bits
                  newPixel.b = oldPixel.b >> (8 - bBpp);     // B bits
                  newPixel.a = oldPixel.a >> (8 - aBpp);     // A bits (not used on dithering)
  
                  // NOTE: Error must be computed between new and old pixel but using same number of bits!
                  // We want to know how much color precision we have lost...
                  rError = (int)oldPixel.r - (int)(newPixel.r << (8 - rBpp));
                  gError = (int)oldPixel.g - (int)(newPixel.g << (8 - gBpp));
                  bError = (int)oldPixel.b - (int)(newPixel.b << (8 - bBpp));
  
                  pixels[y*image->width + x] = newPixel;
  
                  // NOTE: Some cases are out of the array and should be ignored
                  if (x < (image->width - 1))
                  {
                      pixels[y*image->width + x+1].r = MIN((int)pixels[y*image->width + x+1].r + (int)((float)rError*7.0f/16), 0xff);
                      pixels[y*image->width + x+1].g = MIN((int)pixels[y*image->width + x+1].g + (int)((float)gError*7.0f/16), 0xff);
                      pixels[y*image->width + x+1].b = MIN((int)pixels[y*image->width + x+1].b + (int)((float)bError*7.0f/16), 0xff);
                  }
  
                  if ((x > 0) && (y < (image->height - 1)))
                  {
                      pixels[(y+1)*image->width + x-1].r = MIN((int)pixels[(y+1)*image->width + x-1].r + (int)((float)rError*3.0f/16), 0xff);
                      pixels[(y+1)*image->width + x-1].g = MIN((int)pixels[(y+1)*image->width + x-1].g + (int)((float)gError*3.0f/16), 0xff);
                      pixels[(y+1)*image->width + x-1].b = MIN((int)pixels[(y+1)*image->width + x-1].b + (int)((float)bError*3.0f/16), 0xff);
                  }
  
                  if (y < (image->height - 1))
                  {
                      pixels[(y+1)*image->width + x].r = MIN((int)pixels[(y+1)*image->width + x].r + (int)((float)rError*5.0f/16), 0xff);
                      pixels[(y+1)*image->width + x].g = MIN((int)pixels[(y+1)*image->width + x].g + (int)((float)gError*5.0f/16), 0xff);
                      pixels[(y+1)*image->width + x].b = MIN((int)pixels[(y+1)*image->width + x].b + (int)((float)bError*5.0f/16), 0xff);
                  }
  
                  if ((x < (image->width - 1)) && (y < (image->height - 1)))
                  {
                      pixels[(y+1)*image->width + x+1].r = MIN((int)pixels[(y+1)*image->width + x+1].r + (int)((float)rError*1.0f/16), 0xff);
                      pixels[(y+1)*image->width + x+1].g = MIN((int)pixels[(y+1)*image->width + x+1].g + (int)((float)gError*1.0f/16), 0xff);
                      pixels[(y+1)*image->width + x+1].b = MIN((int)pixels[(y+1)*image->width + x+1].b + (int)((float)bError*1.0f/16), 0xff);
                  }
  
                  rPixel = (unsigned short)newPixel.r;
                  gPixel = (unsigned short)newPixel.g;
                  bPixel = (unsigned short)newPixel.b;
                  aPixel = (unsigned short)newPixel.a;
  
                  ((unsigned short *)image->data)[y*image->width + x] = (rPixel << (gBpp + bBpp + aBpp)) | (gPixel << (bBpp + aBpp)) | (bPixel << aBpp) | aPixel;
              }
          }
  
          UnloadImageColors(pixels);
      }
  }
  
  // Flip image vertically
  void ImageFlipVertical(Image *image)
  {
      // 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
      {
          int bytesPerPixel = GetPixelDataSize(1, 1, image->format);
          unsigned char *flippedData = (unsigned char *)RL_MALLOC(image->width*image->height*bytesPerPixel);
  
          for (int i = (image->height - 1), offsetSize = 0; i >= 0; i--)
          {
              memcpy(flippedData + offsetSize, ((unsigned char *)image->data) + i*image->width*bytesPerPixel, image->width*bytesPerPixel);
              offsetSize += image->width*bytesPerPixel;
          }
  
          RL_FREE(image->data);
          image->data = flippedData;
      }
  }
  
  // Flip image horizontally
  void ImageFlipHorizontal(Image *image)
  {
      // 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
      {
          int bytesPerPixel = GetPixelDataSize(1, 1, image->format);
          unsigned char *flippedData = (unsigned char *)RL_MALLOC(image->width*image->height*bytesPerPixel);
  
          for (int y = 0; y < image->height; y++)
          {
              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);
  
                  // 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];
              }
          }
  
          RL_FREE(image->data);
          image->data = flippedData;
  
          /*
          // 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++)
          {
              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;
              }
          }
          */
      }
  }
  
  // Rotate image clockwise 90deg
  void ImageRotateCW(Image *image)
  {
      // 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
      {
          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++)
          {
              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];
              }
          }
  
          RL_FREE(image->data);
          image->data = rotatedData;
          int width = image->width;
          int height = image-> height;
  
          image->width = height;
          image->height = width;
      }
  }
  
  // Rotate image counter-clockwise 90deg
  void ImageRotateCCW(Image *image)
  {
      // 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
      {
          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++)
          {
              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];
              }
          }
  
          RL_FREE(image->data);
          image->data = rotatedData;
          int width = image->width;
          int height = image-> height;
  
          image->width = height;
          image->height = width;
      }
  }
  
  // Modify image color: tint
  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 = LoadImageColors(*image);
  
      float cR = (float)color.r/255;
      float cG = (float)color.g/255;
      float cB = (float)color.b/255;
      float cA = (float)color.a/255;
  
      for (int y = 0; y < image->height; y++)
      {
          for (int x = 0; x < image->width; x++)
          {
              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;
              pixels[y*image->width + x].b = b;
              pixels[y*image->width + x].a = a;
          }
      }
  
      int format = image->format;
      RL_FREE(image->data);
  
      image->data = pixels;
      image->format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8;
  
      ImageFormat(image, format);
  }
  
  // Modify image color: invert
  void ImageColorInvert(Image *image)
  {
      // Security check to avoid program crash
      if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return;
  
      Color *pixels = LoadImageColors(*image);
  
      for (int y = 0; y < image->height; y++)
      {
          for (int x = 0; x < image->width; x++)
          {
              pixels[y*image->width + x].r = 255 - pixels[y*image->width + x].r;
              pixels[y*image->width + x].g = 255 - pixels[y*image->width + x].g;
              pixels[y*image->width + x].b = 255 - pixels[y*image->width + x].b;
          }
      }
  
      int format = image->format;
      RL_FREE(image->data);
  
      image->data = pixels;
      image->format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8;
  
      ImageFormat(image, format);
  }
  
  // Modify image color: grayscale
  void ImageColorGrayscale(Image *image)
  {
      ImageFormat(image, PIXELFORMAT_UNCOMPRESSED_GRAYSCALE);
  }
  
  // Modify image color: contrast
  // NOTE: Contrast values between -100 and 100
  void ImageColorContrast(Image *image, float contrast)
  {
      // Security check to avoid program crash
      if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return;
  
      if (contrast < -100) contrast = -100;
      if (contrast > 100) contrast = 100;
  
      contrast = (100.0f + contrast)/100.0f;
      contrast *= contrast;
  
      Color *pixels = LoadImageColors(*image);
  
      for (int y = 0; y < image->height; y++)
      {
          for (int x = 0; x < image->width; x++)
          {
              float pR = (float)pixels[y*image->width + x].r/255.0f;
              pR -= 0.5;
              pR *= contrast;
              pR += 0.5;
              pR *= 255;
              if (pR < 0) pR = 0;
              if (pR > 255) pR = 255;
  
              float pG = (float)pixels[y*image->width + x].g/255.0f;
              pG -= 0.5;
              pG *= contrast;
              pG += 0.5;
              pG *= 255;
              if (pG < 0) pG = 0;
              if (pG > 255) pG = 255;
  
              float pB = (float)pixels[y*image->width + x].b/255.0f;
              pB -= 0.5;
              pB *= contrast;
              pB += 0.5;
              pB *= 255;
              if (pB < 0) pB = 0;
              if (pB > 255) pB = 255;
  
              pixels[y*image->width + x].r = (unsigned char)pR;
              pixels[y*image->width + x].g = (unsigned char)pG;
              pixels[y*image->width + x].b = (unsigned char)pB;
          }
      }
  
      int format = image->format;
      RL_FREE(image->data);
  
      image->data = pixels;
      image->format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8;
  
      ImageFormat(image, format);
  }
  
  // Modify image color: brightness
  // NOTE: Brightness values between -255 and 255
  void ImageColorBrightness(Image *image, int brightness)
  {
      // Security check to avoid program crash
      if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return;
  
      if (brightness < -255) brightness = -255;
      if (brightness > 255) brightness = 255;
  
      Color *pixels = LoadImageColors(*image);
  
      for (int y = 0; y < image->height; y++)
      {
          for (int x = 0; x < image->width; x++)
          {
              int cR = pixels[y*image->width + x].r + brightness;
              int cG = pixels[y*image->width + x].g + brightness;
              int cB = pixels[y*image->width + x].b + brightness;
  
              if (cR < 0) cR = 1;
              if (cR > 255) cR = 255;
  
              if (cG < 0) cG = 1;
              if (cG > 255) cG = 255;
  
              if (cB < 0) cB = 1;
              if (cB > 255) cB = 255;
  
              pixels[y*image->width + x].r = (unsigned char)cR;
              pixels[y*image->width + x].g = (unsigned char)cG;
              pixels[y*image->width + x].b = (unsigned char)cB;
          }
      }
  
      int format = image->format;
      RL_FREE(image->data);
  
      image->data = pixels;
      image->format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8;
  
      ImageFormat(image, format);
  }
  
  // Modify image color: replace color
  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 = LoadImageColors(*image);
  
      for (int y = 0; y < image->height; y++)
      {
          for (int x = 0; x < image->width; x++)
          {
              if ((pixels[y*image->width + x].r == color.r) &&
                  (pixels[y*image->width + x].g == color.g) &&
                  (pixels[y*image->width + x].b == color.b) &&
                  (pixels[y*image->width + x].a == color.a))
              {
                  pixels[y*image->width + x].r = replace.r;
                  pixels[y*image->width + x].g = replace.g;
                  pixels[y*image->width + x].b = replace.b;
                  pixels[y*image->width + x].a = replace.a;
              }
          }
      }
  
      int format = image->format;
      RL_FREE(image->data);
  
      image->data = pixels;
      image->format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8;
  
      ImageFormat(image, format);
  }
  #endif      // SUPPORT_IMAGE_MANIPULATION
  
  // Load color data from image as a Color array (RGBA - 32bit)
  // NOTE: Memory allocated should be freed using UnloadImageColors();
  Color *LoadImageColors(Image image)
  {
      if ((image.width == 0) || (image.height == 0)) return NULL;
  
      Color *pixels = (Color *)RL_MALLOC(image.width*image.height*sizeof(Color));
  
      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");
  
          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;
  
                  } 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;
  }
  
  // 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 *colorCount)
  {
      #define COLOR_EQUAL(col1, col2) ((col1.r == col2.r)&&(col1.g == col2.g)&&(col1.b == col2.b)&&(col1.a == col2.a))
  
      int palCount = 0;
      Color *palette = NULL;
      Color *pixels = LoadImageColors(image);
  
      if (pixels != NULL)
      {
          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++)
          {
              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);
      }
  
      *colorCount = palCount;
  
      return palette;
  }
  
  // Unload color data loaded with LoadImageColors()
  void UnloadImageColors(Color *colors)
  {
      RL_FREE(colors);
  }
  
  // Unload colors palette loaded with LoadImagePalette()
  void UnloadImagePalette(Color *colors)
  {
      RL_FREE(colors);
  }
  
  // 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;
  }
  
  // Get image pixel color at (x, y) position
  Color GetImageColor(Image image, int x, int y)
  {
      Color color = { 0 };
  
      if ((x >=0) && (x < image.width) && (y >= 0) && (y < image.height))
      {
          switch (image.format)
          {
              case PIXELFORMAT_UNCOMPRESSED_GRAYSCALE:
              {
                  color.r = ((unsigned char *)image.data)[y*image.width + x];
                  color.g = ((unsigned char *)image.data)[y*image.width + x];
                  color.b = ((unsigned char *)image.data)[y*image.width + x];
                  color.a = 255;
  
              } break;
              case PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA:
              {
                  color.r = ((unsigned char *)image.data)[(y*image.width + x)*2];
                  color.g = ((unsigned char *)image.data)[(y*image.width + x)*2];
                  color.b = ((unsigned char *)image.data)[(y*image.width + x)*2];
                  color.a = ((unsigned char *)image.data)[(y*image.width + x)*2 + 1];
  
              } break;
              case PIXELFORMAT_UNCOMPRESSED_R5G5B5A1:
              {
                  unsigned short pixel = ((unsigned short *)image.data)[y*image.width + x];
  
                  color.r = (unsigned char)((float)((pixel & 0b1111100000000000) >> 11)*(255/31));
                  color.g = (unsigned char)((float)((pixel & 0b0000011111000000) >> 6)*(255/31));
                  color.b = (unsigned char)((float)((pixel & 0b0000000000111110) >> 1)*(255/31));
                  color.a = (unsigned char)((pixel & 0b0000000000000001)*255);
  
              } break;
              case PIXELFORMAT_UNCOMPRESSED_R5G6B5:
              {
                  unsigned short pixel = ((unsigned short *)image.data)[y*image.width + x];
  
                  color.r = (unsigned char)((float)((pixel & 0b1111100000000000) >> 11)*(255/31));
                  color.g = (unsigned char)((float)((pixel & 0b0000011111100000) >> 5)*(255/63));
                  color.b = (unsigned char)((float)(pixel & 0b0000000000011111)*(255/31));
                  color.a = 255;
  
              } break;
              case PIXELFORMAT_UNCOMPRESSED_R4G4B4A4:
              {
                  unsigned short pixel = ((unsigned short *)image.data)[y*image.width + x];
  
                  color.r = (unsigned char)((float)((pixel & 0b1111000000000000) >> 12)*(255/15));
                  color.g = (unsigned char)((float)((pixel & 0b0000111100000000) >> 8)*(255/15));
                  color.b = (unsigned char)((float)((pixel & 0b0000000011110000) >> 4)*(255/15));
                  color.a = (unsigned char)((float)(pixel & 0b0000000000001111)*(255/15));
  
              } break;
              case PIXELFORMAT_UNCOMPRESSED_R8G8B8A8:
              {
                  color.r = ((unsigned char *)image.data)[(y*image.width + x)*4];
                  color.g = ((unsigned char *)image.data)[(y*image.width + x)*4 + 1];
                  color.b = ((unsigned char *)image.data)[(y*image.width + x)*4 + 2];
                  color.a = ((unsigned char *)image.data)[(y*image.width + x)*4 + 3];
  
              } break;
              case PIXELFORMAT_UNCOMPRESSED_R8G8B8:
              {
                  color.r = (unsigned char)((unsigned char *)image.data)[(y*image.width + x)*3];
                  color.g = (unsigned char)((unsigned char *)image.data)[(y*image.width + x)*3 + 1];
                  color.b = (unsigned char)((unsigned char *)image.data)[(y*image.width + x)*3 + 2];
                  color.a = 255;
  
              } break;
              case PIXELFORMAT_UNCOMPRESSED_R32:
              {
                  color.r = (unsigned char)(((float *)image.data)[y*image.width + x]*255.0f);
                  color.g = 0;
                  color.b = 0;
                  color.a = 255;
  
              } break;
              case PIXELFORMAT_UNCOMPRESSED_R32G32B32:
              {
                  color.r = (unsigned char)(((float *)image.data)[(y*image.width + x)*3]*255.0f);
                  color.g = (unsigned char)(((float *)image.data)[(y*image.width + x)*3 + 1]*255.0f);
                  color.b = (unsigned char)(((float *)image.data)[(y*image.width + x)*3 + 2]*255.0f);
                  color.a = 255;
  
              } break;
              case PIXELFORMAT_UNCOMPRESSED_R32G32B32A32:
              {
                  color.r = (unsigned char)(((float *)image.data)[(y*image.width + x)*4]*255.0f);
                  color.g = (unsigned char)(((float *)image.data)[(y*image.width + x)*4]*255.0f);
                  color.b = (unsigned char)(((float *)image.data)[(y*image.width + x)*4]*255.0f);
                  color.a = (unsigned char)(((float *)image.data)[(y*image.width + x)*4]*255.0f);
  
              } break;
              default: TRACELOG(LOG_WARNING, "Compressed image format does not support color reading"); break;
          }
      }
      else TRACELOG(LOG_WARNING, "Requested image pixel (%i, %i) out of bounds", x, y);
  
      return color;
  }
  
  //------------------------------------------------------------------------------------
  // Image drawing functions
  //------------------------------------------------------------------------------------
  // Clear image background with given color
  void ImageClearBackground(Image *dst, Color color)
  {
      for (int i = 0; i < dst->width*dst->height; ++i) ImageDrawPixel(dst, i%dst->width, i/dst->width, color);
  }
  
  // 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;
  
      switch (dst->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 *)dst->data)[y*dst->width + x] = 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 *)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;
      }
  }
  
  // Draw pixel within an image (Vector version)
  void ImageDrawPixelV(Image *dst, Vector2 position, Color color)
  {
      ImageDrawPixel(dst, (int)position.x, (int)position.y, color);
  }
  
  // Draw line within an image
  void ImageDrawLine(Image *dst, int startPosX, int startPosY, int endPosX, int endPosY, Color color)
  {
      // Using Bresenham's algorithm as described in
      // Drawing Lines with Pixels - Joshua Scott - March 2012
      // https://classic.csunplugged.org/wp-content/uploads/2014/12/Lines.pdf
  
      int changeInX = (endPosX - startPosX);
      int absChangeInX = (changeInX < 0)? -changeInX : changeInX;
      int changeInY = (endPosY - startPosY);
      int absChangeInY = (changeInY < 0)? -changeInY : changeInY;
  
      int startU, startV, endU, stepV; // Substitutions, either U = X, V = Y or vice versa. See loop at end of function
      //int endV;     // Not needed but left for better understanding, check code below
      int A, B, P;    // See linked paper above, explained down in the main loop
      int reversedXY = (absChangeInY < absChangeInX);
  
      if (reversedXY)
      {
          A = 2*absChangeInY;
          B = A - 2*absChangeInX;
          P = A - absChangeInX;
  
          if (changeInX > 0)
          {
              startU = startPosX;
              startV = startPosY;
              endU = endPosX;
              //endV = endPosY;
          }
          else
          {
              startU = endPosX;
              startV = endPosY;
              endU = startPosX;
              //endV = startPosY;
  
              // Since start and end are reversed
              changeInX = -changeInX;
              changeInY = -changeInY;
          }
  
          stepV = (changeInY < 0)? -1 : 1;
  
          ImageDrawPixel(dst, startU, startV, color);     // At this point they are correctly ordered...
      }
      else
      {
          A = 2*absChangeInX;
          B = A - 2*absChangeInY;
          P = A - absChangeInY;
  
          if (changeInY > 0)
          {
              startU = startPosY;
              startV = startPosX;
              endU = endPosY;
              //endV = endPosX;
          }
          else
          {
              startU = endPosY;
              startV = endPosX;
              endU = startPosY;
              //endV = startPosX;
  
              // Since start and end are reversed
              changeInX = -changeInX;
              changeInY = -changeInY;
          }
  
          stepV = (changeInX < 0)? -1 : 1;
  
          ImageDrawPixel(dst, startV, startU, color);     // ... but need to be reversed here. Repeated in the main loop below
      }
  
      // We already drew the start point. If we started at startU + 0, the line would be crooked and too short
      for (int u = startU + 1, v = startV; u <= endU; u++)
      {
          if (P >= 0)
          {
              v += stepV;     // Adjusts whenever we stray too far from the direct line. Details in the linked paper above
              P += B;         // Remembers that we corrected our path
          }
          else P += A;        // Remembers how far we are from the direct line
  
          if (reversedXY) ImageDrawPixel(dst, u, v, color);
          else ImageDrawPixel(dst, v, u, color);
      }
  }
  
  // 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 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 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);
  }
  
  // Draw rectangle within an image (Vector version)
  void ImageDrawRectangleV(Image *dst, Vector2 position, Vector2 size, Color color)
  {
      ImageDrawRectangle(dst, (int)position.x, (int)position.y, (int)size.x, (int)size.y, color);
  }
  
  // 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++)
      {
          for (int x = sx; x < ex; x++)
          {
              ImageDrawPixel(dst, x, y, color);
          }
      }
  }
  
  // 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);
  }
  
  // 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)
  {
      // 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;
  
      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
      {
          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))
          {
              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 };
  
              srcPtr = &srcMod;
              useSrcMod = true;
          }
  
          // 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;
  
          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(): Get Vector4 instead of Color, easier for ColorAlphaBlend()
          //    [ ] Support f32bit channels drawing
  
          // TODO: Support PIXELFORMAT_UNCOMPRESSED_R32, PIXELFORMAT_UNCOMPRESSED_R32G32B32, PIXELFORMAT_UNCOMPRESSED_R32G32B32A32
  
          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
      }
  }
  
  // Draw text (default font) within an image (destination)
  void ImageDrawText(Image *dst, const char *text, int posX, int posY, int fontSize, Color color)
  {
  #if defined(SUPPORT_MODULE_RTEXT)
      Vector2 position = { (float)posX, (float)posY };
      // NOTE: For default font, spacing is set to desired font size / default font size (10)
      ImageDrawTextEx(dst, GetFontDefault(), text, position, (float)fontSize, (float)fontSize/10, color);   // WARNING: Module required: rtext
  #else
      TRACELOG(LOG_WARNING, "IMAGE: ImageDrawText() requires module: rtext");
  #endif
  }
  
  // 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)
  {
      Image imText = ImageTextEx(font, text, fontSize, spacing, tint);
  
      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);
  }
  
  //------------------------------------------------------------------------------------
  // 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)
      {
          texture = LoadTextureFromImage(image);
          UnloadImage(image);
      }
  
      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.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;
  
      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)
          {
              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; }
          }
  
          cubemap.height = cubemap.width;
      }
  
      // Layout provided or already auto-detected
      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 = ImageCopy(image);       // Image data already follows expected convention
          }
          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)
              {
                  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;
              }
  
              // 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);
  
              // NOTE: Image formating does not work with compressed textures
  
              for (int i = 0; i < 6; i++) ImageDraw(&faces, image, faceRecs[i], (Rectangle){ 0, (float)size*i, (float)size, (float)size }, WHITE);
          }
  
          // NOTE: Cubemap data is expected to be provided as 6 images in a single data array,
          // one after the other (that's a vertical image), following convention: +X, -X, +Y, -Y, +Z, -Z
          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");
  
      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);
  }
  
  //------------------------------------------------------------------------------------
  // Texture configuration functions
  //------------------------------------------------------------------------------------
  // Generate GPU mipmaps for a texture
  void GenTextureMipmaps(Texture2D *texture)
  {
      // NOTE: NPOT textures support check inside function
      // On WebGL (OpenGL ES 2.0) NPOT textures support is limited
      rlGenTextureMipmaps(texture->id, texture->width, texture->height, texture->format, &texture->mipmaps);
  }
  
  // Set texture scaling filter mode
  void SetTextureFilter(Texture2D texture, int filter)
  {
      switch (filter)
      {
          case TEXTURE_FILTER_POINT:
          {
              if (texture.mipmaps > 1)
              {
                  // 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_TEXTURE_FILTER_NEAREST - tex filter: POINT (no filter), no mipmaps
                  rlTextureParameters(texture.id, RL_TEXTURE_MAG_FILTER, RL_TEXTURE_FILTER_NEAREST);
              }
              else
              {
                  // 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 TEXTURE_FILTER_BILINEAR:
          {
              if (texture.mipmaps > 1)
              {
                  // 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_TEXTURE_FILTER_LINEAR - tex filter: BILINEAR, no mipmaps
                  rlTextureParameters(texture.id, RL_TEXTURE_MAG_FILTER, RL_TEXTURE_FILTER_LINEAR);
              }
              else
              {
                  // 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 TEXTURE_FILTER_TRILINEAR:
          {
              if (texture.mipmaps > 1)
              {
                  // 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_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_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 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 wrap)
  {
      switch (wrap)
      {
          case TEXTURE_WRAP_REPEAT:
          {
              // NOTE: It only works if NPOT textures are supported, i.e. OpenGL ES 2.0 could not support it
              rlTextureParameters(texture.id, RL_TEXTURE_WRAP_S, RL_TEXTURE_WRAP_REPEAT);
              rlTextureParameters(texture.id, RL_TEXTURE_WRAP_T, RL_TEXTURE_WRAP_REPEAT);
          } break;
          case TEXTURE_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 TEXTURE_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 TEXTURE_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)
  {
      DrawTextureEx(texture, (Vector2){ (float)posX, (float)posY }, 0.0f, 1.0f, tint);
  }
  
  // Draw a Texture2D with position defined as Vector2
  void DrawTextureV(Texture2D texture, Vector2 position, Color tint)
  {
      DrawTextureEx(texture, position, 0, 1.0f, tint);
  }
  
  // Draw a Texture2D with extended parameters
  void DrawTextureEx(Texture2D texture, Vector2 position, float rotation, float scale, Color tint)
  {
      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, source, dest, origin, rotation, tint);
  }
  
  // Draw a part of a texture (defined by a rectangle)
  void DrawTextureRec(Texture2D texture, Rectangle source, Vector2 position, Color tint)
  {
      Rectangle dest = { position.x, position.y, fabsf(source.width), fabsf(source.height) };
      Vector2 origin = { 0.0f, 0.0f };
  
      DrawTexturePro(texture, source, dest, origin, 0.0f, tint);
  }
  
  // Draw texture quad with tiling and offset parameters
  // NOTE: Tiling and offset should be provided considering normalized texture values [0..1]
  // i.e tiling = { 1.0f, 1.0f } refers to all texture, offset = { 0.5f, 0.5f } moves texture origin to center
  void DrawTextureQuad(Texture2D texture, Vector2 tiling, Vector2 offset, Rectangle quad, Color tint)
  {
      // WARNING: This solution only works if TEXTURE_WRAP_REPEAT is supported,
      // NPOT textures supported is required and OpenGL ES 2.0 could not support it
      Rectangle source = { offset.x*texture.width, offset.y*texture.height, tiling.x*texture.width, tiling.y*texture.height };
      Vector2 origin = { 0.0f, 0.0f };
  
      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!
      if ((source.width == 0) || (source.height == 0)) return;
  
      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 source, Rectangle dest, Vector2 origin, float rotation, Color tint)
  {
      // Check if texture is valid
      if (texture.id > 0)
      {
          float width = (float)texture.width;
          float height = (float)texture.height;
  
          bool flipX = false;
  
          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);
  
              // 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(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);
                  rlColor4ub(tint.r, tint.g, tint.b, tint.a);
                  rlNormal3f(0.0f, 0.0f, 1.0f);                          // Normal vector pointing towards viewer
  
                  // Bottom-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(0.0f, 0.0f);
  
                  // Bottom-right 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(0.0f, dest.height);
  
                  // Top-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(dest.width, dest.height);
  
                  // Top-left 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(dest.width, 0.0f);
              rlEnd();
          rlPopMatrix();
          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 dest, Vector2 origin, float rotation, Color tint)
  {
      if (texture.id > 0)
      {
          float width = (float)texture.width;
          float height = (float)texture.height;
  
          float patchWidth = ((int)dest.width <= 0)? 0.0f : dest.width;
          float patchHeight = ((int)dest.height <= 0)? 0.0f : dest.height;
  
          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;
          float leftBorder = (float)nPatchInfo.left;
          float topBorder = (float)nPatchInfo.top;
          float rightBorder = (float)nPatchInfo.right;
          float bottomBorder = (float)nPatchInfo.bottom;
  
          // Adjust the lateral (left and right) border widths in case patchWidth < texture.width
          if (patchWidth <= (leftBorder + rightBorder) && nPatchInfo.layout != NPATCH_THREE_PATCH_VERTICAL)
          {
              drawCenter = false;
              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.layout != NPATCH_THREE_PATCH_HORIZONTAL)
          {
              drawMiddle = false;
              topBorder = (topBorder/(topBorder + bottomBorder))*patchHeight;
              bottomBorder = patchHeight - topBorder;
          }
  
          Vector2 vertA, vertB, vertC, vertD;
          vertA.x = 0.0f;                             // outer left
          vertA.y = 0.0f;                             // outer top
          vertB.x = leftBorder;                       // inner left
          vertB.y = topBorder;                        // inner top
          vertC.x = patchWidth  - rightBorder;        // inner right
          vertC.y = patchHeight - bottomBorder;       // inner bottom
          vertD.x = patchWidth;                       // outer right
          vertD.y = patchHeight;                      // outer bottom
  
          Vector2 coordA, coordB, coordC, coordD;
          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;
  
          rlSetTexture(texture.id);
  
          rlPushMatrix();
              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
  
                  if (nPatchInfo.layout == NPATCH_NINE_PATCH)
                  {
                      // ------------------------------------------------------------
                      // TOP-LEFT QUAD
                      rlTexCoord2f(coordA.x, coordB.y); rlVertex2f(vertA.x, vertB.y);  // Bottom-left corner for texture and quad
                      rlTexCoord2f(coordB.x, coordB.y); rlVertex2f(vertB.x, vertB.y);  // Bottom-right corner for texture and quad
                      rlTexCoord2f(coordB.x, coordA.y); rlVertex2f(vertB.x, vertA.y);  // Top-right corner for texture and quad
                      rlTexCoord2f(coordA.x, coordA.y); rlVertex2f(vertA.x, vertA.y);  // Top-left corner for texture and quad
                      if (drawCenter)
                      {
                          // TOP-CENTER QUAD
                          rlTexCoord2f(coordB.x, coordB.y); rlVertex2f(vertB.x, vertB.y);  // Bottom-left corner for texture and quad
                          rlTexCoord2f(coordC.x, coordB.y); rlVertex2f(vertC.x, vertB.y);  // Bottom-right corner for texture and quad
                          rlTexCoord2f(coordC.x, coordA.y); rlVertex2f(vertC.x, vertA.y);  // Top-right corner for texture and quad
                          rlTexCoord2f(coordB.x, coordA.y); rlVertex2f(vertB.x, vertA.y);  // Top-left corner for texture and quad
                      }
                      // TOP-RIGHT QUAD
                      rlTexCoord2f(coordC.x, coordB.y); rlVertex2f(vertC.x, vertB.y);  // Bottom-left corner for texture and quad
                      rlTexCoord2f(coordD.x, coordB.y); rlVertex2f(vertD.x, vertB.y);  // Bottom-right corner for texture and quad
                      rlTexCoord2f(coordD.x, coordA.y); rlVertex2f(vertD.x, vertA.y);  // Top-right corner for texture and quad
                      rlTexCoord2f(coordC.x, coordA.y); rlVertex2f(vertC.x, vertA.y);  // Top-left corner for texture and quad
                      if (drawMiddle)
                      {
                          // ------------------------------------------------------------
                          // MIDDLE-LEFT QUAD
                          rlTexCoord2f(coordA.x, coordC.y); rlVertex2f(vertA.x, vertC.y);  // Bottom-left corner for texture and quad
                          rlTexCoord2f(coordB.x, coordC.y); rlVertex2f(vertB.x, vertC.y);  // Bottom-right corner for texture and quad
                          rlTexCoord2f(coordB.x, coordB.y); rlVertex2f(vertB.x, vertB.y);  // Top-right corner for texture and quad
                          rlTexCoord2f(coordA.x, coordB.y); rlVertex2f(vertA.x, vertB.y);  // Top-left corner for texture and quad
                          if (drawCenter)
                          {
                              // MIDDLE-CENTER QUAD
                              rlTexCoord2f(coordB.x, coordC.y); rlVertex2f(vertB.x, vertC.y);  // Bottom-left corner for texture and quad
                              rlTexCoord2f(coordC.x, coordC.y); rlVertex2f(vertC.x, vertC.y);  // Bottom-right corner for texture and quad
                              rlTexCoord2f(coordC.x, coordB.y); rlVertex2f(vertC.x, vertB.y);  // Top-right corner for texture and quad
                              rlTexCoord2f(coordB.x, coordB.y); rlVertex2f(vertB.x, vertB.y);  // Top-left corner for texture and quad
                          }
  
                          // MIDDLE-RIGHT QUAD
                          rlTexCoord2f(coordC.x, coordC.y); rlVertex2f(vertC.x, vertC.y);  // Bottom-left corner for texture and quad
                          rlTexCoord2f(coordD.x, coordC.y); rlVertex2f(vertD.x, vertC.y);  // Bottom-right corner for texture and quad
                          rlTexCoord2f(coordD.x, coordB.y); rlVertex2f(vertD.x, vertB.y);  // Top-right corner for texture and quad
                          rlTexCoord2f(coordC.x, coordB.y); rlVertex2f(vertC.x, vertB.y);  // Top-left corner for texture and quad
                      }
  
                      // ------------------------------------------------------------
                      // BOTTOM-LEFT QUAD
                      rlTexCoord2f(coordA.x, coordD.y); rlVertex2f(vertA.x, vertD.y);  // Bottom-left corner for texture and quad
                      rlTexCoord2f(coordB.x, coordD.y); rlVertex2f(vertB.x, vertD.y);  // Bottom-right corner for texture and quad
                      rlTexCoord2f(coordB.x, coordC.y); rlVertex2f(vertB.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
                      if (drawCenter)
                      {
                          // BOTTOM-CENTER QUAD
                          rlTexCoord2f(coordB.x, coordD.y); rlVertex2f(vertB.x, vertD.y);  // Bottom-left corner for texture and quad
                          rlTexCoord2f(coordC.x, coordD.y); rlVertex2f(vertC.x, vertD.y);  // Bottom-right corner for texture and quad
                          rlTexCoord2f(coordC.x, coordC.y); rlVertex2f(vertC.x, vertC.y);  // Top-right corner for texture and quad
                          rlTexCoord2f(coordB.x, coordC.y); rlVertex2f(vertB.x, vertC.y);  // Top-left corner for texture and quad
                      }
  
                      // BOTTOM-RIGHT QUAD
                      rlTexCoord2f(coordC.x, coordD.y); rlVertex2f(vertC.x, vertD.y);  // Bottom-left corner for texture and quad
                      rlTexCoord2f(coordD.x, coordD.y); rlVertex2f(vertD.x, vertD.y);  // Bottom-right corner for texture and quad
                      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.layout == NPATCH_THREE_PATCH_VERTICAL)
                  {
                      // TOP QUAD
                      // -----------------------------------------------------------
                      // Texture coords                 Vertices
                      rlTexCoord2f(coordA.x, coordB.y); rlVertex2f(vertA.x, vertB.y);  // Bottom-left corner for texture and quad
                      rlTexCoord2f(coordD.x, coordB.y); rlVertex2f(vertD.x, vertB.y);  // Bottom-right corner for texture and quad
                      rlTexCoord2f(coordD.x, coordA.y); rlVertex2f(vertD.x, vertA.y);  // Top-right corner for texture and quad
                      rlTexCoord2f(coordA.x, coordA.y); rlVertex2f(vertA.x, vertA.y);  // Top-left corner for texture and quad
                      if (drawCenter)
                      {
                          // MIDDLE QUAD
                          // -----------------------------------------------------------
                          // Texture coords                 Vertices
                          rlTexCoord2f(coordA.x, coordC.y); rlVertex2f(vertA.x, vertC.y);  // Bottom-left corner for texture and quad
                          rlTexCoord2f(coordD.x, coordC.y); rlVertex2f(vertD.x, vertC.y);  // Bottom-right corner for texture and quad
                          rlTexCoord2f(coordD.x, coordB.y); rlVertex2f(vertD.x, vertB.y);  // Top-right corner for texture and quad
                          rlTexCoord2f(coordA.x, coordB.y); rlVertex2f(vertA.x, vertB.y);  // Top-left corner for texture and quad
                      }
                      // BOTTOM QUAD
                      // -----------------------------------------------------------
                      // Texture coords                 Vertices
                      rlTexCoord2f(coordA.x, coordD.y); rlVertex2f(vertA.x, vertD.y);  // Bottom-left corner for texture and quad
                      rlTexCoord2f(coordD.x, coordD.y); rlVertex2f(vertD.x, vertD.y);  // Bottom-right corner for texture and quad
                      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.layout == NPATCH_THREE_PATCH_HORIZONTAL)
                  {
                      // LEFT QUAD
                      // -----------------------------------------------------------
                      // Texture coords                 Vertices
                      rlTexCoord2f(coordA.x, coordD.y); rlVertex2f(vertA.x, vertD.y);  // Bottom-left corner for texture and quad
                      rlTexCoord2f(coordB.x, coordD.y); rlVertex2f(vertB.x, vertD.y);  // Bottom-right corner for texture and quad
                      rlTexCoord2f(coordB.x, coordA.y); rlVertex2f(vertB.x, vertA.y);  // Top-right corner for texture and quad
                      rlTexCoord2f(coordA.x, coordA.y); rlVertex2f(vertA.x, vertA.y);  // Top-left corner for texture and quad
                      if (drawCenter)
                      {
                          // CENTER QUAD
                          // -----------------------------------------------------------
                          // Texture coords                 Vertices
                          rlTexCoord2f(coordB.x, coordD.y); rlVertex2f(vertB.x, vertD.y);  // Bottom-left corner for texture and quad
                          rlTexCoord2f(coordC.x, coordD.y); rlVertex2f(vertC.x, vertD.y);  // Bottom-right corner for texture and quad
                          rlTexCoord2f(coordC.x, coordA.y); rlVertex2f(vertC.x, vertA.y);  // Top-right corner for texture and quad
                          rlTexCoord2f(coordB.x, coordA.y); rlVertex2f(vertB.x, vertA.y);  // Top-left corner for texture and quad
                      }
                      // RIGHT QUAD
                      // -----------------------------------------------------------
                      // Texture coords                 Vertices
                      rlTexCoord2f(coordC.x, coordD.y); rlVertex2f(vertC.x, vertD.y);  // Bottom-left corner for texture and quad
                      rlTexCoord2f(coordD.x, coordD.y); rlVertex2f(vertD.x, vertD.y);  // Bottom-right corner for texture and quad
                      rlTexCoord2f(coordD.x, coordA.y); rlVertex2f(vertD.x, vertA.y);  // Top-right corner for texture and quad
                      rlTexCoord2f(coordC.x, coordA.y); rlVertex2f(vertC.x, vertA.y);  // Top-left corner for texture and quad
                  }
              rlEnd();
          rlPopMatrix();
  
          rlSetTexture(0);
      }
  }
  
  // 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 pointCount, Color tint)
  {
      rlCheckRenderBatchLimit((pointCount - 1)*4);
  
      rlSetTexture(texture.id);
  
      // Texturing is only supported on QUADs
      rlBegin(RL_QUADS);
  
          rlColor4ub(tint.r, tint.g, tint.b, tint.a);
  
          for (int i = 0; i < pointCount - 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);
  }
  
  // Get 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)};
  }
  
  // Get 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);
  }
  
  // Get 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;
  }
  
  // Get 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;
  }
  
  // Get 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)
      {
          hsv.y = 0.0f;
          hsv.x = 0.0f;       // Undefined, maybe NAN?
          return hsv;
      }
  
      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;
      }
  
      // 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
      }
  
      hsv.x *= 60.0f;     // Convert to degrees
  
      if (hsv.x < 0.0f) hsv.x += 360.0f;
  
      return hsv;
  }
  
  // Get 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;
  }
  
  // Get 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)};
  }
  
  // Get 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;
  }
  
  // Get a Color struct from hexadecimal value
  Color GetColor(unsigned 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 color = { 0 };
  
      switch (format)
      {
          case PIXELFORMAT_UNCOMPRESSED_GRAYSCALE: color = (Color){ ((unsigned char *)srcPtr)[0], ((unsigned char *)srcPtr)[0], ((unsigned char *)srcPtr)[0], 255 }; break;
          case PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA: color = (Color){ ((unsigned char *)srcPtr)[0], ((unsigned char *)srcPtr)[0], ((unsigned char *)srcPtr)[0], ((unsigned char *)srcPtr)[1] }; break;
          case PIXELFORMAT_UNCOMPRESSED_R5G6B5:
          {
              color.r = (unsigned char)((((unsigned short *)srcPtr)[0] >> 11)*255/31);
              color.g = (unsigned char)(((((unsigned short *)srcPtr)[0] >> 5) & 0b0000000000111111)*255/63);
              color.b = (unsigned char)((((unsigned short *)srcPtr)[0] & 0b0000000000011111)*255/31);
              color.a = 255;
  
          } break;
          case PIXELFORMAT_UNCOMPRESSED_R5G5B5A1:
          {
              color.r = (unsigned char)((((unsigned short *)srcPtr)[0] >> 11)*255/31);
              color.g = (unsigned char)(((((unsigned short *)srcPtr)[0] >> 6) & 0b0000000000011111)*255/31);
              color.b = (unsigned char)((((unsigned short *)srcPtr)[0] & 0b0000000000011111)*255/31);
              color.a = (((unsigned short *)srcPtr)[0] & 0b0000000000000001)? 255 : 0;
  
          } break;
          case PIXELFORMAT_UNCOMPRESSED_R4G4B4A4:
          {
              color.r = (unsigned char)((((unsigned short *)srcPtr)[0] >> 12)*255/15);
              color.g = (unsigned char)(((((unsigned short *)srcPtr)[0] >> 8) & 0b0000000000001111)*255/15);
              color.b = (unsigned char)(((((unsigned short *)srcPtr)[0] >> 4) & 0b0000000000001111)*255/15);
              color.a = (unsigned char)((((unsigned short *)srcPtr)[0] & 0b0000000000001111)*255/15);
  
          } break;
          case PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: color = (Color){ ((unsigned char *)srcPtr)[0], ((unsigned char *)srcPtr)[1], ((unsigned char *)srcPtr)[2], ((unsigned char *)srcPtr)[3] }; break;
          case PIXELFORMAT_UNCOMPRESSED_R8G8B8: color = (Color){ ((unsigned char *)srcPtr)[0], ((unsigned char *)srcPtr)[1], ((unsigned char *)srcPtr)[2], 255 }; break;
          case PIXELFORMAT_UNCOMPRESSED_R32:
          {
              // NOTE: Pixel normalized float value is converted to [0..255]
              color.r = (unsigned char)(((float *)srcPtr)[0]*255.0f);
              color.g = (unsigned char)(((float *)srcPtr)[0]*255.0f);
              color.b = (unsigned char)(((float *)srcPtr)[0]*255.0f);
              color.a = 255;
  
          } break;
          case PIXELFORMAT_UNCOMPRESSED_R32G32B32:
          {
              // NOTE: Pixel normalized float value is converted to [0..255]
              color.r = (unsigned char)(((float *)srcPtr)[0]*255.0f);
              color.g = (unsigned char)(((float *)srcPtr)[1]*255.0f);
              color.b = (unsigned char)(((float *)srcPtr)[2]*255.0f);
              color.a = 255;
  
          } break;
          case PIXELFORMAT_UNCOMPRESSED_R32G32B32A32:
          {
              // NOTE: Pixel normalized float value is converted to [0..255]
              color.r = (unsigned char)(((float *)srcPtr)[0]*255.0f);
              color.g = (unsigned char)(((float *)srcPtr)[1]*255.0f);
              color.b = (unsigned char)(((float *)srcPtr)[2]*255.0f);
              color.a = (unsigned char)(((float *)srcPtr)[3]*255.0f);
  
          } break;
          default: break;
      }
  
      return color;
  }
  
  // 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 unsigned char *fileData, unsigned int fileSize)
  {
      unsigned char *fileDataPtr = (unsigned char *)fileData;
  
      // Required extension:
      // GL_EXT_texture_compression_s3tc
  
      // Supported tokens (defined by extensions)
      // GL_COMPRESSED_RGB_S3TC_DXT1_EXT      0x83F0
      // GL_COMPRESSED_RGBA_S3TC_DXT1_EXT     0x83F1
      // GL_COMPRESSED_RGBA_S3TC_DXT3_EXT     0x83F2
      // GL_COMPRESSED_RGBA_S3TC_DXT5_EXT     0x83F3
  
      #define FOURCC_DXT1 0x31545844  // Equivalent to "DXT1" in ASCII
      #define FOURCC_DXT3 0x33545844  // Equivalent to "DXT3" in ASCII
      #define FOURCC_DXT5 0x35545844  // Equivalent to "DXT5" in ASCII
  
      // DDS Pixel Format
      typedef struct {
          unsigned int size;
          unsigned int flags;
          unsigned int fourCC;
          unsigned int rgbBitCount;
          unsigned int rBitMask;
          unsigned int gBitMask;
          unsigned int bBitMask;
          unsigned int aBitMask;
      } DDSPixelFormat;
  
      // DDS Header (124 bytes)
      typedef struct {
          unsigned int size;
          unsigned int flags;
          unsigned int height;
          unsigned int width;
          unsigned int pitchOrLinearSize;
          unsigned int depth;
          unsigned int mipmapCount;
          unsigned int reserved1[11];
          DDSPixelFormat ddspf;
          unsigned int caps;
          unsigned int caps2;
          unsigned int caps3;
          unsigned int caps4;
          unsigned int reserved2;
      } DDSHeader;
  
      Image image = { 0 };
  
      if (fileDataPtr != NULL)
      {
          // Verify the type of file
          unsigned char *ddsHeaderId = fileDataPtr;
          fileDataPtr += 4;
  
          if ((ddsHeaderId[0] != 'D') || (ddsHeaderId[1] != 'D') || (ddsHeaderId[2] != 'S') || (ddsHeaderId[3] != ' '))
          {
              TRACELOG(LOG_WARNING, "IMAGE: DDS file data not valid");
          }
          else
          {
              DDSHeader *ddsHeader = (DDSHeader *)fileDataPtr;
  
              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);
  
              fileDataPtr += sizeof(DDSHeader);   // Skip header
  
              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->ddspf.rgbBitCount == 16)     // 16bit mode, no compressed
              {
                  if (ddsHeader->ddspf.flags == 0x40)         // no alpha channel
                  {
                      int dataSize = image.width*image.height*sizeof(unsigned short);
                      image.data = (unsigned short *)RL_MALLOC(dataSize);
  
                      memcpy(image.data, fileDataPtr, dataSize);
  
                      image.format = PIXELFORMAT_UNCOMPRESSED_R5G6B5;
                  }
                  else if (ddsHeader->ddspf.flags == 0x41)        // with alpha channel
                  {
                      if (ddsHeader->ddspf.aBitMask == 0x8000)    // 1bit alpha
                      {
                          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;
  
                          // NOTE: Data comes as A1R5G5B5, it must be reordered to R5G5B5A1
                          for (int i = 0; i < image.width*image.height; i++)
                          {
                              alpha = ((unsigned short *)image.data)[i] >> 15;
                              ((unsigned short *)image.data)[i] = ((unsigned short *)image.data)[i] << 1;
                              ((unsigned short *)image.data)[i] += alpha;
                          }
  
                          image.format = PIXELFORMAT_UNCOMPRESSED_R5G5B5A1;
                      }
                      else if (ddsHeader->ddspf.aBitMask == 0xf000)   // 4bit alpha
                      {
                          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;
  
                          // NOTE: Data comes as A4R4G4B4, it must be reordered R4G4B4A4
                          for (int i = 0; i < image.width*image.height; i++)
                          {
                              alpha = ((unsigned short *)image.data)[i] >> 12;
                              ((unsigned short *)image.data)[i] = ((unsigned short *)image.data)[i] << 4;
                              ((unsigned short *)image.data)[i] += alpha;
                          }
  
                          image.format = PIXELFORMAT_UNCOMPRESSED_R4G4B4A4;
                      }
                  }
              }
              else if (ddsHeader->ddspf.flags == 0x40 && ddsHeader->ddspf.rgbBitCount == 24)   // DDS_RGB, no compressed
              {
                  int dataSize = image.width*image.height*3*sizeof(unsigned char);
                  image.data = (unsigned short *)RL_MALLOC(dataSize);
  
                  memcpy(image.data, fileDataPtr, dataSize);
  
                  image.format = PIXELFORMAT_UNCOMPRESSED_R8G8B8;
              }
              else if (ddsHeader->ddspf.flags == 0x41 && ddsHeader->ddspf.rgbBitCount == 32) // DDS_RGBA, no compressed
              {
                  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;
  
                  // NOTE: Data comes as A8R8G8B8, it must be reordered R8G8B8A8 (view next comment)
                  // DirecX understand ARGB as a 32bit DWORD but the actual memory byte alignment is BGRA
                  // So, we must realign B8G8R8A8 to R8G8B8A8
                  for (int i = 0; i < image.width*image.height*4; i += 4)
                  {
                      blue = ((unsigned char *)image.data)[i];
                      ((unsigned char *)image.data)[i] = ((unsigned char *)image.data)[i + 2];
                      ((unsigned char *)image.data)[i + 2] = blue;
                  }
  
                  image.format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8;
              }
              else if (((ddsHeader->ddspf.flags == 0x04) || (ddsHeader->ddspf.flags == 0x05)) && (ddsHeader->ddspf.fourCC > 0)) // Compressed
              {
                  int dataSize = 0;
  
                  // Calculate data size, including all mipmaps
                  if (ddsHeader->mipmapCount > 1) dataSize = ddsHeader->pitchOrLinearSize*2;
                  else dataSize = ddsHeader->pitchOrLinearSize;
  
                  image.data = (unsigned char *)RL_MALLOC(dataSize*sizeof(unsigned char));
  
                  memcpy(image.data, fileDataPtr, dataSize);
  
                  switch (ddsHeader->ddspf.fourCC)
                  {
                      case FOURCC_DXT1:
                      {
                          if (ddsHeader->ddspf.flags == 0x04) image.format = PIXELFORMAT_COMPRESSED_DXT1_RGB;
                          else image.format = PIXELFORMAT_COMPRESSED_DXT1_RGBA;
                      } break;
                      case FOURCC_DXT3: image.format = PIXELFORMAT_COMPRESSED_DXT3_RGBA; break;
                      case FOURCC_DXT5: image.format = PIXELFORMAT_COMPRESSED_DXT5_RGBA; break;
                      default: break;
                  }
              }
          }
      }
  
      return image;
  }
  #endif
  
  #if defined(SUPPORT_FILEFORMAT_PKM)
  // 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 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)
  
      // Supported tokens (defined by extensions)
      // GL_ETC1_RGB8_OES                 0x8D64
      // GL_COMPRESSED_RGB8_ETC2          0x9274
      // GL_COMPRESSED_RGBA8_ETC2_EAC     0x9278
  
      // PKM file (ETC1) Header (16 bytes)
      typedef struct {
          char id[4];                 // "PKM "
          char version[2];            // "10" or "20"
          unsigned short format;      // Data format (big-endian) (Check list below)
          unsigned short width;       // Texture width (big-endian) (origWidth rounded to multiple of 4)
          unsigned short height;      // Texture height (big-endian) (origHeight rounded to multiple of 4)
          unsigned short origWidth;   // Original width (big-endian)
          unsigned short origHeight;  // Original height (big-endian)
      } PKMHeader;
  
      // Formats list
      // version 10: format: 0=ETC1_RGB, [1=ETC1_RGBA, 2=ETC1_RGB_MIP, 3=ETC1_RGBA_MIP] (not used)
      // version 20: format: 0=ETC1_RGB, 1=ETC2_RGB, 2=ETC2_RGBA_OLD, 3=ETC2_RGBA, 4=ETC2_RGBA1, 5=ETC2_R, 6=ETC2_RG, 7=ETC2_SIGNED_R, 8=ETC2_SIGNED_R
  
      // NOTE: The extended width and height are the widths rounded up to a multiple of 4.
      // NOTE: ETC is always 4bit per pixel (64 bit for each 4x4 block of pixels)
  
      Image image = { 0 };
  
      if (fileDataPtr != NULL)
      {
          PKMHeader *pkmHeader = (PKMHeader *)fileDataPtr;
  
          if ((pkmHeader->id[0] != 'P') || (pkmHeader->id[1] != 'K') || (pkmHeader->id[2] != 'M') || (pkmHeader->id[3] != ' '))
          {
              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);
  
              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.mipmaps = 1;
  
              int bpp = 4;
              if (pkmHeader->format == 3) bpp = 8;
  
              int dataSize = image.width*image.height*bpp/8;  // Total data size in bytes
  
              image.data = (unsigned char *)RL_MALLOC(dataSize*sizeof(unsigned char));
  
              memcpy(image.data, fileDataPtr, dataSize);
  
              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;
          }
      }
  
      return image;
  }
  #endif
  
  #if defined(SUPPORT_FILEFORMAT_KTX)
  // Load KTX compressed image data (ETC1/ETC2 compression)
  // TODO: Review KTX loading, many things changed!
  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)
  
      // Supported tokens (defined by extensions)
      // GL_ETC1_RGB8_OES                 0x8D64
      // GL_COMPRESSED_RGB8_ETC2          0x9274
      // GL_COMPRESSED_RGBA8_ETC2_EAC     0x9278
  
      // KTX file Header (64 bytes)
      // v1.1 - https://www.khronos.org/opengles/sdk/tools/KTX/file_format_spec/
      // v2.0 - http://github.khronos.org/KTX-Specification/
  
      // TODO: Support KTX 2.2 specs!
  
      typedef struct {
          char id[12];                        // Identifier: "«KTX 11»\r\n\x1A\n"
          unsigned int endianness;            // Little endian: 0x01 0x02 0x03 0x04
          unsigned int glType;                // For compressed textures, glType must equal 0
          unsigned int glTypeSize;            // For compressed texture data, usually 1
          unsigned int glFormat;              // For compressed textures is 0
          unsigned int glInternalFormat;      // Compressed internal format
          unsigned int glBaseInternalFormat;  // Same as glFormat (RGB, RGBA, ALPHA...)
          unsigned int width;                 // Texture image width in pixels
          unsigned int height;                // Texture image height in pixels
          unsigned int depth;                 // For 2D textures is 0
          unsigned int elements;              // Number of array elements, usually 0
          unsigned int faces;                 // Cubemap faces, for no-cubemap = 1
          unsigned int mipmapLevels;          // Non-mipmapped textures = 1
          unsigned int keyValueDataSize;      // Used to encode any arbitrary data...
      } KTXHeader;
  
      // NOTE: Before start of every mipmap data block, we have: unsigned int dataSize
  
      Image image = { 0 };
  
      if (fileDataPtr != NULL)
      {
          KTXHeader *ktxHeader = (KTXHeader *)fileDataPtr;
  
          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: KTX file data not valid");
          }
          else
          {
              fileDataPtr += sizeof(KTXHeader);           // Move file data pointer
  
              image.width = ktxHeader->width;
              image.height = ktxHeader->height;
              image.mipmaps = ktxHeader->mipmapLevels;
  
              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);
  
              fileDataPtr += ktxHeader->keyValueDataSize; // Skip value data size
  
              int dataSize = ((int *)fileDataPtr)[0];
              fileDataPtr += sizeof(int);
  
              image.data = (unsigned char *)RL_MALLOC(dataSize*sizeof(unsigned char));
  
              memcpy(image.data, fileDataPtr, dataSize);
  
              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;
  
              // TODO: Support uncompressed data formats? Right now it returns format = 0!
          }
      }
  
      return image;
  }
  
  // Save image data as KTX file
  // NOTE: By default KTX 1.1 spec is used, 2.0 is still on draft (01Oct2018)
  // TODO: Review KTX saving, many things changed!
  static int SaveKTX(Image image, const char *fileName)
  {
      // 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/ - Final specs by 2021-04-18
      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
          unsigned int glType;                // For compressed textures, glType must equal 0
          unsigned int glTypeSize;            // For compressed texture data, usually 1
          unsigned int glFormat;              // For compressed textures is 0
          unsigned int glInternalFormat;      // Compressed internal format
          unsigned int glBaseInternalFormat;  // Same as glFormat (RGB, RGBA, ALPHA...)       // KTX 2.0: UInt32 vkFormat
          unsigned int width;                 // Texture image width in pixels
          unsigned int height;                // Texture image height in pixels
          unsigned int depth;                 // For 2D textures is 0
          unsigned int elements;              // Number of array elements, usually 0
          unsigned int faces;                 // Cubemap faces, for no-cubemap = 1
          unsigned int mipmapLevels;          // Non-mipmapped textures = 1
          unsigned int keyValueDataSize;      // Used to encode any arbitrary data...         // KTX 2.0: UInt32 levelOrder - ordering of the mipmap levels, usually 0
                                                                                              // KTX 2.0: UInt32 supercompressionScheme - 0 (None), 1 (Crunch CRN), 2 (Zlib DEFLATE)...
          // KTX 2.0 defines additional header elements...
      } KTXHeader;
  
      // Calculate file dataSize required
      int dataSize = sizeof(KTXHeader);
  
      for (int i = 0, width = image.width, height = image.height; i < image.mipmaps; i++)
      {
          dataSize += GetPixelDataSize(width, height, image.format);
          width /= 2; height /= 2;
      }
  
      unsigned char *fileData = RL_CALLOC(dataSize, 1);
      unsigned char *fileDataPtr = fileData;
  
      KTXHeader ktxHeader = { 0 };
  
      // KTX identifier (v1.1)
      //unsigned char id[12] = { '«', 'K', 'T', 'X', ' ', '1', '1', '»', '\r', '\n', '\x1A', '\n' };
      //unsigned char id[12] = { 0xAB, 0x4B, 0x54, 0x58, 0x20, 0x31, 0x31, 0xBB, 0x0D, 0x0A, 0x1A, 0x0A };
  
      const char ktxIdentifier[12] = { 0xAB, 'K', 'T', 'X', ' ', '1', '1', 0xBB, '\r', '\n', 0x1A, '\n' };
  
      // Get the image header
      memcpy(ktxHeader.id, ktxIdentifier, 12);  // KTX 1.1 signature
      ktxHeader.endianness = 0;
      ktxHeader.glType = 0;                     // Obtained from image.format
      ktxHeader.glTypeSize = 1;
      ktxHeader.glFormat = 0;                   // Obtained from image.format
      ktxHeader.glInternalFormat = 0;           // Obtained from image.format
      ktxHeader.glBaseInternalFormat = 0;
      ktxHeader.width = image.width;
      ktxHeader.height = image.height;
      ktxHeader.depth = 0;
      ktxHeader.elements = 0;
      ktxHeader.faces = 1;
      ktxHeader.mipmapLevels = image.mipmaps;   // If it was 0, it means mipmaps should be generated on loading (not for compressed formats)
      ktxHeader.keyValueDataSize = 0;           // No extra data after the header
  
      rlGetGlTextureFormats(image.format, &ktxHeader.glInternalFormat, &ktxHeader.glFormat, &ktxHeader.glType);   // rlgl module function
      ktxHeader.glBaseInternalFormat = ktxHeader.glFormat;    // KTX 1.1 only
  
      // NOTE: We can save into a .ktx all PixelFormats supported by raylib, including compressed formats like DXT, ETC or ASTC
  
      if (ktxHeader.glFormat == -1) TRACELOG(LOG_WARNING, "IMAGE: GL format not supported for KTX export (%i)", ktxHeader.glFormat);
      else
      {
          memcpy(fileDataPtr, &ktxHeader, sizeof(KTXHeader));
          fileDataPtr += sizeof(KTXHeader);
  
          int width = image.width;
          int height = image.height;
          int dataOffset = 0;
  
          // Save all mipmaps data
          for (int i = 0; i < image.mipmaps; i++)
          {
              unsigned int dataSize = GetPixelDataSize(width, height, image.format);
  
              memcpy(fileDataPtr, &dataSize, sizeof(unsigned int));
              memcpy(fileDataPtr + 4, (unsigned char *)image.data + dataOffset, dataSize);
  
              width /= 2;
              height /= 2;
              dataOffset += dataSize;
              fileDataPtr += (4 + dataSize);
          }
      }
  
      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;
  }
  #endif
  
  #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 unsigned char *fileData, unsigned int fileSize)
  {
      unsigned char *fileDataPtr = (unsigned char *)fileData;
  
      // Required extension:
      // GL_IMG_texture_compression_pvrtc
  
      // Supported tokens (defined by extensions)
      // GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG       0x8C00
      // GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG      0x8C02
  
  #if 0   // Not used...
      // PVR file v2 Header (52 bytes)
      typedef struct {
          unsigned int headerLength;
          unsigned int height;
          unsigned int width;
          unsigned int numMipmaps;
          unsigned int flags;
          unsigned int dataLength;
          unsigned int bpp;
          unsigned int bitmaskRed;
          unsigned int bitmaskGreen;
          unsigned int bitmaskBlue;
          unsigned int bitmaskAlpha;
          unsigned int pvrTag;
          unsigned int numSurfs;
      } PVRHeaderV2;
  #endif
  
      // PVR file v3 Header (52 bytes)
      // NOTE: After it could be metadata (15 bytes?)
      typedef struct {
          char id[4];
          unsigned int flags;
          unsigned char channels[4];      // pixelFormat high part
          unsigned char channelDepth[4];  // pixelFormat low part
          unsigned int colourSpace;
          unsigned int channelType;
          unsigned int height;
          unsigned int width;
          unsigned int depth;
          unsigned int numSurfaces;
          unsigned int numFaces;
          unsigned int numMipmaps;
          unsigned int metaDataSize;
      } PVRHeaderV3;
  
  #if 0   // Not used...
      // Metadata (usually 15 bytes)
      typedef struct {
          unsigned int devFOURCC;
          unsigned int key;
          unsigned int dataSize;      // Not used?
          unsigned char *data;        // Not used?
      } PVRMetadata;
  #endif
  
      Image image = { 0 };
  
      if (fileDataPtr != NULL)
      {
          // Check PVR image version
          unsigned char pvrVersion = fileDataPtr[0];
  
          // Load different PVR data formats
          if (pvrVersion == 0x50)
          {
              PVRHeaderV3 *pvrHeader = (PVRHeaderV3 *)fileDataPtr;
  
              if ((pvrHeader->id[0] != 'P') || (pvrHeader->id[1] != 'V') || (pvrHeader->id[2] != 'R') || (pvrHeader->id[3] != 3))
              {
                  TRACELOG(LOG_WARNING, "IMAGE: PVR file data not valid");
              }
              else
              {
                  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 = 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->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)
                      {
                          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 = PIXELFORMAT_COMPRESSED_PVRT_RGB;
                  else if (pvrHeader->channels[0] == 3) image.format = PIXELFORMAT_COMPRESSED_PVRT_RGBA;
  
                  fileDataPtr += pvrHeader->metaDataSize;    // Skip meta data header
  
                  // Calculate data size (depends on format)
                  int bpp = 0;
                  switch (image.format)
                  {
                      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));
  
                  memcpy(image.data, fileDataPtr, dataSize);
              }
          }
          else if (pvrVersion == 52) TRACELOG(LOG_INFO, "IMAGE: PVRv2 format not supported, update your files to PVRv3");
      }
  
      return image;
  }
  #endif
  
  #if defined(SUPPORT_FILEFORMAT_ASTC)
  // Load ASTC compressed image data (ASTC compression)
  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
  
      // Supported tokens (defined by extensions)
      // GL_COMPRESSED_RGBA_ASTC_4x4_KHR      0x93b0
      // GL_COMPRESSED_RGBA_ASTC_8x8_KHR      0x93b7
  
      // ASTC file Header (16 bytes)
      typedef struct {
          unsigned char id[4];        // Signature: 0x13 0xAB 0xA1 0x5C
          unsigned char blockX;       // Block X dimensions
          unsigned char blockY;       // Block Y dimensions
          unsigned char blockZ;       // Block Z dimensions (1 for 2D images)
          unsigned char width[3];     // Image width in pixels (24bit value)
          unsigned char height[3];    // Image height in pixels (24bit value)
          unsigned char length[3];    // Image Z-size (1 for 2D images)
      } ASTCHeader;
  
      Image image = { 0 };
  
      if (fileDataPtr != NULL)
      {
          ASTCHeader *astcHeader = (ASTCHeader *)fileDataPtr;
  
          if ((astcHeader->id[3] != 0x5c) || (astcHeader->id[2] != 0xa1) || (astcHeader->id[1] != 0xab) || (astcHeader->id[0] != 0x13))
          {
              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]);
  
              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);
  
              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);
  
              // NOTE: Currently we only support 2 blocks configurations: 4x4 and 8x8
              if ((bpp == 8) || (bpp == 2))
              {
                  int dataSize = image.width*image.height*bpp/8;  // Data size in bytes
  
                  image.data = (unsigned char *)RL_MALLOC(dataSize*sizeof(unsigned char));
  
                  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: ASTC block size configuration not supported");
          }
      }
  
      return image;
  }
  #endif
  
  // 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
      {
          for (int i = 0, k = 0; i < image.width*image.height; i++)
          {
              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;
              }
          }
      }
  
      return pixels;
  }
  
  #endif      // SUPPORT_MODULE_RTEXTURES
  