rtextures.c (225677B)
1 /********************************************************************************************** 2 * 3 * rtextures - Basic functions to load and draw textures 4 * 5 * CONFIGURATION: 6 * #define SUPPORT_MODULE_RTEXTURES 7 * rtextures module is included in the build 8 * 9 * #define SUPPORT_FILEFORMAT_BMP 10 * #define SUPPORT_FILEFORMAT_PNG 11 * #define SUPPORT_FILEFORMAT_TGA 12 * #define SUPPORT_FILEFORMAT_JPG 13 * #define SUPPORT_FILEFORMAT_GIF 14 * #define SUPPORT_FILEFORMAT_QOI 15 * #define SUPPORT_FILEFORMAT_PSD 16 * #define SUPPORT_FILEFORMAT_HDR 17 * #define SUPPORT_FILEFORMAT_PIC 18 * #define SUPPORT_FILEFORMAT_PNM 19 * #define SUPPORT_FILEFORMAT_DDS 20 * #define SUPPORT_FILEFORMAT_PKM 21 * #define SUPPORT_FILEFORMAT_KTX 22 * #define SUPPORT_FILEFORMAT_PVR 23 * #define SUPPORT_FILEFORMAT_ASTC 24 * Select desired fileformats to be supported for image data loading. Some of those formats are 25 * supported by default, to remove support, just comment unrequired #define in this module 26 * 27 * #define SUPPORT_IMAGE_EXPORT 28 * Support image export in multiple file formats 29 * 30 * #define SUPPORT_IMAGE_MANIPULATION 31 * Support multiple image editing functions to scale, adjust colors, flip, draw on images, crop... 32 * If not defined only some image editing functions supported: ImageFormat(), ImageAlphaMask(), ImageResize*() 33 * 34 * #define SUPPORT_IMAGE_GENERATION 35 * Support procedural image generation functionality (gradient, spot, perlin-noise, cellular) 36 * 37 * DEPENDENCIES: 38 * stb_image - Multiple image formats loading (JPEG, PNG, BMP, TGA, PSD, GIF, PIC) 39 * NOTE: stb_image has been slightly modified to support Android platform. 40 * stb_image_resize - Multiple image resize algorithms 41 * 42 * 43 * LICENSE: zlib/libpng 44 * 45 * Copyright (c) 2013-2024 Ramon Santamaria (@raysan5) 46 * 47 * This software is provided "as-is", without any express or implied warranty. In no event 48 * will the authors be held liable for any damages arising from the use of this software. 49 * 50 * Permission is granted to anyone to use this software for any purpose, including commercial 51 * applications, and to alter it and redistribute it freely, subject to the following restrictions: 52 * 53 * 1. The origin of this software must not be misrepresented; you must not claim that you 54 * wrote the original software. If you use this software in a product, an acknowledgment 55 * in the product documentation would be appreciated but is not required. 56 * 57 * 2. Altered source versions must be plainly marked as such, and must not be misrepresented 58 * as being the original software. 59 * 60 * 3. This notice may not be removed or altered from any source distribution. 61 * 62 **********************************************************************************************/ 63 64 #include "raylib.h" // Declares module functions 65 66 // Check if config flags have been externally provided on compilation line 67 #if !defined(EXTERNAL_CONFIG_FLAGS) 68 #include "config.h" // Defines module configuration flags 69 #endif 70 71 #if defined(SUPPORT_MODULE_RTEXTURES) 72 73 #include "utils.h" // Required for: TRACELOG() 74 #include "rlgl.h" // OpenGL abstraction layer to multiple versions 75 76 #include <stdlib.h> // Required for: malloc(), calloc(), free() 77 #include <string.h> // Required for: strlen() [Used in ImageTextEx()], strcmp() [Used in LoadImageFromMemory()/LoadImageAnimFromMemory()/ExportImageToMemory()] 78 #include <math.h> // Required for: fabsf() [Used in DrawTextureRec()] 79 #include <stdio.h> // Required for: sprintf() [Used in ExportImageAsCode()] 80 81 // Support only desired texture formats on stb_image 82 #if !defined(SUPPORT_FILEFORMAT_BMP) 83 #define STBI_NO_BMP 84 #endif 85 #if !defined(SUPPORT_FILEFORMAT_PNG) 86 #define STBI_NO_PNG 87 #endif 88 #if !defined(SUPPORT_FILEFORMAT_TGA) 89 #define STBI_NO_TGA 90 #endif 91 #if !defined(SUPPORT_FILEFORMAT_JPG) 92 #define STBI_NO_JPEG // Image format .jpg and .jpeg 93 #endif 94 #if !defined(SUPPORT_FILEFORMAT_PSD) 95 #define STBI_NO_PSD 96 #endif 97 #if !defined(SUPPORT_FILEFORMAT_GIF) 98 #define STBI_NO_GIF 99 #endif 100 #if !defined(SUPPORT_FILEFORMAT_PIC) 101 #define STBI_NO_PIC 102 #endif 103 #if !defined(SUPPORT_FILEFORMAT_HDR) 104 #define STBI_NO_HDR 105 #endif 106 #if !defined(SUPPORT_FILEFORMAT_PNM) 107 #define STBI_NO_PNM 108 #endif 109 110 #if defined(SUPPORT_FILEFORMAT_DDS) 111 #define RL_GPUTEX_SUPPORT_DDS 112 #endif 113 #if defined(SUPPORT_FILEFORMAT_PKM) 114 #define RL_GPUTEX_SUPPORT_PKM 115 #endif 116 #if defined(SUPPORT_FILEFORMAT_KTX) 117 #define RL_GPUTEX_SUPPORT_KTX 118 #endif 119 #if defined(SUPPORT_FILEFORMAT_PVR) 120 #define RL_GPUTEX_SUPPORT_PVR 121 #endif 122 #if defined(SUPPORT_FILEFORMAT_ASTC) 123 #define RL_GPUTEX_SUPPORT_ASTC 124 #endif 125 126 // Image fileformats not supported by default 127 #if defined(__TINYC__) 128 #define STBI_NO_SIMD 129 #endif 130 131 #if (defined(SUPPORT_FILEFORMAT_BMP) || \ 132 defined(SUPPORT_FILEFORMAT_PNG) || \ 133 defined(SUPPORT_FILEFORMAT_TGA) || \ 134 defined(SUPPORT_FILEFORMAT_JPG) || \ 135 defined(SUPPORT_FILEFORMAT_PSD) || \ 136 defined(SUPPORT_FILEFORMAT_GIF) || \ 137 defined(SUPPORT_FILEFORMAT_HDR) || \ 138 defined(SUPPORT_FILEFORMAT_PIC) || \ 139 defined(SUPPORT_FILEFORMAT_PNM)) 140 141 #if defined(__GNUC__) // GCC and Clang 142 #pragma GCC diagnostic push 143 #pragma GCC diagnostic ignored "-Wunused-function" 144 #endif 145 146 #define STBI_MALLOC RL_MALLOC 147 #define STBI_FREE RL_FREE 148 #define STBI_REALLOC RL_REALLOC 149 150 #define STBI_NO_THREAD_LOCALS 151 152 #define STB_IMAGE_IMPLEMENTATION 153 #include "external/stb_image.h" // Required for: stbi_load_from_file() 154 // NOTE: Used to read image data (multiple formats support) 155 156 #if defined(__GNUC__) // GCC and Clang 157 #pragma GCC diagnostic pop 158 #endif 159 #endif 160 161 #if (defined(SUPPORT_FILEFORMAT_DDS) || \ 162 defined(SUPPORT_FILEFORMAT_PKM) || \ 163 defined(SUPPORT_FILEFORMAT_KTX) || \ 164 defined(SUPPORT_FILEFORMAT_PVR) || \ 165 defined(SUPPORT_FILEFORMAT_ASTC)) 166 167 #if defined(__GNUC__) // GCC and Clang 168 #pragma GCC diagnostic push 169 #pragma GCC diagnostic ignored "-Wunused-function" 170 #endif 171 172 #define RL_GPUTEX_IMPLEMENTATION 173 #include "external/rl_gputex.h" // Required for: rl_load_xxx_from_memory() 174 // NOTE: Used to read compressed textures data (multiple formats support) 175 176 #if defined(__GNUC__) // GCC and Clang 177 #pragma GCC diagnostic pop 178 #endif 179 #endif 180 181 #if defined(SUPPORT_FILEFORMAT_QOI) 182 #define QOI_MALLOC RL_MALLOC 183 #define QOI_FREE RL_FREE 184 185 #if defined(_MSC_VER) // Disable some MSVC warning 186 #pragma warning(push) 187 #pragma warning(disable : 4267) 188 #endif 189 190 #define QOI_IMPLEMENTATION 191 #include "external/qoi.h" 192 193 #if defined(_MSC_VER) 194 #pragma warning(pop) // Disable MSVC warning suppression 195 #endif 196 197 #endif 198 199 #if defined(SUPPORT_IMAGE_EXPORT) 200 #define STBIW_MALLOC RL_MALLOC 201 #define STBIW_FREE RL_FREE 202 #define STBIW_REALLOC RL_REALLOC 203 204 #define STB_IMAGE_WRITE_IMPLEMENTATION 205 #include "external/stb_image_write.h" // Required for: stbi_write_*() 206 #endif 207 208 #if defined(SUPPORT_IMAGE_GENERATION) 209 #define STB_PERLIN_IMPLEMENTATION 210 #include "external/stb_perlin.h" // Required for: stb_perlin_fbm_noise3 211 #endif 212 213 #define STBIR_MALLOC(size,c) ((void)(c), RL_MALLOC(size)) 214 #define STBIR_FREE(ptr,c) ((void)(c), RL_FREE(ptr)) 215 216 #if defined(__GNUC__) // GCC and Clang 217 #pragma GCC diagnostic push 218 #pragma GCC diagnostic ignored "-Wunused-function" 219 #endif 220 221 #define STB_IMAGE_RESIZE_IMPLEMENTATION 222 #include "external/stb_image_resize2.h" // Required for: stbir_resize_uint8_linear() [ImageResize()] 223 224 #if defined(__GNUC__) // GCC and Clang 225 #pragma GCC diagnostic pop 226 #endif 227 228 //---------------------------------------------------------------------------------- 229 // Defines and Macros 230 //---------------------------------------------------------------------------------- 231 #ifndef PIXELFORMAT_UNCOMPRESSED_R5G5B5A1_ALPHA_THRESHOLD 232 #define PIXELFORMAT_UNCOMPRESSED_R5G5B5A1_ALPHA_THRESHOLD 50 // Threshold over 255 to set alpha as 0 233 #endif 234 235 #ifndef GAUSSIAN_BLUR_ITERATIONS 236 #define GAUSSIAN_BLUR_ITERATIONS 4 // Number of box blur iterations to approximate gaussian blur 237 #endif 238 239 //---------------------------------------------------------------------------------- 240 // Types and Structures Definition 241 //---------------------------------------------------------------------------------- 242 // ... 243 244 //---------------------------------------------------------------------------------- 245 // Global Variables Definition 246 //---------------------------------------------------------------------------------- 247 // It's lonely here... 248 249 //---------------------------------------------------------------------------------- 250 // Other Modules Functions Declaration (required by text) 251 //---------------------------------------------------------------------------------- 252 extern void LoadFontDefault(void); // [Module: text] Loads default font, required by ImageDrawText() 253 254 //---------------------------------------------------------------------------------- 255 // Module specific Functions Declaration 256 //---------------------------------------------------------------------------------- 257 static float HalfToFloat(unsigned short x); 258 static unsigned short FloatToHalf(float x); 259 static Vector4 *LoadImageDataNormalized(Image image); // Load pixel data from image as Vector4 array (float normalized) 260 261 //---------------------------------------------------------------------------------- 262 // Module Functions Definition 263 //---------------------------------------------------------------------------------- 264 265 // Load image from file into CPU memory (RAM) 266 Image LoadImage(const char *fileName) 267 { 268 Image image = { 0 }; 269 270 #if defined(SUPPORT_FILEFORMAT_PNG) || \ 271 defined(SUPPORT_FILEFORMAT_BMP) || \ 272 defined(SUPPORT_FILEFORMAT_TGA) || \ 273 defined(SUPPORT_FILEFORMAT_JPG) || \ 274 defined(SUPPORT_FILEFORMAT_GIF) || \ 275 defined(SUPPORT_FILEFORMAT_PIC) || \ 276 defined(SUPPORT_FILEFORMAT_HDR) || \ 277 defined(SUPPORT_FILEFORMAT_PNM) || \ 278 defined(SUPPORT_FILEFORMAT_PSD) 279 280 #define STBI_REQUIRED 281 #endif 282 283 // Loading file to memory 284 int dataSize = 0; 285 unsigned char *fileData = LoadFileData(fileName, &dataSize); 286 287 // Loading image from memory data 288 if (fileData != NULL) 289 { 290 image = LoadImageFromMemory(GetFileExtension(fileName), fileData, dataSize); 291 292 UnloadFileData(fileData); 293 } 294 295 return image; 296 } 297 298 // Load an image from RAW file data 299 Image LoadImageRaw(const char *fileName, int width, int height, int format, int headerSize) 300 { 301 Image image = { 0 }; 302 303 int dataSize = 0; 304 unsigned char *fileData = LoadFileData(fileName, &dataSize); 305 306 if (fileData != NULL) 307 { 308 unsigned char *dataPtr = fileData; 309 int size = GetPixelDataSize(width, height, format); 310 311 if (size <= dataSize) // Security check 312 { 313 // Offset file data to expected raw image by header size 314 if ((headerSize > 0) && ((headerSize + size) <= dataSize)) dataPtr += headerSize; 315 316 image.data = RL_MALLOC(size); // Allocate required memory in bytes 317 memcpy(image.data, dataPtr, size); // Copy required data to image 318 image.width = width; 319 image.height = height; 320 image.mipmaps = 1; 321 image.format = format; 322 } 323 324 UnloadFileData(fileData); 325 } 326 327 return image; 328 } 329 330 // Load animated image data 331 // - Image.data buffer includes all frames: [image#0][image#1][image#2][...] 332 // - Number of frames is returned through 'frames' parameter 333 // - All frames are returned in RGBA format 334 // - Frames delay data is discarded 335 Image LoadImageAnim(const char *fileName, int *frames) 336 { 337 Image image = { 0 }; 338 int frameCount = 0; 339 340 #if defined(SUPPORT_FILEFORMAT_GIF) 341 if (IsFileExtension(fileName, ".gif")) 342 { 343 int dataSize = 0; 344 unsigned char *fileData = LoadFileData(fileName, &dataSize); 345 346 if (fileData != NULL) 347 { 348 int comp = 0; 349 int *delays = NULL; 350 image.data = stbi_load_gif_from_memory(fileData, dataSize, &delays, &image.width, &image.height, &frameCount, &comp, 4); 351 352 image.mipmaps = 1; 353 image.format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8; 354 355 UnloadFileData(fileData); 356 RL_FREE(delays); // NOTE: Frames delays are discarded 357 } 358 } 359 #else 360 if (false) { } 361 #endif 362 else 363 { 364 image = LoadImage(fileName); 365 frameCount = 1; 366 } 367 368 *frames = frameCount; 369 return image; 370 } 371 372 // Load animated image data 373 // - Image.data buffer includes all frames: [image#0][image#1][image#2][...] 374 // - Number of frames is returned through 'frames' parameter 375 // - All frames are returned in RGBA format 376 // - Frames delay data is discarded 377 Image LoadImageAnimFromMemory(const char *fileType, const unsigned char *fileData, int dataSize, int *frames) 378 { 379 Image image = { 0 }; 380 int frameCount = 0; 381 382 // Security check for input data 383 if ((fileType == NULL) || (fileData == NULL) || (dataSize == 0)) return image; 384 385 #if defined(SUPPORT_FILEFORMAT_GIF) 386 if ((strcmp(fileType, ".gif") == 0) || (strcmp(fileType, ".GIF") == 0)) 387 { 388 if (fileData != NULL) 389 { 390 int comp = 0; 391 int *delays = NULL; 392 image.data = stbi_load_gif_from_memory(fileData, dataSize, &delays, &image.width, &image.height, &frameCount, &comp, 4); 393 394 image.mipmaps = 1; 395 image.format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8; 396 397 RL_FREE(delays); // NOTE: Frames delays are discarded 398 } 399 } 400 #else 401 if (false) { } 402 #endif 403 else 404 { 405 image = LoadImageFromMemory(fileType, fileData, dataSize); 406 frameCount = 1; 407 } 408 409 *frames = frameCount; 410 return image; 411 } 412 413 // Load image from memory buffer, fileType refers to extension: i.e. ".png" 414 // WARNING: File extension must be provided in lower-case 415 Image LoadImageFromMemory(const char *fileType, const unsigned char *fileData, int dataSize) 416 { 417 Image image = { 0 }; 418 419 // Security check for input data 420 if ((fileData == NULL) || (dataSize == 0)) 421 { 422 TRACELOG(LOG_WARNING, "IMAGE: Invalid file data"); 423 return image; 424 } 425 if (fileType == NULL) 426 { 427 TRACELOG(LOG_WARNING, "IMAGE: Missing file extension"); 428 return image; 429 } 430 431 if ((false) 432 #if defined(SUPPORT_FILEFORMAT_PNG) 433 || (strcmp(fileType, ".png") == 0) || (strcmp(fileType, ".PNG") == 0) 434 #endif 435 #if defined(SUPPORT_FILEFORMAT_BMP) 436 || (strcmp(fileType, ".bmp") == 0) || (strcmp(fileType, ".BMP") == 0) 437 #endif 438 #if defined(SUPPORT_FILEFORMAT_TGA) 439 || (strcmp(fileType, ".tga") == 0) || (strcmp(fileType, ".TGA") == 0) 440 #endif 441 #if defined(SUPPORT_FILEFORMAT_JPG) 442 || (strcmp(fileType, ".jpg") == 0) || (strcmp(fileType, ".jpeg") == 0) 443 || (strcmp(fileType, ".JPG") == 0) || (strcmp(fileType, ".JPEG") == 0) 444 #endif 445 #if defined(SUPPORT_FILEFORMAT_GIF) 446 || (strcmp(fileType, ".gif") == 0) || (strcmp(fileType, ".GIF") == 0) 447 #endif 448 #if defined(SUPPORT_FILEFORMAT_PIC) 449 || (strcmp(fileType, ".pic") == 0) || (strcmp(fileType, ".PIC") == 0) 450 #endif 451 #if defined(SUPPORT_FILEFORMAT_PNM) 452 || (strcmp(fileType, ".ppm") == 0) || (strcmp(fileType, ".pgm") == 0) 453 || (strcmp(fileType, ".PPM") == 0) || (strcmp(fileType, ".PGM") == 0) 454 #endif 455 #if defined(SUPPORT_FILEFORMAT_PSD) 456 || (strcmp(fileType, ".psd") == 0) || (strcmp(fileType, ".PSD") == 0) 457 #endif 458 ) 459 { 460 #if defined(STBI_REQUIRED) 461 // NOTE: Using stb_image to load images (Supports multiple image formats) 462 463 if (fileData != NULL) 464 { 465 int comp = 0; 466 image.data = stbi_load_from_memory(fileData, dataSize, &image.width, &image.height, &comp, 0); 467 468 if (image.data != NULL) 469 { 470 image.mipmaps = 1; 471 472 if (comp == 1) image.format = PIXELFORMAT_UNCOMPRESSED_GRAYSCALE; 473 else if (comp == 2) image.format = PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA; 474 else if (comp == 3) image.format = PIXELFORMAT_UNCOMPRESSED_R8G8B8; 475 else if (comp == 4) image.format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8; 476 } 477 } 478 #endif 479 } 480 #if defined(SUPPORT_FILEFORMAT_HDR) 481 else if ((strcmp(fileType, ".hdr") == 0) || (strcmp(fileType, ".HDR") == 0)) 482 { 483 #if defined(STBI_REQUIRED) 484 if (fileData != NULL) 485 { 486 int comp = 0; 487 image.data = stbi_loadf_from_memory(fileData, dataSize, &image.width, &image.height, &comp, 0); 488 489 image.mipmaps = 1; 490 491 if (comp == 1) image.format = PIXELFORMAT_UNCOMPRESSED_R32; 492 else if (comp == 3) image.format = PIXELFORMAT_UNCOMPRESSED_R32G32B32; 493 else if (comp == 4) image.format = PIXELFORMAT_UNCOMPRESSED_R32G32B32A32; 494 else 495 { 496 TRACELOG(LOG_WARNING, "IMAGE: HDR file format not supported"); 497 UnloadImage(image); 498 } 499 } 500 #endif 501 } 502 #endif 503 #if defined(SUPPORT_FILEFORMAT_QOI) 504 else if ((strcmp(fileType, ".qoi") == 0) || (strcmp(fileType, ".QOI") == 0)) 505 { 506 if (fileData != NULL) 507 { 508 qoi_desc desc = { 0 }; 509 image.data = qoi_decode(fileData, dataSize, &desc, (int) fileData[12]); 510 image.width = desc.width; 511 image.height = desc.height; 512 image.format = desc.channels == 4 ? PIXELFORMAT_UNCOMPRESSED_R8G8B8A8 : PIXELFORMAT_UNCOMPRESSED_R8G8B8; 513 image.mipmaps = 1; 514 } 515 } 516 #endif 517 #if defined(SUPPORT_FILEFORMAT_DDS) 518 else if ((strcmp(fileType, ".dds") == 0) || (strcmp(fileType, ".DDS") == 0)) 519 { 520 image.data = rl_load_dds_from_memory(fileData, dataSize, &image.width, &image.height, &image.format, &image.mipmaps); 521 } 522 #endif 523 #if defined(SUPPORT_FILEFORMAT_PKM) 524 else if ((strcmp(fileType, ".pkm") == 0) || (strcmp(fileType, ".PKM") == 0)) 525 { 526 image.data = rl_load_pkm_from_memory(fileData, dataSize, &image.width, &image.height, &image.format, &image.mipmaps); 527 } 528 #endif 529 #if defined(SUPPORT_FILEFORMAT_KTX) 530 else if ((strcmp(fileType, ".ktx") == 0) || (strcmp(fileType, ".KTX") == 0)) 531 { 532 image.data = rl_load_ktx_from_memory(fileData, dataSize, &image.width, &image.height, &image.format, &image.mipmaps); 533 } 534 #endif 535 #if defined(SUPPORT_FILEFORMAT_PVR) 536 else if ((strcmp(fileType, ".pvr") == 0) || (strcmp(fileType, ".PVR") == 0)) 537 { 538 image.data = rl_load_pvr_from_memory(fileData, dataSize, &image.width, &image.height, &image.format, &image.mipmaps); 539 } 540 #endif 541 #if defined(SUPPORT_FILEFORMAT_ASTC) 542 else if ((strcmp(fileType, ".astc") == 0) || (strcmp(fileType, ".ASTC") == 0)) 543 { 544 image.data = rl_load_astc_from_memory(fileData, dataSize, &image.width, &image.height, &image.format, &image.mipmaps); 545 } 546 #endif 547 else TRACELOG(LOG_WARNING, "IMAGE: Data format not supported"); 548 549 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); 550 else TRACELOG(LOG_WARNING, "IMAGE: Failed to load image data"); 551 552 return image; 553 } 554 555 // Load image from GPU texture data 556 // NOTE: Compressed texture formats not supported 557 Image LoadImageFromTexture(Texture2D texture) 558 { 559 Image image = { 0 }; 560 561 if (texture.format < PIXELFORMAT_COMPRESSED_DXT1_RGB) 562 { 563 image.data = rlReadTexturePixels(texture.id, texture.width, texture.height, texture.format); 564 565 if (image.data != NULL) 566 { 567 image.width = texture.width; 568 image.height = texture.height; 569 image.format = texture.format; 570 image.mipmaps = 1; 571 572 #if defined(GRAPHICS_API_OPENGL_ES2) 573 // NOTE: Data retrieved on OpenGL ES 2.0 should be RGBA, 574 // coming from FBO color buffer attachment, but it seems 575 // original texture format is retrieved on RPI... 576 image.format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8; 577 #endif 578 TRACELOG(LOG_INFO, "TEXTURE: [ID %i] Pixel data retrieved successfully", texture.id); 579 } 580 else TRACELOG(LOG_WARNING, "TEXTURE: [ID %i] Failed to retrieve pixel data", texture.id); 581 } 582 else TRACELOG(LOG_WARNING, "TEXTURE: [ID %i] Failed to retrieve compressed pixel data", texture.id); 583 584 return image; 585 } 586 587 // Load image from screen buffer and (screenshot) 588 Image LoadImageFromScreen(void) 589 { 590 Vector2 scale = GetWindowScaleDPI(); 591 Image image = { 0 }; 592 593 image.width = (int)(GetScreenWidth()*scale.x); 594 image.height = (int)(GetScreenHeight()*scale.y); 595 image.mipmaps = 1; 596 image.format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8; 597 image.data = rlReadScreenPixels(image.width, image.height); 598 599 return image; 600 } 601 602 // Check if an image is ready 603 bool IsImageValid(Image image) 604 { 605 bool result = false; 606 607 if ((image.data != NULL) && // Validate pixel data available 608 (image.width > 0) && // Validate image width 609 (image.height > 0) && // Validate image height 610 (image.format > 0) && // Validate image format 611 (image.mipmaps > 0)) result = true; // Validate image mipmaps (at least 1 for basic mipmap level) 612 613 return result; 614 } 615 616 // Unload image from CPU memory (RAM) 617 void UnloadImage(Image image) 618 { 619 RL_FREE(image.data); 620 } 621 622 // Export image data to file 623 // NOTE: File format depends on fileName extension 624 bool ExportImage(Image image, const char *fileName) 625 { 626 int result = 0; 627 628 // Security check for input data 629 if ((image.width == 0) || (image.height == 0) || (image.data == NULL)) return result; 630 631 #if defined(SUPPORT_IMAGE_EXPORT) 632 int channels = 4; 633 bool allocatedData = false; 634 unsigned char *imgData = (unsigned char *)image.data; 635 636 if (image.format == PIXELFORMAT_UNCOMPRESSED_GRAYSCALE) channels = 1; 637 else if (image.format == PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA) channels = 2; 638 else if (image.format == PIXELFORMAT_UNCOMPRESSED_R8G8B8) channels = 3; 639 else if (image.format == PIXELFORMAT_UNCOMPRESSED_R8G8B8A8) channels = 4; 640 else 641 { 642 // NOTE: Getting Color array as RGBA unsigned char values 643 imgData = (unsigned char *)LoadImageColors(image); 644 allocatedData = true; 645 } 646 647 #if defined(SUPPORT_FILEFORMAT_PNG) 648 if (IsFileExtension(fileName, ".png")) 649 { 650 int dataSize = 0; 651 unsigned char *fileData = stbi_write_png_to_mem((const unsigned char *)imgData, image.width*channels, image.width, image.height, channels, &dataSize); 652 result = SaveFileData(fileName, fileData, dataSize); 653 RL_FREE(fileData); 654 } 655 #else 656 if (false) { } 657 #endif 658 #if defined(SUPPORT_FILEFORMAT_BMP) 659 else if (IsFileExtension(fileName, ".bmp")) result = stbi_write_bmp(fileName, image.width, image.height, channels, imgData); 660 #endif 661 #if defined(SUPPORT_FILEFORMAT_TGA) 662 else if (IsFileExtension(fileName, ".tga")) result = stbi_write_tga(fileName, image.width, image.height, channels, imgData); 663 #endif 664 #if defined(SUPPORT_FILEFORMAT_JPG) 665 else if (IsFileExtension(fileName, ".jpg") || 666 IsFileExtension(fileName, ".jpeg")) result = stbi_write_jpg(fileName, image.width, image.height, channels, imgData, 90); // JPG quality: between 1 and 100 667 #endif 668 #if defined(SUPPORT_FILEFORMAT_QOI) 669 else if (IsFileExtension(fileName, ".qoi")) 670 { 671 channels = 0; 672 if (image.format == PIXELFORMAT_UNCOMPRESSED_R8G8B8) channels = 3; 673 else if (image.format == PIXELFORMAT_UNCOMPRESSED_R8G8B8A8) channels = 4; 674 else TRACELOG(LOG_WARNING, "IMAGE: Image pixel format must be R8G8B8 or R8G8B8A8"); 675 676 if ((channels == 3) || (channels == 4)) 677 { 678 qoi_desc desc = { 0 }; 679 desc.width = image.width; 680 desc.height = image.height; 681 desc.channels = channels; 682 desc.colorspace = QOI_SRGB; 683 684 result = qoi_write(fileName, imgData, &desc); 685 } 686 } 687 #endif 688 #if defined(SUPPORT_FILEFORMAT_KTX) 689 else if (IsFileExtension(fileName, ".ktx")) 690 { 691 result = rl_save_ktx(fileName, image.data, image.width, image.height, image.format, image.mipmaps); 692 } 693 #endif 694 else if (IsFileExtension(fileName, ".raw")) 695 { 696 // Export raw pixel data (without header) 697 // NOTE: It's up to the user to track image parameters 698 result = SaveFileData(fileName, image.data, GetPixelDataSize(image.width, image.height, image.format)); 699 } 700 701 if (allocatedData) RL_FREE(imgData); 702 #endif // SUPPORT_IMAGE_EXPORT 703 704 if (result != 0) TRACELOG(LOG_INFO, "FILEIO: [%s] Image exported successfully", fileName); 705 else TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to export image", fileName); 706 707 return result; 708 } 709 710 // Export image to memory buffer 711 unsigned char *ExportImageToMemory(Image image, const char *fileType, int *dataSize) 712 { 713 unsigned char *fileData = NULL; 714 *dataSize = 0; 715 716 // Security check for input data 717 if ((image.width == 0) || (image.height == 0) || (image.data == NULL)) return NULL; 718 719 #if defined(SUPPORT_IMAGE_EXPORT) 720 int channels = 4; 721 722 if (image.format == PIXELFORMAT_UNCOMPRESSED_GRAYSCALE) channels = 1; 723 else if (image.format == PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA) channels = 2; 724 else if (image.format == PIXELFORMAT_UNCOMPRESSED_R8G8B8) channels = 3; 725 else if (image.format == PIXELFORMAT_UNCOMPRESSED_R8G8B8A8) channels = 4; 726 727 #if defined(SUPPORT_FILEFORMAT_PNG) 728 if ((strcmp(fileType, ".png") == 0) || (strcmp(fileType, ".PNG") == 0)) 729 { 730 fileData = stbi_write_png_to_mem((const unsigned char *)image.data, image.width*channels, image.width, image.height, channels, dataSize); 731 } 732 #endif 733 734 #endif 735 736 return fileData; 737 } 738 739 // Export image as code file (.h) defining an array of bytes 740 bool ExportImageAsCode(Image image, const char *fileName) 741 { 742 bool success = false; 743 744 #if defined(SUPPORT_IMAGE_EXPORT) 745 746 #ifndef TEXT_BYTES_PER_LINE 747 #define TEXT_BYTES_PER_LINE 20 748 #endif 749 750 int dataSize = GetPixelDataSize(image.width, image.height, image.format); 751 752 // NOTE: Text data buffer size is estimated considering image data size in bytes 753 // and requiring 6 char bytes for every byte: "0x00, " 754 char *txtData = (char *)RL_CALLOC(dataSize*6 + 2000, sizeof(char)); 755 756 int byteCount = 0; 757 byteCount += sprintf(txtData + byteCount, "////////////////////////////////////////////////////////////////////////////////////////\n"); 758 byteCount += sprintf(txtData + byteCount, "// //\n"); 759 byteCount += sprintf(txtData + byteCount, "// ImageAsCode exporter v1.0 - Image pixel data exported as an array of bytes //\n"); 760 byteCount += sprintf(txtData + byteCount, "// //\n"); 761 byteCount += sprintf(txtData + byteCount, "// more info and bugs-report: github.com/raysan5/raylib //\n"); 762 byteCount += sprintf(txtData + byteCount, "// feedback and support: ray[at]raylib.com //\n"); 763 byteCount += sprintf(txtData + byteCount, "// //\n"); 764 byteCount += sprintf(txtData + byteCount, "// Copyright (c) 2018-2024 Ramon Santamaria (@raysan5) //\n"); 765 byteCount += sprintf(txtData + byteCount, "// //\n"); 766 byteCount += sprintf(txtData + byteCount, "////////////////////////////////////////////////////////////////////////////////////////\n\n"); 767 768 // Get file name from path and convert variable name to uppercase 769 char varFileName[256] = { 0 }; 770 strcpy(varFileName, GetFileNameWithoutExt(fileName)); 771 for (int i = 0; varFileName[i] != '\0'; i++) if ((varFileName[i] >= 'a') && (varFileName[i] <= 'z')) { varFileName[i] = varFileName[i] - 32; } 772 773 // Add image information 774 byteCount += sprintf(txtData + byteCount, "// Image data information\n"); 775 byteCount += sprintf(txtData + byteCount, "#define %s_WIDTH %i\n", varFileName, image.width); 776 byteCount += sprintf(txtData + byteCount, "#define %s_HEIGHT %i\n", varFileName, image.height); 777 byteCount += sprintf(txtData + byteCount, "#define %s_FORMAT %i // raylib internal pixel format\n\n", varFileName, image.format); 778 779 byteCount += sprintf(txtData + byteCount, "static unsigned char %s_DATA[%i] = { ", varFileName, dataSize); 780 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]); 781 byteCount += sprintf(txtData + byteCount, "0x%x };\n", ((unsigned char *)image.data)[dataSize - 1]); 782 783 // NOTE: Text data size exported is determined by '\0' (NULL) character 784 success = SaveFileText(fileName, txtData); 785 786 RL_FREE(txtData); 787 788 #endif // SUPPORT_IMAGE_EXPORT 789 790 if (success != 0) TRACELOG(LOG_INFO, "FILEIO: [%s] Image as code exported successfully", fileName); 791 else TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to export image as code", fileName); 792 793 return success; 794 } 795 796 //------------------------------------------------------------------------------------ 797 // Image generation functions 798 //------------------------------------------------------------------------------------ 799 // Generate image: plain color 800 Image GenImageColor(int width, int height, Color color) 801 { 802 Color *pixels = (Color *)RL_CALLOC(width*height, sizeof(Color)); 803 804 for (int i = 0; i < width*height; i++) pixels[i] = color; 805 806 Image image = { 807 .data = pixels, 808 .width = width, 809 .height = height, 810 .format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8, 811 .mipmaps = 1 812 }; 813 814 return image; 815 } 816 817 #if defined(SUPPORT_IMAGE_GENERATION) 818 // Generate image: linear gradient 819 // The direction value specifies the direction of the gradient (in degrees) 820 // with 0 being vertical (from top to bottom), 90 being horizontal (from left to right) 821 // The gradient effectively rotates counter-clockwise by the specified amount 822 Image GenImageGradientLinear(int width, int height, int direction, Color start, Color end) 823 { 824 Color *pixels = (Color *)RL_MALLOC(width*height*sizeof(Color)); 825 826 float radianDirection = (float)(90 - direction)/180.f*3.14159f; 827 float cosDir = cosf(radianDirection); 828 float sinDir = sinf(radianDirection); 829 830 // Calculate how far the top-left pixel is along the gradient direction from the center of said gradient 831 float startingPos = 0.5f - (cosDir*width/2) - (sinDir*height/2); 832 // With directions that lie in the first or third quadrant (i.e. from top-left to 833 // bottom-right or vice-versa), pixel (0, 0) is the farthest point on the gradient 834 // (i.e. the pixel which should become one of the gradient's ends color); while for 835 // directions that lie in the second or fourth quadrant, that point is pixel (width, 0). 836 float maxPosValue = 837 ((signbit(sinDir) != 0) == (signbit(cosDir) != 0)) 838 ? fabsf(startingPos) 839 : fabsf(startingPos+width*cosDir); 840 for (int i = 0; i < width; i++) 841 { 842 for (int j = 0; j < height; j++) 843 { 844 // Calculate the relative position of the pixel along the gradient direction 845 float pos = (startingPos + (i*cosDir + j*sinDir)) / maxPosValue; 846 847 float factor = pos; 848 factor = (factor > 1.0f)? 1.0f : factor; // Clamp to [-1,1] 849 factor = (factor < -1.0f)? -1.0f : factor; // Clamp to [-1,1] 850 factor = factor / 2 + 0.5f; 851 852 // Generate the color for this pixel 853 pixels[j*width + i].r = (int)((float)end.r*factor + (float)start.r*(1.0f - factor)); 854 pixels[j*width + i].g = (int)((float)end.g*factor + (float)start.g*(1.0f - factor)); 855 pixels[j*width + i].b = (int)((float)end.b*factor + (float)start.b*(1.0f - factor)); 856 pixels[j*width + i].a = (int)((float)end.a*factor + (float)start.a*(1.0f - factor)); 857 } 858 } 859 860 Image image = { 861 .data = pixels, 862 .width = width, 863 .height = height, 864 .format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8, 865 .mipmaps = 1 866 }; 867 868 return image; 869 } 870 871 // Generate image: radial gradient 872 Image GenImageGradientRadial(int width, int height, float density, Color inner, Color outer) 873 { 874 Color *pixels = (Color *)RL_MALLOC(width*height*sizeof(Color)); 875 float radius = (width < height)? (float)width/2.0f : (float)height/2.0f; 876 877 float centerX = (float)width/2.0f; 878 float centerY = (float)height/2.0f; 879 880 for (int y = 0; y < height; y++) 881 { 882 for (int x = 0; x < width; x++) 883 { 884 float dist = hypotf((float)x - centerX, (float)y - centerY); 885 float factor = (dist - radius*density)/(radius*(1.0f - density)); 886 887 factor = (float)fmax(factor, 0.0f); 888 factor = (float)fmin(factor, 1.f); // dist can be bigger than radius, so we have to check 889 890 pixels[y*width + x].r = (int)((float)outer.r*factor + (float)inner.r*(1.0f - factor)); 891 pixels[y*width + x].g = (int)((float)outer.g*factor + (float)inner.g*(1.0f - factor)); 892 pixels[y*width + x].b = (int)((float)outer.b*factor + (float)inner.b*(1.0f - factor)); 893 pixels[y*width + x].a = (int)((float)outer.a*factor + (float)inner.a*(1.0f - factor)); 894 } 895 } 896 897 Image image = { 898 .data = pixels, 899 .width = width, 900 .height = height, 901 .format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8, 902 .mipmaps = 1 903 }; 904 905 return image; 906 } 907 908 // Generate image: square gradient 909 Image GenImageGradientSquare(int width, int height, float density, Color inner, Color outer) 910 { 911 Color *pixels = (Color *)RL_MALLOC(width*height*sizeof(Color)); 912 913 float centerX = (float)width/2.0f; 914 float centerY = (float)height/2.0f; 915 916 for (int y = 0; y < height; y++) 917 { 918 for (int x = 0; x < width; x++) 919 { 920 // Calculate the Manhattan distance from the center 921 float distX = fabsf(x - centerX); 922 float distY = fabsf(y - centerY); 923 924 // Normalize the distances by the dimensions of the gradient rectangle 925 float normalizedDistX = distX/centerX; 926 float normalizedDistY = distY/centerY; 927 928 // Calculate the total normalized Manhattan distance 929 float manhattanDist = fmaxf(normalizedDistX, normalizedDistY); 930 931 // Subtract the density from the manhattanDist, then divide by (1 - density) 932 // This makes the gradient start from the center when density is 0, and from the edge when density is 1 933 float factor = (manhattanDist - density)/(1.0f - density); 934 935 // Clamp the factor between 0 and 1 936 factor = fminf(fmaxf(factor, 0.0f), 1.0f); 937 938 // Blend the colors based on the calculated factor 939 pixels[y*width + x].r = (int)((float)outer.r*factor + (float)inner.r*(1.0f - factor)); 940 pixels[y*width + x].g = (int)((float)outer.g*factor + (float)inner.g*(1.0f - factor)); 941 pixels[y*width + x].b = (int)((float)outer.b*factor + (float)inner.b*(1.0f - factor)); 942 pixels[y*width + x].a = (int)((float)outer.a*factor + (float)inner.a*(1.0f - factor)); 943 } 944 } 945 946 Image image = { 947 .data = pixels, 948 .width = width, 949 .height = height, 950 .format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8, 951 .mipmaps = 1 952 }; 953 954 return image; 955 } 956 957 // Generate image: checked 958 Image GenImageChecked(int width, int height, int checksX, int checksY, Color col1, Color col2) 959 { 960 Color *pixels = (Color *)RL_MALLOC(width*height*sizeof(Color)); 961 962 for (int y = 0; y < height; y++) 963 { 964 for (int x = 0; x < width; x++) 965 { 966 if ((x/checksX + y/checksY)%2 == 0) pixels[y*width + x] = col1; 967 else pixels[y*width + x] = col2; 968 } 969 } 970 971 Image image = { 972 .data = pixels, 973 .width = width, 974 .height = height, 975 .format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8, 976 .mipmaps = 1 977 }; 978 979 return image; 980 } 981 982 // Generate image: white noise 983 // NOTE: It requires GetRandomValue(), defined in [rcore] 984 Image GenImageWhiteNoise(int width, int height, float factor) 985 { 986 Color *pixels = (Color *)RL_MALLOC(width*height*sizeof(Color)); 987 988 for (int i = 0; i < width*height; i++) 989 { 990 if (GetRandomValue(0, 99) < (int)(factor*100.0f)) pixels[i] = WHITE; 991 else pixels[i] = BLACK; 992 } 993 994 Image image = { 995 .data = pixels, 996 .width = width, 997 .height = height, 998 .format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8, 999 .mipmaps = 1 1000 }; 1001 1002 return image; 1003 } 1004 1005 // Generate image: perlin noise 1006 Image GenImagePerlinNoise(int width, int height, int offsetX, int offsetY, float scale) 1007 { 1008 Color *pixels = (Color *)RL_MALLOC(width*height*sizeof(Color)); 1009 1010 float aspectRatio = (float)width / (float)height; 1011 for (int y = 0; y < height; y++) 1012 { 1013 for (int x = 0; x < width; x++) 1014 { 1015 float nx = (float)(x + offsetX)*(scale/(float)width); 1016 float ny = (float)(y + offsetY)*(scale/(float)height); 1017 1018 // Apply aspect ratio compensation to wider side 1019 if (width > height) nx *= aspectRatio; 1020 else ny /= aspectRatio; 1021 1022 // Basic perlin noise implementation (not used) 1023 //float p = (stb_perlin_noise3(nx, ny, 0.0f, 0, 0, 0); 1024 1025 // Calculate a better perlin noise using fbm (fractal brownian motion) 1026 // Typical values to start playing with: 1027 // lacunarity = ~2.0 -- spacing between successive octaves (use exactly 2.0 for wrapping output) 1028 // gain = 0.5 -- relative weighting applied to each successive octave 1029 // octaves = 6 -- number of "octaves" of noise3() to sum 1030 float p = stb_perlin_fbm_noise3(nx, ny, 1.0f, 2.0f, 0.5f, 6); 1031 1032 // Clamp between -1.0f and 1.0f 1033 if (p < -1.0f) p = -1.0f; 1034 if (p > 1.0f) p = 1.0f; 1035 1036 // We need to normalize the data from [-1..1] to [0..1] 1037 float np = (p + 1.0f)/2.0f; 1038 1039 int intensity = (int)(np*255.0f); 1040 pixels[y*width + x] = (Color){ intensity, intensity, intensity, 255 }; 1041 } 1042 } 1043 1044 Image image = { 1045 .data = pixels, 1046 .width = width, 1047 .height = height, 1048 .format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8, 1049 .mipmaps = 1 1050 }; 1051 1052 return image; 1053 } 1054 1055 // Generate image: cellular algorithm. Bigger tileSize means bigger cells 1056 Image GenImageCellular(int width, int height, int tileSize) 1057 { 1058 Color *pixels = (Color *)RL_MALLOC(width*height*sizeof(Color)); 1059 1060 int seedsPerRow = width/tileSize; 1061 int seedsPerCol = height/tileSize; 1062 int seedCount = seedsPerRow*seedsPerCol; 1063 1064 Vector2 *seeds = (Vector2 *)RL_MALLOC(seedCount*sizeof(Vector2)); 1065 1066 for (int i = 0; i < seedCount; i++) 1067 { 1068 int y = (i/seedsPerRow)*tileSize + GetRandomValue(0, tileSize - 1); 1069 int x = (i%seedsPerRow)*tileSize + GetRandomValue(0, tileSize - 1); 1070 seeds[i] = (Vector2){ (float)x, (float)y }; 1071 } 1072 1073 for (int y = 0; y < height; y++) 1074 { 1075 int tileY = y/tileSize; 1076 1077 for (int x = 0; x < width; x++) 1078 { 1079 int tileX = x/tileSize; 1080 1081 float minDistance = 65536.0f; //(float)strtod("Inf", NULL); 1082 1083 // Check all adjacent tiles 1084 for (int i = -1; i < 2; i++) 1085 { 1086 if ((tileX + i < 0) || (tileX + i >= seedsPerRow)) continue; 1087 1088 for (int j = -1; j < 2; j++) 1089 { 1090 if ((tileY + j < 0) || (tileY + j >= seedsPerCol)) continue; 1091 1092 Vector2 neighborSeed = seeds[(tileY + j)*seedsPerRow + tileX + i]; 1093 1094 float dist = (float)hypot(x - (int)neighborSeed.x, y - (int)neighborSeed.y); 1095 minDistance = (float)fmin(minDistance, dist); 1096 } 1097 } 1098 1099 // I made this up, but it seems to give good results at all tile sizes 1100 int intensity = (int)(minDistance*256.0f/tileSize); 1101 if (intensity > 255) intensity = 255; 1102 1103 pixels[y*width + x] = (Color){ intensity, intensity, intensity, 255 }; 1104 } 1105 } 1106 1107 RL_FREE(seeds); 1108 1109 Image image = { 1110 .data = pixels, 1111 .width = width, 1112 .height = height, 1113 .format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8, 1114 .mipmaps = 1 1115 }; 1116 1117 return image; 1118 } 1119 1120 // Generate image: grayscale image from text data 1121 Image GenImageText(int width, int height, const char *text) 1122 { 1123 Image image = { 0 }; 1124 1125 int textLength = (int)strlen(text); 1126 int imageViewSize = width*height; 1127 1128 image.width = width; 1129 image.height = height; 1130 image.format = PIXELFORMAT_UNCOMPRESSED_GRAYSCALE; 1131 image.data = RL_CALLOC(imageViewSize, 1); 1132 image.mipmaps = 1; 1133 1134 memcpy(image.data, text, (textLength > imageViewSize)? imageViewSize : textLength); 1135 1136 return image; 1137 } 1138 #endif // SUPPORT_IMAGE_GENERATION 1139 1140 //------------------------------------------------------------------------------------ 1141 // Image manipulation functions 1142 //------------------------------------------------------------------------------------ 1143 // Copy an image to a new image 1144 Image ImageCopy(Image image) 1145 { 1146 Image newImage = { 0 }; 1147 1148 int width = image.width; 1149 int height = image.height; 1150 int size = 0; 1151 1152 for (int i = 0; i < image.mipmaps; i++) 1153 { 1154 size += GetPixelDataSize(width, height, image.format); 1155 1156 width /= 2; 1157 height /= 2; 1158 1159 // Security check for NPOT textures 1160 if (width < 1) width = 1; 1161 if (height < 1) height = 1; 1162 } 1163 1164 newImage.data = RL_CALLOC(size, 1); 1165 1166 if (newImage.data != NULL) 1167 { 1168 // NOTE: Size must be provided in bytes 1169 memcpy(newImage.data, image.data, size); 1170 1171 newImage.width = image.width; 1172 newImage.height = image.height; 1173 newImage.mipmaps = image.mipmaps; 1174 newImage.format = image.format; 1175 } 1176 1177 return newImage; 1178 } 1179 1180 // Create an image from another image piece 1181 Image ImageFromImage(Image image, Rectangle rec) 1182 { 1183 Image result = { 0 }; 1184 1185 int bytesPerPixel = GetPixelDataSize(1, 1, image.format); 1186 1187 result.width = (int)rec.width; 1188 result.height = (int)rec.height; 1189 result.data = RL_CALLOC((int)rec.width*(int)rec.height*bytesPerPixel, 1); 1190 result.format = image.format; 1191 result.mipmaps = 1; 1192 1193 for (int y = 0; y < (int)rec.height; y++) 1194 { 1195 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); 1196 } 1197 1198 return result; 1199 } 1200 1201 // Crop an image to area defined by a rectangle 1202 // NOTE: Security checks are performed in case rectangle goes out of bounds 1203 void ImageCrop(Image *image, Rectangle crop) 1204 { 1205 // Security check to avoid program crash 1206 if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; 1207 1208 // Security checks to validate crop rectangle 1209 if (crop.x < 0) { crop.width += crop.x; crop.x = 0; } 1210 if (crop.y < 0) { crop.height += crop.y; crop.y = 0; } 1211 if ((crop.x + crop.width) > image->width) crop.width = image->width - crop.x; 1212 if ((crop.y + crop.height) > image->height) crop.height = image->height - crop.y; 1213 if ((crop.x > image->width) || (crop.y > image->height)) 1214 { 1215 TRACELOG(LOG_WARNING, "IMAGE: Failed to crop, rectangle out of bounds"); 1216 return; 1217 } 1218 1219 if (image->mipmaps > 1) TRACELOG(LOG_WARNING, "Image manipulation only applied to base mipmap level"); 1220 if (image->format >= PIXELFORMAT_COMPRESSED_DXT1_RGB) TRACELOG(LOG_WARNING, "Image manipulation not supported for compressed formats"); 1221 else 1222 { 1223 int bytesPerPixel = GetPixelDataSize(1, 1, image->format); 1224 1225 unsigned char *croppedData = (unsigned char *)RL_MALLOC((int)(crop.width*crop.height)*bytesPerPixel); 1226 1227 // OPTION 1: Move cropped data line-by-line 1228 for (int y = (int)crop.y, offsetSize = 0; y < (int)(crop.y + crop.height); y++) 1229 { 1230 memcpy(croppedData + offsetSize, ((unsigned char *)image->data) + (y*image->width + (int)crop.x)*bytesPerPixel, (int)crop.width*bytesPerPixel); 1231 offsetSize += ((int)crop.width*bytesPerPixel); 1232 } 1233 1234 /* 1235 // OPTION 2: Move cropped data pixel-by-pixel or byte-by-byte 1236 for (int y = (int)crop.y; y < (int)(crop.y + crop.height); y++) 1237 { 1238 for (int x = (int)crop.x; x < (int)(crop.x + crop.width); x++) 1239 { 1240 //memcpy(croppedData + ((y - (int)crop.y)*(int)crop.width + (x - (int)crop.x))*bytesPerPixel, ((unsigned char *)image->data) + (y*image->width + x)*bytesPerPixel, bytesPerPixel); 1241 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]; 1242 } 1243 } 1244 */ 1245 1246 RL_FREE(image->data); 1247 image->data = croppedData; 1248 image->width = (int)crop.width; 1249 image->height = (int)crop.height; 1250 } 1251 } 1252 1253 // Convert image data to desired format 1254 void ImageFormat(Image *image, int newFormat) 1255 { 1256 // Security check to avoid program crash 1257 if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; 1258 1259 if ((newFormat != 0) && (image->format != newFormat)) 1260 { 1261 if ((image->format < PIXELFORMAT_COMPRESSED_DXT1_RGB) && (newFormat < PIXELFORMAT_COMPRESSED_DXT1_RGB)) 1262 { 1263 Vector4 *pixels = LoadImageDataNormalized(*image); // Supports 8 to 32 bit per channel 1264 1265 RL_FREE(image->data); // WARNING! We loose mipmaps data --> Regenerated at the end... 1266 image->data = NULL; 1267 image->format = newFormat; 1268 1269 switch (image->format) 1270 { 1271 case PIXELFORMAT_UNCOMPRESSED_GRAYSCALE: 1272 { 1273 image->data = (unsigned char *)RL_MALLOC(image->width*image->height*sizeof(unsigned char)); 1274 1275 for (int i = 0; i < image->width*image->height; i++) 1276 { 1277 ((unsigned char *)image->data)[i] = (unsigned char)((pixels[i].x*0.299f + pixels[i].y*0.587f + pixels[i].z*0.114f)*255.0f); 1278 } 1279 1280 } break; 1281 case PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA: 1282 { 1283 image->data = (unsigned char *)RL_MALLOC(image->width*image->height*2*sizeof(unsigned char)); 1284 1285 for (int i = 0, k = 0; i < image->width*image->height*2; i += 2, k++) 1286 { 1287 ((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); 1288 ((unsigned char *)image->data)[i + 1] = (unsigned char)(pixels[k].w*255.0f); 1289 } 1290 1291 } break; 1292 case PIXELFORMAT_UNCOMPRESSED_R5G6B5: 1293 { 1294 image->data = (unsigned short *)RL_MALLOC(image->width*image->height*sizeof(unsigned short)); 1295 1296 unsigned char r = 0; 1297 unsigned char g = 0; 1298 unsigned char b = 0; 1299 1300 for (int i = 0; i < image->width*image->height; i++) 1301 { 1302 r = (unsigned char)(round(pixels[i].x*31.0f)); 1303 g = (unsigned char)(round(pixels[i].y*63.0f)); 1304 b = (unsigned char)(round(pixels[i].z*31.0f)); 1305 1306 ((unsigned short *)image->data)[i] = (unsigned short)r << 11 | (unsigned short)g << 5 | (unsigned short)b; 1307 } 1308 1309 } break; 1310 case PIXELFORMAT_UNCOMPRESSED_R8G8B8: 1311 { 1312 image->data = (unsigned char *)RL_MALLOC(image->width*image->height*3*sizeof(unsigned char)); 1313 1314 for (int i = 0, k = 0; i < image->width*image->height*3; i += 3, k++) 1315 { 1316 ((unsigned char *)image->data)[i] = (unsigned char)(pixels[k].x*255.0f); 1317 ((unsigned char *)image->data)[i + 1] = (unsigned char)(pixels[k].y*255.0f); 1318 ((unsigned char *)image->data)[i + 2] = (unsigned char)(pixels[k].z*255.0f); 1319 } 1320 } break; 1321 case PIXELFORMAT_UNCOMPRESSED_R5G5B5A1: 1322 { 1323 image->data = (unsigned short *)RL_MALLOC(image->width*image->height*sizeof(unsigned short)); 1324 1325 unsigned char r = 0; 1326 unsigned char g = 0; 1327 unsigned char b = 0; 1328 unsigned char a = 0; 1329 1330 for (int i = 0; i < image->width*image->height; i++) 1331 { 1332 r = (unsigned char)(round(pixels[i].x*31.0f)); 1333 g = (unsigned char)(round(pixels[i].y*31.0f)); 1334 b = (unsigned char)(round(pixels[i].z*31.0f)); 1335 a = (pixels[i].w > ((float)PIXELFORMAT_UNCOMPRESSED_R5G5B5A1_ALPHA_THRESHOLD/255.0f))? 1 : 0; 1336 1337 ((unsigned short *)image->data)[i] = (unsigned short)r << 11 | (unsigned short)g << 6 | (unsigned short)b << 1 | (unsigned short)a; 1338 } 1339 1340 } break; 1341 case PIXELFORMAT_UNCOMPRESSED_R4G4B4A4: 1342 { 1343 image->data = (unsigned short *)RL_MALLOC(image->width*image->height*sizeof(unsigned short)); 1344 1345 unsigned char r = 0; 1346 unsigned char g = 0; 1347 unsigned char b = 0; 1348 unsigned char a = 0; 1349 1350 for (int i = 0; i < image->width*image->height; i++) 1351 { 1352 r = (unsigned char)(round(pixels[i].x*15.0f)); 1353 g = (unsigned char)(round(pixels[i].y*15.0f)); 1354 b = (unsigned char)(round(pixels[i].z*15.0f)); 1355 a = (unsigned char)(round(pixels[i].w*15.0f)); 1356 1357 ((unsigned short *)image->data)[i] = (unsigned short)r << 12 | (unsigned short)g << 8 | (unsigned short)b << 4 | (unsigned short)a; 1358 } 1359 1360 } break; 1361 case PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: 1362 { 1363 image->data = (unsigned char *)RL_MALLOC(image->width*image->height*4*sizeof(unsigned char)); 1364 1365 for (int i = 0, k = 0; i < image->width*image->height*4; i += 4, k++) 1366 { 1367 ((unsigned char *)image->data)[i] = (unsigned char)(pixels[k].x*255.0f); 1368 ((unsigned char *)image->data)[i + 1] = (unsigned char)(pixels[k].y*255.0f); 1369 ((unsigned char *)image->data)[i + 2] = (unsigned char)(pixels[k].z*255.0f); 1370 ((unsigned char *)image->data)[i + 3] = (unsigned char)(pixels[k].w*255.0f); 1371 } 1372 } break; 1373 case PIXELFORMAT_UNCOMPRESSED_R32: 1374 { 1375 // WARNING: Image is converted to GRAYSCALE equivalent 32bit 1376 1377 image->data = (float *)RL_MALLOC(image->width*image->height*sizeof(float)); 1378 1379 for (int i = 0; i < image->width*image->height; i++) 1380 { 1381 ((float *)image->data)[i] = (float)(pixels[i].x*0.299f + pixels[i].y*0.587f + pixels[i].z*0.114f); 1382 } 1383 } break; 1384 case PIXELFORMAT_UNCOMPRESSED_R32G32B32: 1385 { 1386 image->data = (float *)RL_MALLOC(image->width*image->height*3*sizeof(float)); 1387 1388 for (int i = 0, k = 0; i < image->width*image->height*3; i += 3, k++) 1389 { 1390 ((float *)image->data)[i] = pixels[k].x; 1391 ((float *)image->data)[i + 1] = pixels[k].y; 1392 ((float *)image->data)[i + 2] = pixels[k].z; 1393 } 1394 } break; 1395 case PIXELFORMAT_UNCOMPRESSED_R32G32B32A32: 1396 { 1397 image->data = (float *)RL_MALLOC(image->width*image->height*4*sizeof(float)); 1398 1399 for (int i = 0, k = 0; i < image->width*image->height*4; i += 4, k++) 1400 { 1401 ((float *)image->data)[i] = pixels[k].x; 1402 ((float *)image->data)[i + 1] = pixels[k].y; 1403 ((float *)image->data)[i + 2] = pixels[k].z; 1404 ((float *)image->data)[i + 3] = pixels[k].w; 1405 } 1406 } break; 1407 case PIXELFORMAT_UNCOMPRESSED_R16: 1408 { 1409 // WARNING: Image is converted to GRAYSCALE equivalent 16bit 1410 1411 image->data = (unsigned short *)RL_MALLOC(image->width*image->height*sizeof(unsigned short)); 1412 1413 for (int i = 0; i < image->width*image->height; i++) 1414 { 1415 ((unsigned short *)image->data)[i] = FloatToHalf((float)(pixels[i].x*0.299f + pixels[i].y*0.587f + pixels[i].z*0.114f)); 1416 } 1417 } break; 1418 case PIXELFORMAT_UNCOMPRESSED_R16G16B16: 1419 { 1420 image->data = (unsigned short *)RL_MALLOC(image->width*image->height*3*sizeof(unsigned short)); 1421 1422 for (int i = 0, k = 0; i < image->width*image->height*3; i += 3, k++) 1423 { 1424 ((unsigned short *)image->data)[i] = FloatToHalf(pixels[k].x); 1425 ((unsigned short *)image->data)[i + 1] = FloatToHalf(pixels[k].y); 1426 ((unsigned short *)image->data)[i + 2] = FloatToHalf(pixels[k].z); 1427 } 1428 } break; 1429 case PIXELFORMAT_UNCOMPRESSED_R16G16B16A16: 1430 { 1431 image->data = (unsigned short *)RL_MALLOC(image->width*image->height*4*sizeof(unsigned short)); 1432 1433 for (int i = 0, k = 0; i < image->width*image->height*4; i += 4, k++) 1434 { 1435 ((unsigned short *)image->data)[i] = FloatToHalf(pixels[k].x); 1436 ((unsigned short *)image->data)[i + 1] = FloatToHalf(pixels[k].y); 1437 ((unsigned short *)image->data)[i + 2] = FloatToHalf(pixels[k].z); 1438 ((unsigned short *)image->data)[i + 3] = FloatToHalf(pixels[k].w); 1439 } 1440 } break; 1441 default: break; 1442 } 1443 1444 RL_FREE(pixels); 1445 pixels = NULL; 1446 1447 // In case original image had mipmaps, generate mipmaps for formatted image 1448 // NOTE: Original mipmaps are replaced by new ones, if custom mipmaps were used, they are lost 1449 if (image->mipmaps > 1) 1450 { 1451 image->mipmaps = 1; 1452 #if defined(SUPPORT_IMAGE_MANIPULATION) 1453 if (image->data != NULL) ImageMipmaps(image); 1454 #endif 1455 } 1456 } 1457 else TRACELOG(LOG_WARNING, "IMAGE: Data format is compressed, can not be converted"); 1458 } 1459 } 1460 1461 // Create an image from text (default font) 1462 Image ImageText(const char *text, int fontSize, Color color) 1463 { 1464 Image imText = { 0 }; 1465 #if defined(SUPPORT_MODULE_RTEXT) 1466 int defaultFontSize = 10; // Default Font chars height in pixel 1467 if (fontSize < defaultFontSize) fontSize = defaultFontSize; 1468 int spacing = fontSize/defaultFontSize; 1469 imText = ImageTextEx(GetFontDefault(), text, (float)fontSize, (float)spacing, color); // WARNING: Module required: rtext 1470 #else 1471 imText = GenImageColor(200, 60, BLACK); // Generating placeholder black image rectangle 1472 TRACELOG(LOG_WARNING, "IMAGE: ImageTextEx() requires module: rtext"); 1473 #endif 1474 return imText; 1475 } 1476 1477 // Create an image from text (custom sprite font) 1478 // WARNING: Module required: rtext 1479 Image ImageTextEx(Font font, const char *text, float fontSize, float spacing, Color tint) 1480 { 1481 Image imText = { 0 }; 1482 #if defined(SUPPORT_MODULE_RTEXT) 1483 int size = (int)strlen(text); // Get size in bytes of text 1484 1485 int textOffsetX = 0; // Image drawing position X 1486 int textOffsetY = 0; // Offset between lines (on linebreak '\n') 1487 1488 // NOTE: Text image is generated at font base size, later scaled to desired font size 1489 Vector2 imSize = MeasureTextEx(font, text, (float)font.baseSize, spacing); // WARNING: Module required: rtext 1490 Vector2 textSize = MeasureTextEx(font, text, fontSize, spacing); 1491 1492 // Create image to store text 1493 imText = GenImageColor((int)imSize.x, (int)imSize.y, BLANK); 1494 1495 for (int i = 0; i < size;) 1496 { 1497 // Get next codepoint from byte string and glyph index in font 1498 int codepointByteCount = 0; 1499 int codepoint = GetCodepointNext(&text[i], &codepointByteCount); // WARNING: Module required: rtext 1500 int index = GetGlyphIndex(font, codepoint); // WARNING: Module required: rtext 1501 1502 if (codepoint == '\n') 1503 { 1504 // NOTE: Fixed line spacing of 1.5 line-height 1505 // TODO: Support custom line spacing defined by user 1506 textOffsetY += (font.baseSize + font.baseSize/2); 1507 textOffsetX = 0; 1508 } 1509 else 1510 { 1511 if ((codepoint != ' ') && (codepoint != '\t')) 1512 { 1513 Rectangle rec = { (float)(textOffsetX + font.glyphs[index].offsetX), (float)(textOffsetY + font.glyphs[index].offsetY), (float)font.recs[index].width, (float)font.recs[index].height }; 1514 ImageDraw(&imText, font.glyphs[index].image, (Rectangle){ 0, 0, (float)font.glyphs[index].image.width, (float)font.glyphs[index].image.height }, rec, tint); 1515 } 1516 1517 if (font.glyphs[index].advanceX == 0) textOffsetX += (int)(font.recs[index].width + spacing); 1518 else textOffsetX += font.glyphs[index].advanceX + (int)spacing; 1519 } 1520 1521 i += codepointByteCount; // Move text bytes counter to next codepoint 1522 } 1523 1524 // Scale image depending on text size 1525 if (textSize.y != imSize.y) 1526 { 1527 float scaleFactor = textSize.y/imSize.y; 1528 TRACELOG(LOG_INFO, "IMAGE: Text scaled by factor: %f", scaleFactor); 1529 1530 // Using nearest-neighbor scaling algorithm for default font 1531 // TODO: Allow defining the preferred scaling mechanism externally 1532 if (font.texture.id == GetFontDefault().texture.id) ImageResizeNN(&imText, (int)(imSize.x*scaleFactor), (int)(imSize.y*scaleFactor)); 1533 else ImageResize(&imText, (int)(imSize.x*scaleFactor), (int)(imSize.y*scaleFactor)); 1534 } 1535 #else 1536 imText = GenImageColor(200, 60, BLACK); // Generating placeholder black image rectangle 1537 TRACELOG(LOG_WARNING, "IMAGE: ImageTextEx() requires module: rtext"); 1538 #endif 1539 return imText; 1540 } 1541 1542 // Create an image from a selected channel of another image 1543 Image ImageFromChannel(Image image, int selectedChannel) 1544 { 1545 Image result = { 0 }; 1546 1547 // Security check to avoid program crash 1548 if ((image.data == NULL) || (image.width == 0) || (image.height == 0)) return result; 1549 1550 // Check selected channel is valid 1551 if (selectedChannel < 0) 1552 { 1553 TRACELOG(LOG_WARNING, "Channel cannot be negative. Setting channel to 0."); 1554 selectedChannel = 0; 1555 } 1556 1557 if (image.format == PIXELFORMAT_UNCOMPRESSED_GRAYSCALE || 1558 image.format == PIXELFORMAT_UNCOMPRESSED_R32 || 1559 image.format == PIXELFORMAT_UNCOMPRESSED_R16) 1560 { 1561 if (selectedChannel > 0) 1562 { 1563 TRACELOG(LOG_WARNING, "This image has only 1 channel. Setting channel to it."); 1564 selectedChannel = 0; 1565 } 1566 } 1567 else if (image.format == PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA) 1568 { 1569 if (selectedChannel > 1) 1570 { 1571 TRACELOG(LOG_WARNING, "This image has only 2 channels. Setting channel to alpha."); 1572 selectedChannel = 1; 1573 } 1574 } 1575 else if (image.format == PIXELFORMAT_UNCOMPRESSED_R5G6B5 || 1576 image.format == PIXELFORMAT_UNCOMPRESSED_R8G8B8 || 1577 image.format == PIXELFORMAT_UNCOMPRESSED_R32G32B32 || 1578 image.format == PIXELFORMAT_UNCOMPRESSED_R16G16B16) 1579 { 1580 if (selectedChannel > 2) 1581 { 1582 TRACELOG(LOG_WARNING, "This image has only 3 channels. Setting channel to red."); 1583 selectedChannel = 0; 1584 } 1585 } 1586 1587 // Check for RGBA formats 1588 if (selectedChannel > 3) 1589 { 1590 TRACELOG(LOG_WARNING, "ImageFromChannel supports channels 0 to 3 (rgba). Setting channel to alpha."); 1591 selectedChannel = 3; 1592 } 1593 1594 // TODO: Consider other one-channel formats: R16, R32 1595 result.format = PIXELFORMAT_UNCOMPRESSED_GRAYSCALE; 1596 result.height = image.height; 1597 result.width = image.width; 1598 result.mipmaps = 1; 1599 1600 unsigned char *pixels = (unsigned char *)RL_CALLOC(image.width*image.height, sizeof(unsigned char)); // Values from 0 to 255 1601 1602 if (image.format >= PIXELFORMAT_COMPRESSED_DXT1_RGB) TRACELOG(LOG_WARNING, "IMAGE: Pixel data retrieval not supported for compressed image formats"); 1603 else 1604 { 1605 for (int i = 0, k = 0; i < image.width*image.height; i++) 1606 { 1607 float pixelValue = -1; 1608 switch (image.format) 1609 { 1610 case PIXELFORMAT_UNCOMPRESSED_GRAYSCALE: 1611 { 1612 pixelValue = (float)((unsigned char *)image.data)[i + selectedChannel]/255.0f; 1613 1614 } break; 1615 case PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA: 1616 { 1617 pixelValue = (float)((unsigned char *)image.data)[k + selectedChannel]/255.0f; 1618 k += 2; 1619 1620 } break; 1621 case PIXELFORMAT_UNCOMPRESSED_R5G5B5A1: 1622 { 1623 unsigned short pixel = ((unsigned short *)image.data)[i]; 1624 1625 if (selectedChannel == 0) pixelValue = (float)((pixel & 0b1111100000000000) >> 11)*(1.0f/31); 1626 else if (selectedChannel == 1) pixelValue = (float)((pixel & 0b0000011111000000) >> 6)*(1.0f/31); 1627 else if (selectedChannel == 2) pixelValue = (float)((pixel & 0b0000000000111110) >> 1)*(1.0f/31); 1628 else if (selectedChannel == 3) pixelValue = ((pixel & 0b0000000000000001) == 0)? 0.0f : 1.0f; 1629 1630 } break; 1631 case PIXELFORMAT_UNCOMPRESSED_R5G6B5: 1632 { 1633 unsigned short pixel = ((unsigned short *)image.data)[i]; 1634 1635 if (selectedChannel == 0) pixelValue = (float)((pixel & 0b1111100000000000) >> 11)*(1.0f/31); 1636 else if (selectedChannel == 1) pixelValue = (float)((pixel & 0b0000011111100000) >> 5)*(1.0f/63); 1637 else if (selectedChannel == 2) pixelValue = (float)(pixel & 0b0000000000011111)*(1.0f/31); 1638 1639 } break; 1640 case PIXELFORMAT_UNCOMPRESSED_R4G4B4A4: 1641 { 1642 unsigned short pixel = ((unsigned short *)image.data)[i]; 1643 1644 if (selectedChannel == 0) pixelValue = (float)((pixel & 0b1111000000000000) >> 12)*(1.0f/15); 1645 else if (selectedChannel == 1) pixelValue = (float)((pixel & 0b0000111100000000) >> 8)*(1.0f/15); 1646 else if (selectedChannel == 2) pixelValue = (float)((pixel & 0b0000000011110000) >> 4)*(1.0f/15); 1647 else if (selectedChannel == 3) pixelValue = (float)(pixel & 0b0000000000001111)*(1.0f/15); 1648 1649 } break; 1650 case PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: 1651 { 1652 pixelValue = (float)((unsigned char *)image.data)[k + selectedChannel]/255.0f; 1653 k += 4; 1654 1655 } break; 1656 case PIXELFORMAT_UNCOMPRESSED_R8G8B8: 1657 { 1658 pixelValue = (float)((unsigned char *)image.data)[k + selectedChannel]/255.0f; 1659 k += 3; 1660 1661 } break; 1662 case PIXELFORMAT_UNCOMPRESSED_R32: 1663 { 1664 pixelValue = ((float *)image.data)[k]; 1665 k += 1; 1666 1667 } break; 1668 case PIXELFORMAT_UNCOMPRESSED_R32G32B32: 1669 { 1670 pixelValue = ((float *)image.data)[k + selectedChannel]; 1671 k += 3; 1672 1673 } break; 1674 case PIXELFORMAT_UNCOMPRESSED_R32G32B32A32: 1675 { 1676 pixelValue = ((float *)image.data)[k + selectedChannel]; 1677 k += 4; 1678 1679 } break; 1680 case PIXELFORMAT_UNCOMPRESSED_R16: 1681 { 1682 pixelValue = HalfToFloat(((unsigned short *)image.data)[k]); 1683 k += 1; 1684 1685 } break; 1686 case PIXELFORMAT_UNCOMPRESSED_R16G16B16: 1687 { 1688 pixelValue = HalfToFloat(((unsigned short *)image.data)[k+selectedChannel]); 1689 k += 3; 1690 1691 } break; 1692 case PIXELFORMAT_UNCOMPRESSED_R16G16B16A16: 1693 { 1694 pixelValue = HalfToFloat(((unsigned short *)image.data)[k + selectedChannel]); 1695 k += 4; 1696 1697 } break; 1698 default: break; 1699 } 1700 1701 pixels[i] = (unsigned char)(pixelValue*255); 1702 } 1703 } 1704 1705 result.data = pixels; 1706 1707 return result; 1708 } 1709 1710 // Resize and image to new size using Nearest-Neighbor scaling algorithm 1711 void ImageResizeNN(Image *image,int newWidth,int newHeight) 1712 { 1713 // Security check to avoid program crash 1714 if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; 1715 1716 Color *pixels = LoadImageColors(*image); 1717 Color *output = (Color *)RL_MALLOC(newWidth*newHeight*sizeof(Color)); 1718 1719 // EDIT: added +1 to account for an early rounding problem 1720 int xRatio = (int)((image->width << 16)/newWidth) + 1; 1721 int yRatio = (int)((image->height << 16)/newHeight) + 1; 1722 1723 int x2, y2; 1724 for (int y = 0; y < newHeight; y++) 1725 { 1726 for (int x = 0; x < newWidth; x++) 1727 { 1728 x2 = ((x*xRatio) >> 16); 1729 y2 = ((y*yRatio) >> 16); 1730 1731 output[(y*newWidth) + x] = pixels[(y2*image->width) + x2] ; 1732 } 1733 } 1734 1735 int format = image->format; 1736 1737 RL_FREE(image->data); 1738 1739 image->data = output; 1740 image->width = newWidth; 1741 image->height = newHeight; 1742 image->format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8; 1743 1744 ImageFormat(image, format); // Reformat 32bit RGBA image to original format 1745 1746 UnloadImageColors(pixels); 1747 } 1748 1749 // Resize and image to new size 1750 // NOTE: Uses stb default scaling filters (both bicubic): 1751 // STBIR_DEFAULT_FILTER_UPSAMPLE STBIR_FILTER_CATMULLROM 1752 // STBIR_DEFAULT_FILTER_DOWNSAMPLE STBIR_FILTER_MITCHELL (high-quality Catmull-Rom) 1753 void ImageResize(Image *image, int newWidth, int newHeight) 1754 { 1755 // Security check to avoid program crash 1756 if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; 1757 1758 // Check if we can use a fast path on image scaling 1759 // It can be for 8 bit per channel images with 1 to 4 channels per pixel 1760 if ((image->format == PIXELFORMAT_UNCOMPRESSED_GRAYSCALE) || 1761 (image->format == PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA) || 1762 (image->format == PIXELFORMAT_UNCOMPRESSED_R8G8B8) || 1763 (image->format == PIXELFORMAT_UNCOMPRESSED_R8G8B8A8)) 1764 { 1765 int bytesPerPixel = GetPixelDataSize(1, 1, image->format); 1766 unsigned char *output = (unsigned char *)RL_MALLOC(newWidth*newHeight*bytesPerPixel); 1767 1768 switch (image->format) 1769 { 1770 case PIXELFORMAT_UNCOMPRESSED_GRAYSCALE: stbir_resize_uint8_linear((unsigned char *)image->data, image->width, image->height, 0, output, newWidth, newHeight, 0, (stbir_pixel_layout)1); break; 1771 case PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA: stbir_resize_uint8_linear((unsigned char *)image->data, image->width, image->height, 0, output, newWidth, newHeight, 0, (stbir_pixel_layout)2); break; 1772 case PIXELFORMAT_UNCOMPRESSED_R8G8B8: stbir_resize_uint8_linear((unsigned char *)image->data, image->width, image->height, 0, output, newWidth, newHeight, 0, (stbir_pixel_layout)3); break; 1773 case PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: stbir_resize_uint8_linear((unsigned char *)image->data, image->width, image->height, 0, output, newWidth, newHeight, 0, (stbir_pixel_layout)4); break; 1774 default: break; 1775 } 1776 1777 RL_FREE(image->data); 1778 image->data = output; 1779 image->width = newWidth; 1780 image->height = newHeight; 1781 } 1782 else 1783 { 1784 // Get data as Color pixels array to work with it 1785 Color *pixels = LoadImageColors(*image); 1786 Color *output = (Color *)RL_MALLOC(newWidth*newHeight*sizeof(Color)); 1787 1788 // NOTE: Color data is cast to (unsigned char *), there shouldn't been any problem... 1789 stbir_resize_uint8_linear((unsigned char *)pixels, image->width, image->height, 0, (unsigned char *)output, newWidth, newHeight, 0, (stbir_pixel_layout)4); 1790 1791 int format = image->format; 1792 1793 UnloadImageColors(pixels); 1794 RL_FREE(image->data); 1795 1796 image->data = output; 1797 image->width = newWidth; 1798 image->height = newHeight; 1799 image->format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8; 1800 1801 ImageFormat(image, format); // Reformat 32bit RGBA image to original format 1802 } 1803 } 1804 1805 // Resize canvas and fill with color 1806 // NOTE: Resize offset is relative to the top-left corner of the original image 1807 void ImageResizeCanvas(Image *image, int newWidth, int newHeight, int offsetX, int offsetY, Color fill) 1808 { 1809 // Security check to avoid program crash 1810 if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; 1811 1812 if (image->mipmaps > 1) TRACELOG(LOG_WARNING, "Image manipulation only applied to base mipmap level"); 1813 if (image->format >= PIXELFORMAT_COMPRESSED_DXT1_RGB) TRACELOG(LOG_WARNING, "Image manipulation not supported for compressed formats"); 1814 else if ((newWidth != image->width) || (newHeight != image->height)) 1815 { 1816 Rectangle srcRec = { 0, 0, (float)image->width, (float)image->height }; 1817 Vector2 dstPos = { (float)offsetX, (float)offsetY }; 1818 1819 if (offsetX < 0) 1820 { 1821 srcRec.x = (float)-offsetX; 1822 srcRec.width += (float)offsetX; 1823 dstPos.x = 0; 1824 } 1825 else if ((offsetX + image->width) > newWidth) srcRec.width = (float)(newWidth - offsetX); 1826 1827 if (offsetY < 0) 1828 { 1829 srcRec.y = (float)-offsetY; 1830 srcRec.height += (float)offsetY; 1831 dstPos.y = 0; 1832 } 1833 else if ((offsetY + image->height) > newHeight) srcRec.height = (float)(newHeight - offsetY); 1834 1835 if (newWidth < srcRec.width) srcRec.width = (float)newWidth; 1836 if (newHeight < srcRec.height) srcRec.height = (float)newHeight; 1837 1838 int bytesPerPixel = GetPixelDataSize(1, 1, image->format); 1839 unsigned char *resizedData = (unsigned char *)RL_CALLOC(newWidth*newHeight*bytesPerPixel, 1); 1840 1841 // Fill resized canvas with fill color 1842 // Set first pixel with image->format 1843 SetPixelColor(resizedData, fill, image->format); 1844 1845 // Fill remaining bytes of first row 1846 for (int x = 1; x < newWidth; x++) 1847 { 1848 memcpy(resizedData + x*bytesPerPixel, resizedData, bytesPerPixel); 1849 } 1850 // Copy the first row into the other rows 1851 for (int y = 1; y < newHeight; y++) 1852 { 1853 memcpy(resizedData + y*newWidth*bytesPerPixel, resizedData, newWidth*bytesPerPixel); 1854 } 1855 1856 // Copy old image to resized canvas 1857 int dstOffsetSize = ((int)dstPos.y*newWidth + (int)dstPos.x)*bytesPerPixel; 1858 1859 for (int y = 0; y < (int)srcRec.height; y++) 1860 { 1861 memcpy(resizedData + dstOffsetSize, ((unsigned char *)image->data) + ((y + (int)srcRec.y)*image->width + (int)srcRec.x)*bytesPerPixel, (int)srcRec.width*bytesPerPixel); 1862 dstOffsetSize += (newWidth*bytesPerPixel); 1863 } 1864 1865 RL_FREE(image->data); 1866 image->data = resizedData; 1867 image->width = newWidth; 1868 image->height = newHeight; 1869 } 1870 } 1871 1872 #if defined(SUPPORT_IMAGE_MANIPULATION) 1873 // Convert image to POT (power-of-two) 1874 // NOTE: It could be useful on OpenGL ES 2.0 (RPI, HTML5) 1875 void ImageToPOT(Image *image, Color fill) 1876 { 1877 // Security check to avoid program crash 1878 if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; 1879 1880 // Calculate next power-of-two values 1881 // NOTE: Just add the required amount of pixels at the right and bottom sides of image... 1882 int potWidth = (int)powf(2, ceilf(logf((float)image->width)/logf(2))); 1883 int potHeight = (int)powf(2, ceilf(logf((float)image->height)/logf(2))); 1884 1885 // Check if POT texture generation is required (if texture is not already POT) 1886 if ((potWidth != image->width) || (potHeight != image->height)) ImageResizeCanvas(image, potWidth, potHeight, 0, 0, fill); 1887 } 1888 1889 // Crop image depending on alpha value 1890 // NOTE: Threshold is defined as a percentage: 0.0f -> 1.0f 1891 void ImageAlphaCrop(Image *image, float threshold) 1892 { 1893 // Security check to avoid program crash 1894 if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; 1895 1896 Rectangle crop = GetImageAlphaBorder(*image, threshold); 1897 1898 // Crop if rectangle is valid 1899 if (((int)crop.width != 0) && ((int)crop.height != 0)) ImageCrop(image, crop); 1900 } 1901 1902 // Clear alpha channel to desired color 1903 // NOTE: Threshold defines the alpha limit, 0.0f to 1.0f 1904 void ImageAlphaClear(Image *image, Color color, float threshold) 1905 { 1906 // Security check to avoid program crash 1907 if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; 1908 1909 if (image->mipmaps > 1) TRACELOG(LOG_WARNING, "Image manipulation only applied to base mipmap level"); 1910 if (image->format >= PIXELFORMAT_COMPRESSED_DXT1_RGB) TRACELOG(LOG_WARNING, "Image manipulation not supported for compressed formats"); 1911 else 1912 { 1913 switch (image->format) 1914 { 1915 case PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA: 1916 { 1917 unsigned char thresholdValue = (unsigned char)(threshold*255.0f); 1918 for (int i = 1; i < image->width*image->height*2; i += 2) 1919 { 1920 if (((unsigned char *)image->data)[i] <= thresholdValue) 1921 { 1922 ((unsigned char *)image->data)[i - 1] = color.r; 1923 ((unsigned char *)image->data)[i] = color.a; 1924 } 1925 } 1926 } break; 1927 case PIXELFORMAT_UNCOMPRESSED_R5G5B5A1: 1928 { 1929 unsigned char thresholdValue = ((threshold < 0.5f)? 0 : 1); 1930 1931 unsigned char r = (unsigned char)(round((float)color.r*31.0f)); 1932 unsigned char g = (unsigned char)(round((float)color.g*31.0f)); 1933 unsigned char b = (unsigned char)(round((float)color.b*31.0f)); 1934 unsigned char a = (color.a < 128)? 0 : 1; 1935 1936 for (int i = 0; i < image->width*image->height; i++) 1937 { 1938 if ((((unsigned short *)image->data)[i] & 0b0000000000000001) <= thresholdValue) 1939 { 1940 ((unsigned short *)image->data)[i] = (unsigned short)r << 11 | (unsigned short)g << 6 | (unsigned short)b << 1 | (unsigned short)a; 1941 } 1942 } 1943 } break; 1944 case PIXELFORMAT_UNCOMPRESSED_R4G4B4A4: 1945 { 1946 unsigned char thresholdValue = (unsigned char)(threshold*15.0f); 1947 1948 unsigned char r = (unsigned char)(round((float)color.r*15.0f)); 1949 unsigned char g = (unsigned char)(round((float)color.g*15.0f)); 1950 unsigned char b = (unsigned char)(round((float)color.b*15.0f)); 1951 unsigned char a = (unsigned char)(round((float)color.a*15.0f)); 1952 1953 for (int i = 0; i < image->width*image->height; i++) 1954 { 1955 if ((((unsigned short *)image->data)[i] & 0x000f) <= thresholdValue) 1956 { 1957 ((unsigned short *)image->data)[i] = (unsigned short)r << 12 | (unsigned short)g << 8 | (unsigned short)b << 4 | (unsigned short)a; 1958 } 1959 } 1960 } break; 1961 case PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: 1962 { 1963 unsigned char thresholdValue = (unsigned char)(threshold*255.0f); 1964 for (int i = 3; i < image->width*image->height*4; i += 4) 1965 { 1966 if (((unsigned char *)image->data)[i] <= thresholdValue) 1967 { 1968 ((unsigned char *)image->data)[i - 3] = color.r; 1969 ((unsigned char *)image->data)[i - 2] = color.g; 1970 ((unsigned char *)image->data)[i - 1] = color.b; 1971 ((unsigned char *)image->data)[i] = color.a; 1972 } 1973 } 1974 } break; 1975 case PIXELFORMAT_UNCOMPRESSED_R32G32B32A32: 1976 { 1977 for (int i = 3; i < image->width*image->height*4; i += 4) 1978 { 1979 if (((float *)image->data)[i] <= threshold) 1980 { 1981 ((float *)image->data)[i - 3] = (float)color.r/255.0f; 1982 ((float *)image->data)[i - 2] = (float)color.g/255.0f; 1983 ((float *)image->data)[i - 1] = (float)color.b/255.0f; 1984 ((float *)image->data)[i] = (float)color.a/255.0f; 1985 } 1986 } 1987 } break; 1988 case PIXELFORMAT_UNCOMPRESSED_R16G16B16A16: 1989 { 1990 for (int i = 3; i < image->width*image->height*4; i += 4) 1991 { 1992 if (HalfToFloat(((unsigned short *)image->data)[i]) <= threshold) 1993 { 1994 ((unsigned short *)image->data)[i - 3] = FloatToHalf((float)color.r/255.0f); 1995 ((unsigned short *)image->data)[i - 2] = FloatToHalf((float)color.g/255.0f); 1996 ((unsigned short *)image->data)[i - 1] = FloatToHalf((float)color.b/255.0f); 1997 ((unsigned short *)image->data)[i] = FloatToHalf((float)color.a/255.0f); 1998 } 1999 } 2000 } break; 2001 default: break; 2002 } 2003 } 2004 } 2005 2006 // Apply alpha mask to image 2007 // NOTE 1: Returned image is GRAY_ALPHA (16bit) or RGBA (32bit) 2008 // NOTE 2: alphaMask should be same size as image 2009 void ImageAlphaMask(Image *image, Image alphaMask) 2010 { 2011 if ((image->width != alphaMask.width) || (image->height != alphaMask.height)) 2012 { 2013 TRACELOG(LOG_WARNING, "IMAGE: Alpha mask must be same size as image"); 2014 } 2015 else if (image->format >= PIXELFORMAT_COMPRESSED_DXT1_RGB) 2016 { 2017 TRACELOG(LOG_WARNING, "IMAGE: Alpha mask can not be applied to compressed data formats"); 2018 } 2019 else 2020 { 2021 // Force mask to be Grayscale 2022 Image mask = ImageCopy(alphaMask); 2023 if (mask.format != PIXELFORMAT_UNCOMPRESSED_GRAYSCALE) ImageFormat(&mask, PIXELFORMAT_UNCOMPRESSED_GRAYSCALE); 2024 2025 // In case image is only grayscale, we just add alpha channel 2026 if (image->format == PIXELFORMAT_UNCOMPRESSED_GRAYSCALE) 2027 { 2028 unsigned char *data = (unsigned char *)RL_MALLOC(image->width*image->height*2); 2029 2030 // Apply alpha mask to alpha channel 2031 for (int i = 0, k = 0; (i < mask.width*mask.height) || (i < image->width*image->height); i++, k += 2) 2032 { 2033 data[k] = ((unsigned char *)image->data)[i]; 2034 data[k + 1] = ((unsigned char *)mask.data)[i]; 2035 } 2036 2037 RL_FREE(image->data); 2038 image->data = data; 2039 image->format = PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA; 2040 } 2041 else 2042 { 2043 // Convert image to RGBA 2044 if (image->format != PIXELFORMAT_UNCOMPRESSED_R8G8B8A8) ImageFormat(image, PIXELFORMAT_UNCOMPRESSED_R8G8B8A8); 2045 2046 // Apply alpha mask to alpha channel 2047 for (int i = 0, k = 3; (i < mask.width*mask.height) || (i < image->width*image->height); i++, k += 4) 2048 { 2049 ((unsigned char *)image->data)[k] = ((unsigned char *)mask.data)[i]; 2050 } 2051 } 2052 2053 UnloadImage(mask); 2054 } 2055 } 2056 2057 // Premultiply alpha channel 2058 void ImageAlphaPremultiply(Image *image) 2059 { 2060 // Security check to avoid program crash 2061 if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; 2062 2063 float alpha = 0.0f; 2064 Color *pixels = LoadImageColors(*image); 2065 2066 for (int i = 0; i < image->width*image->height; i++) 2067 { 2068 if (pixels[i].a == 0) 2069 { 2070 pixels[i].r = 0; 2071 pixels[i].g = 0; 2072 pixels[i].b = 0; 2073 } 2074 else if (pixels[i].a < 255) 2075 { 2076 alpha = (float)pixels[i].a/255.0f; 2077 pixels[i].r = (unsigned char)((float)pixels[i].r*alpha); 2078 pixels[i].g = (unsigned char)((float)pixels[i].g*alpha); 2079 pixels[i].b = (unsigned char)((float)pixels[i].b*alpha); 2080 } 2081 } 2082 2083 RL_FREE(image->data); 2084 2085 int format = image->format; 2086 image->data = pixels; 2087 image->format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8; 2088 2089 ImageFormat(image, format); 2090 } 2091 2092 // Apply box blur to image 2093 void ImageBlurGaussian(Image *image, int blurSize) 2094 { 2095 // Security check to avoid program crash 2096 if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; 2097 2098 ImageAlphaPremultiply(image); 2099 2100 Color *pixels = LoadImageColors(*image); 2101 2102 // Loop switches between pixelsCopy1 and pixelsCopy2 2103 Vector4 *pixelsCopy1 = RL_MALLOC((image->height)*(image->width)*sizeof(Vector4)); 2104 Vector4 *pixelsCopy2 = RL_MALLOC((image->height)*(image->width)*sizeof(Vector4)); 2105 2106 for (int i = 0; i < (image->height*image->width); i++) 2107 { 2108 pixelsCopy1[i].x = pixels[i].r; 2109 pixelsCopy1[i].y = pixels[i].g; 2110 pixelsCopy1[i].z = pixels[i].b; 2111 pixelsCopy1[i].w = pixels[i].a; 2112 } 2113 2114 // Repeated convolution of rectangular window signal by itself converges to a gaussian distribution 2115 for (int j = 0; j < GAUSSIAN_BLUR_ITERATIONS; j++) 2116 { 2117 // Horizontal motion blur 2118 for (int row = 0; row < image->height; row++) 2119 { 2120 float avgR = 0.0f; 2121 float avgG = 0.0f; 2122 float avgB = 0.0f; 2123 float avgAlpha = 0.0f; 2124 int convolutionSize = blurSize; 2125 2126 for (int i = 0; i < blurSize; i++) 2127 { 2128 avgR += pixelsCopy1[row*image->width + i].x; 2129 avgG += pixelsCopy1[row*image->width + i].y; 2130 avgB += pixelsCopy1[row*image->width + i].z; 2131 avgAlpha += pixelsCopy1[row*image->width + i].w; 2132 } 2133 2134 for (int x = 0; x < image->width; x++) 2135 { 2136 if (x-blurSize-1 >= 0) 2137 { 2138 avgR -= pixelsCopy1[row*image->width + x-blurSize-1].x; 2139 avgG -= pixelsCopy1[row*image->width + x-blurSize-1].y; 2140 avgB -= pixelsCopy1[row*image->width + x-blurSize-1].z; 2141 avgAlpha -= pixelsCopy1[row*image->width + x-blurSize-1].w; 2142 convolutionSize--; 2143 } 2144 2145 if (x+blurSize < image->width) 2146 { 2147 avgR += pixelsCopy1[row*image->width + x+blurSize].x; 2148 avgG += pixelsCopy1[row*image->width + x+blurSize].y; 2149 avgB += pixelsCopy1[row*image->width + x+blurSize].z; 2150 avgAlpha += pixelsCopy1[row*image->width + x+blurSize].w; 2151 convolutionSize++; 2152 } 2153 2154 pixelsCopy2[row*image->width + x].x = avgR/convolutionSize; 2155 pixelsCopy2[row*image->width + x].y = avgG/convolutionSize; 2156 pixelsCopy2[row*image->width + x].z = avgB/convolutionSize; 2157 pixelsCopy2[row*image->width + x].w = avgAlpha/convolutionSize; 2158 } 2159 } 2160 2161 // Vertical motion blur 2162 for (int col = 0; col < image->width; col++) 2163 { 2164 float avgR = 0.0f; 2165 float avgG = 0.0f; 2166 float avgB = 0.0f; 2167 float avgAlpha = 0.0f; 2168 int convolutionSize = blurSize; 2169 2170 for (int i = 0; i < blurSize; i++) 2171 { 2172 avgR += pixelsCopy2[i*image->width + col].x; 2173 avgG += pixelsCopy2[i*image->width + col].y; 2174 avgB += pixelsCopy2[i*image->width + col].z; 2175 avgAlpha += pixelsCopy2[i*image->width + col].w; 2176 } 2177 2178 for (int y = 0; y < image->height; y++) 2179 { 2180 if (y-blurSize-1 >= 0) 2181 { 2182 avgR -= pixelsCopy2[(y-blurSize-1)*image->width + col].x; 2183 avgG -= pixelsCopy2[(y-blurSize-1)*image->width + col].y; 2184 avgB -= pixelsCopy2[(y-blurSize-1)*image->width + col].z; 2185 avgAlpha -= pixelsCopy2[(y-blurSize-1)*image->width + col].w; 2186 convolutionSize--; 2187 } 2188 if (y+blurSize < image->height) 2189 { 2190 avgR += pixelsCopy2[(y+blurSize)*image->width + col].x; 2191 avgG += pixelsCopy2[(y+blurSize)*image->width + col].y; 2192 avgB += pixelsCopy2[(y+blurSize)*image->width + col].z; 2193 avgAlpha += pixelsCopy2[(y+blurSize)*image->width + col].w; 2194 convolutionSize++; 2195 } 2196 2197 pixelsCopy1[y*image->width + col].x = (unsigned char) (avgR/convolutionSize); 2198 pixelsCopy1[y*image->width + col].y = (unsigned char) (avgG/convolutionSize); 2199 pixelsCopy1[y*image->width + col].z = (unsigned char) (avgB/convolutionSize); 2200 pixelsCopy1[y*image->width + col].w = (unsigned char) (avgAlpha/convolutionSize); 2201 } 2202 } 2203 } 2204 2205 // Reverse premultiply 2206 for (int i = 0; i < (image->width)*(image->height); i++) 2207 { 2208 if (pixelsCopy1[i].w == 0.0f) 2209 { 2210 pixels[i].r = 0; 2211 pixels[i].g = 0; 2212 pixels[i].b = 0; 2213 pixels[i].a = 0; 2214 } 2215 else if (pixelsCopy1[i].w <= 255.0f) 2216 { 2217 float alpha = (float)pixelsCopy1[i].w/255.0f; 2218 pixels[i].r = (unsigned char)((float)pixelsCopy1[i].x/alpha); 2219 pixels[i].g = (unsigned char)((float)pixelsCopy1[i].y/alpha); 2220 pixels[i].b = (unsigned char)((float)pixelsCopy1[i].z/alpha); 2221 pixels[i].a = (unsigned char) pixelsCopy1[i].w; 2222 } 2223 } 2224 2225 int format = image->format; 2226 RL_FREE(image->data); 2227 RL_FREE(pixelsCopy1); 2228 RL_FREE(pixelsCopy2); 2229 2230 image->data = pixels; 2231 image->format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8; 2232 2233 ImageFormat(image, format); 2234 } 2235 2236 // Apply custom square convolution kernel to image 2237 // NOTE: The convolution kernel matrix is expected to be square 2238 void ImageKernelConvolution(Image *image, const float *kernel, int kernelSize) 2239 { 2240 if ((image->data == NULL) || (image->width == 0) || (image->height == 0) || kernel == NULL) return; 2241 2242 int kernelWidth = (int)sqrtf((float)kernelSize); 2243 2244 if (kernelWidth*kernelWidth != kernelSize) 2245 { 2246 TRACELOG(LOG_WARNING, "IMAGE: Convolution kernel must be square to be applied"); 2247 return; 2248 } 2249 2250 Color *pixels = LoadImageColors(*image); 2251 2252 Vector4 *imageCopy2 = RL_MALLOC((image->height)*(image->width)*sizeof(Vector4)); 2253 Vector4 *temp = RL_MALLOC(kernelSize*sizeof(Vector4)); 2254 2255 for (int i = 0; i < kernelSize; i++) 2256 { 2257 temp[i].x = 0.0f; 2258 temp[i].y = 0.0f; 2259 temp[i].z = 0.0f; 2260 temp[i].w = 0.0f; 2261 } 2262 2263 float rRes = 0.0f; 2264 float gRes = 0.0f; 2265 float bRes = 0.0f; 2266 float aRes = 0.0f; 2267 2268 int startRange = 0, endRange = 0; 2269 2270 if (kernelWidth%2 == 0) 2271 { 2272 startRange = -kernelWidth/2; 2273 endRange = kernelWidth/2; 2274 } 2275 else 2276 { 2277 startRange = -kernelWidth/2; 2278 endRange = kernelWidth/2 + 1; 2279 } 2280 2281 for (int x = 0; x < image->height; x++) 2282 { 2283 for (int y = 0; y < image->width; y++) 2284 { 2285 for (int xk = startRange; xk < endRange; xk++) 2286 { 2287 for (int yk = startRange; yk < endRange; yk++) 2288 { 2289 int xkabs = xk + kernelWidth/2; 2290 int ykabs = yk + kernelWidth/2; 2291 unsigned int imgindex = image->width*(x + xk) + (y + yk); 2292 2293 if (imgindex >= (unsigned int)(image->width*image->height)) 2294 { 2295 temp[kernelWidth*xkabs + ykabs].x = 0.0f; 2296 temp[kernelWidth*xkabs + ykabs].y = 0.0f; 2297 temp[kernelWidth*xkabs + ykabs].z = 0.0f; 2298 temp[kernelWidth*xkabs + ykabs].w = 0.0f; 2299 } 2300 else 2301 { 2302 temp[kernelWidth*xkabs + ykabs].x = ((float)pixels[imgindex].r)/255.0f*kernel[kernelWidth*xkabs + ykabs]; 2303 temp[kernelWidth*xkabs + ykabs].y = ((float)pixels[imgindex].g)/255.0f*kernel[kernelWidth*xkabs + ykabs]; 2304 temp[kernelWidth*xkabs + ykabs].z = ((float)pixels[imgindex].b)/255.0f*kernel[kernelWidth*xkabs + ykabs]; 2305 temp[kernelWidth*xkabs + ykabs].w = ((float)pixels[imgindex].a)/255.0f*kernel[kernelWidth*xkabs + ykabs]; 2306 } 2307 } 2308 } 2309 2310 for (int i = 0; i < kernelSize; i++) 2311 { 2312 rRes += temp[i].x; 2313 gRes += temp[i].y; 2314 bRes += temp[i].z; 2315 aRes += temp[i].w; 2316 } 2317 2318 if (rRes < 0.0f) rRes = 0.0f; 2319 if (gRes < 0.0f) gRes = 0.0f; 2320 if (bRes < 0.0f) bRes = 0.0f; 2321 2322 if (rRes > 1.0f) rRes = 1.0f; 2323 if (gRes > 1.0f) gRes = 1.0f; 2324 if (bRes > 1.0f) bRes = 1.0f; 2325 2326 imageCopy2[image->width*x + y].x = rRes; 2327 imageCopy2[image->width*x + y].y = gRes; 2328 imageCopy2[image->width*x + y].z = bRes; 2329 imageCopy2[image->width*x + y].w = aRes; 2330 2331 rRes = 0.0f; 2332 gRes = 0.0f; 2333 bRes = 0.0f; 2334 aRes = 0.0f; 2335 2336 for (int i = 0; i < kernelSize; i++) 2337 { 2338 temp[i].x = 0.0f; 2339 temp[i].y = 0.0f; 2340 temp[i].z = 0.0f; 2341 temp[i].w = 0.0f; 2342 } 2343 } 2344 } 2345 2346 for (int i = 0; i < (image->width*image->height); i++) 2347 { 2348 float alpha = (float)imageCopy2[i].w; 2349 2350 pixels[i].r = (unsigned char)((imageCopy2[i].x)*255.0f); 2351 pixels[i].g = (unsigned char)((imageCopy2[i].y)*255.0f); 2352 pixels[i].b = (unsigned char)((imageCopy2[i].z)*255.0f); 2353 pixels[i].a = (unsigned char)((alpha)*255.0f); 2354 } 2355 2356 int format = image->format; 2357 RL_FREE(image->data); 2358 RL_FREE(imageCopy2); 2359 RL_FREE(temp); 2360 2361 image->data = pixels; 2362 image->format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8; 2363 ImageFormat(image, format); 2364 } 2365 2366 // Generate all mipmap levels for a provided image 2367 // NOTE 1: Supports POT and NPOT images 2368 // NOTE 2: image.data is scaled to include mipmap levels 2369 // NOTE 3: Mipmaps format is the same as base image 2370 void ImageMipmaps(Image *image) 2371 { 2372 // Security check to avoid program crash 2373 if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; 2374 2375 int mipCount = 1; // Required mipmap levels count (including base level) 2376 int mipWidth = image->width; // Base image width 2377 int mipHeight = image->height; // Base image height 2378 int mipSize = GetPixelDataSize(mipWidth, mipHeight, image->format); // Image data size (in bytes) 2379 2380 // Count mipmap levels required 2381 while ((mipWidth != 1) || (mipHeight != 1)) 2382 { 2383 if (mipWidth != 1) mipWidth /= 2; 2384 if (mipHeight != 1) mipHeight /= 2; 2385 2386 // Security check for NPOT textures 2387 if (mipWidth < 1) mipWidth = 1; 2388 if (mipHeight < 1) mipHeight = 1; 2389 2390 TRACELOGD("IMAGE: Next mipmap level: %i x %i - current size %i", mipWidth, mipHeight, mipSize); 2391 2392 mipCount++; 2393 mipSize += GetPixelDataSize(mipWidth, mipHeight, image->format); // Add mipmap size (in bytes) 2394 } 2395 2396 if (image->mipmaps < mipCount) 2397 { 2398 void *temp = RL_REALLOC(image->data, mipSize); 2399 2400 if (temp != NULL) image->data = temp; // Assign new pointer (new size) to store mipmaps data 2401 else TRACELOG(LOG_WARNING, "IMAGE: Mipmaps required memory could not be allocated"); 2402 2403 // Pointer to allocated memory point where store next mipmap level data 2404 unsigned char *nextmip = image->data; 2405 2406 mipWidth = image->width; 2407 mipHeight = image->height; 2408 mipSize = GetPixelDataSize(mipWidth, mipHeight, image->format); 2409 Image imCopy = ImageCopy(*image); 2410 2411 for (int i = 1; i < mipCount; i++) 2412 { 2413 nextmip += mipSize; 2414 2415 mipWidth /= 2; 2416 mipHeight /= 2; 2417 2418 // Security check for NPOT textures 2419 if (mipWidth < 1) mipWidth = 1; 2420 if (mipHeight < 1) mipHeight = 1; 2421 2422 mipSize = GetPixelDataSize(mipWidth, mipHeight, image->format); 2423 2424 if (i < image->mipmaps) continue; 2425 2426 TRACELOGD("IMAGE: Generating mipmap level: %i (%i x %i) - size: %i - offset: 0x%x", i, mipWidth, mipHeight, mipSize, nextmip); 2427 2428 ImageResize(&imCopy, mipWidth, mipHeight); // Uses internally Mitchell cubic downscale filter 2429 2430 memcpy(nextmip, imCopy.data, mipSize); 2431 } 2432 2433 UnloadImage(imCopy); 2434 2435 image->mipmaps = mipCount; 2436 } 2437 else TRACELOG(LOG_WARNING, "IMAGE: Mipmaps already available"); 2438 } 2439 2440 // Dither image data to 16bpp or lower (Floyd-Steinberg dithering) 2441 // NOTE: In case selected bpp do not represent a known 16bit format, 2442 // dithered data is stored in the LSB part of the unsigned short 2443 void ImageDither(Image *image, int rBpp, int gBpp, int bBpp, int aBpp) 2444 { 2445 // Security check to avoid program crash 2446 if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; 2447 2448 if (image->format >= PIXELFORMAT_COMPRESSED_DXT1_RGB) 2449 { 2450 TRACELOG(LOG_WARNING, "IMAGE: Compressed data formats can not be dithered"); 2451 return; 2452 } 2453 2454 if ((rBpp + gBpp + bBpp + aBpp) > 16) 2455 { 2456 TRACELOG(LOG_WARNING, "IMAGE: Unsupported dithering bpps (%ibpp), only 16bpp or lower modes supported", (rBpp+gBpp+bBpp+aBpp)); 2457 } 2458 else 2459 { 2460 Color *pixels = LoadImageColors(*image); 2461 2462 RL_FREE(image->data); // free old image data 2463 2464 if ((image->format != PIXELFORMAT_UNCOMPRESSED_R8G8B8) && (image->format != PIXELFORMAT_UNCOMPRESSED_R8G8B8A8)) 2465 { 2466 TRACELOG(LOG_WARNING, "IMAGE: Format is already 16bpp or lower, dithering could have no effect"); 2467 } 2468 2469 // Define new image format, check if desired bpp match internal known format 2470 if ((rBpp == 5) && (gBpp == 6) && (bBpp == 5) && (aBpp == 0)) image->format = PIXELFORMAT_UNCOMPRESSED_R5G6B5; 2471 else if ((rBpp == 5) && (gBpp == 5) && (bBpp == 5) && (aBpp == 1)) image->format = PIXELFORMAT_UNCOMPRESSED_R5G5B5A1; 2472 else if ((rBpp == 4) && (gBpp == 4) && (bBpp == 4) && (aBpp == 4)) image->format = PIXELFORMAT_UNCOMPRESSED_R4G4B4A4; 2473 else 2474 { 2475 image->format = 0; 2476 TRACELOG(LOG_WARNING, "IMAGE: Unsupported dithered OpenGL internal format: %ibpp (R%iG%iB%iA%i)", (rBpp+gBpp+bBpp+aBpp), rBpp, gBpp, bBpp, aBpp); 2477 } 2478 2479 // NOTE: We will store the dithered data as unsigned short (16bpp) 2480 image->data = (unsigned short *)RL_MALLOC(image->width*image->height*sizeof(unsigned short)); 2481 2482 Color oldPixel = WHITE; 2483 Color newPixel = WHITE; 2484 2485 int rError, gError, bError; 2486 unsigned short rPixel, gPixel, bPixel, aPixel; // Used for 16bit pixel composition 2487 2488 #define MIN(a,b) (((a)<(b))?(a):(b)) 2489 2490 for (int y = 0; y < image->height; y++) 2491 { 2492 for (int x = 0; x < image->width; x++) 2493 { 2494 oldPixel = pixels[y*image->width + x]; 2495 2496 // NOTE: New pixel obtained by bits truncate, it would be better to round values (check ImageFormat()) 2497 newPixel.r = oldPixel.r >> (8 - rBpp); // R bits 2498 newPixel.g = oldPixel.g >> (8 - gBpp); // G bits 2499 newPixel.b = oldPixel.b >> (8 - bBpp); // B bits 2500 newPixel.a = oldPixel.a >> (8 - aBpp); // A bits (not used on dithering) 2501 2502 // NOTE: Error must be computed between new and old pixel but using same number of bits! 2503 // We want to know how much color precision we have lost... 2504 rError = (int)oldPixel.r - (int)(newPixel.r << (8 - rBpp)); 2505 gError = (int)oldPixel.g - (int)(newPixel.g << (8 - gBpp)); 2506 bError = (int)oldPixel.b - (int)(newPixel.b << (8 - bBpp)); 2507 2508 pixels[y*image->width + x] = newPixel; 2509 2510 // NOTE: Some cases are out of the array and should be ignored 2511 if (x < (image->width - 1)) 2512 { 2513 pixels[y*image->width + x+1].r = MIN((int)pixels[y*image->width + x+1].r + (int)((float)rError*7.0f/16), 0xff); 2514 pixels[y*image->width + x+1].g = MIN((int)pixels[y*image->width + x+1].g + (int)((float)gError*7.0f/16), 0xff); 2515 pixels[y*image->width + x+1].b = MIN((int)pixels[y*image->width + x+1].b + (int)((float)bError*7.0f/16), 0xff); 2516 } 2517 2518 if ((x > 0) && (y < (image->height - 1))) 2519 { 2520 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); 2521 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); 2522 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); 2523 } 2524 2525 if (y < (image->height - 1)) 2526 { 2527 pixels[(y+1)*image->width + x].r = MIN((int)pixels[(y+1)*image->width + x].r + (int)((float)rError*5.0f/16), 0xff); 2528 pixels[(y+1)*image->width + x].g = MIN((int)pixels[(y+1)*image->width + x].g + (int)((float)gError*5.0f/16), 0xff); 2529 pixels[(y+1)*image->width + x].b = MIN((int)pixels[(y+1)*image->width + x].b + (int)((float)bError*5.0f/16), 0xff); 2530 } 2531 2532 if ((x < (image->width - 1)) && (y < (image->height - 1))) 2533 { 2534 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); 2535 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); 2536 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); 2537 } 2538 2539 rPixel = (unsigned short)newPixel.r; 2540 gPixel = (unsigned short)newPixel.g; 2541 bPixel = (unsigned short)newPixel.b; 2542 aPixel = (unsigned short)newPixel.a; 2543 2544 ((unsigned short *)image->data)[y*image->width + x] = (rPixel << (gBpp + bBpp + aBpp)) | (gPixel << (bBpp + aBpp)) | (bPixel << aBpp) | aPixel; 2545 } 2546 } 2547 2548 UnloadImageColors(pixels); 2549 } 2550 } 2551 2552 // Flip image vertically 2553 void ImageFlipVertical(Image *image) 2554 { 2555 // Security check to avoid program crash 2556 if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; 2557 2558 if (image->mipmaps > 1) TRACELOG(LOG_WARNING, "Image manipulation only applied to base mipmap level"); 2559 if (image->format >= PIXELFORMAT_COMPRESSED_DXT1_RGB) TRACELOG(LOG_WARNING, "Image manipulation not supported for compressed formats"); 2560 else 2561 { 2562 int bytesPerPixel = GetPixelDataSize(1, 1, image->format); 2563 unsigned char *flippedData = (unsigned char *)RL_MALLOC(image->width*image->height*bytesPerPixel); 2564 2565 for (int i = (image->height - 1), offsetSize = 0; i >= 0; i--) 2566 { 2567 memcpy(flippedData + offsetSize, ((unsigned char *)image->data) + i*image->width*bytesPerPixel, image->width*bytesPerPixel); 2568 offsetSize += image->width*bytesPerPixel; 2569 } 2570 2571 RL_FREE(image->data); 2572 image->data = flippedData; 2573 } 2574 } 2575 2576 // Flip image horizontally 2577 void ImageFlipHorizontal(Image *image) 2578 { 2579 // Security check to avoid program crash 2580 if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; 2581 2582 if (image->mipmaps > 1) TRACELOG(LOG_WARNING, "Image manipulation only applied to base mipmap level"); 2583 if (image->format >= PIXELFORMAT_COMPRESSED_DXT1_RGB) TRACELOG(LOG_WARNING, "Image manipulation not supported for compressed formats"); 2584 else 2585 { 2586 int bytesPerPixel = GetPixelDataSize(1, 1, image->format); 2587 unsigned char *flippedData = (unsigned char *)RL_MALLOC(image->width*image->height*bytesPerPixel); 2588 2589 for (int y = 0; y < image->height; y++) 2590 { 2591 for (int x = 0; x < image->width; x++) 2592 { 2593 // OPTION 1: Move pixels with memcpy() 2594 //memcpy(flippedData + (y*image->width + x)*bytesPerPixel, ((unsigned char *)image->data) + (y*image->width + (image->width - 1 - x))*bytesPerPixel, bytesPerPixel); 2595 2596 // OPTION 2: Just copy data pixel by pixel 2597 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]; 2598 } 2599 } 2600 2601 RL_FREE(image->data); 2602 image->data = flippedData; 2603 2604 /* 2605 // OPTION 3: Faster implementation (specific for 32bit pixels) 2606 // NOTE: It does not require additional allocations 2607 uint32_t *ptr = (uint32_t *)image->data; 2608 for (int y = 0; y < image->height; y++) 2609 { 2610 for (int x = 0; x < image->width/2; x++) 2611 { 2612 uint32_t backup = ptr[y*image->width + x]; 2613 ptr[y*image->width + x] = ptr[y*image->width + (image->width - 1 - x)]; 2614 ptr[y*image->width + (image->width - 1 - x)] = backup; 2615 } 2616 } 2617 */ 2618 } 2619 } 2620 2621 // Rotate image in degrees 2622 void ImageRotate(Image *image, int degrees) 2623 { 2624 // Security check to avoid program crash 2625 if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; 2626 2627 if (image->mipmaps > 1) TRACELOG(LOG_WARNING, "Image manipulation only applied to base mipmap level"); 2628 if (image->format >= PIXELFORMAT_COMPRESSED_DXT1_RGB) TRACELOG(LOG_WARNING, "Image manipulation not supported for compressed formats"); 2629 else 2630 { 2631 float rad = degrees*PI/180.0f; 2632 float sinRadius = sinf(rad); 2633 float cosRadius = cosf(rad); 2634 2635 int width = (int)(fabsf(image->width*cosRadius) + fabsf(image->height*sinRadius)); 2636 int height = (int)(fabsf(image->height*cosRadius) + fabsf(image->width*sinRadius)); 2637 2638 int bytesPerPixel = GetPixelDataSize(1, 1, image->format); 2639 unsigned char *rotatedData = (unsigned char *)RL_CALLOC(width*height, bytesPerPixel); 2640 2641 for (int y = 0; y < height; y++) 2642 { 2643 for (int x = 0; x < width; x++) 2644 { 2645 float oldX = ((x - width/2.0f)*cosRadius + (y - height/2.0f)*sinRadius) + image->width/2.0f; 2646 float oldY = ((y - height/2.0f)*cosRadius - (x - width/2.0f)*sinRadius) + image->height/2.0f; 2647 2648 if ((oldX >= 0) && (oldX < image->width) && (oldY >= 0) && (oldY < image->height)) 2649 { 2650 int x1 = (int)floorf(oldX); 2651 int y1 = (int)floorf(oldY); 2652 int x2 = MIN(x1 + 1, image->width - 1); 2653 int y2 = MIN(y1 + 1, image->height - 1); 2654 2655 float px = oldX - x1; 2656 float py = oldY - y1; 2657 2658 for (int i = 0; i < bytesPerPixel; i++) 2659 { 2660 float f1 = ((unsigned char *)image->data)[(y1*image->width + x1)*bytesPerPixel + i]; 2661 float f2 = ((unsigned char *)image->data)[(y1*image->width + x2)*bytesPerPixel + i]; 2662 float f3 = ((unsigned char *)image->data)[(y2*image->width + x1)*bytesPerPixel + i]; 2663 float f4 = ((unsigned char *)image->data)[(y2*image->width + x2)*bytesPerPixel + i]; 2664 2665 float val = f1*(1 - px)*(1 - py) + f2*px*(1 - py) + f3*(1 - px)*py + f4*px*py; 2666 2667 rotatedData[(y*width + x)*bytesPerPixel + i] = (unsigned char)val; 2668 } 2669 } 2670 } 2671 } 2672 2673 RL_FREE(image->data); 2674 image->data = rotatedData; 2675 image->width = width; 2676 image->height = height; 2677 } 2678 } 2679 2680 // Rotate image clockwise 90deg 2681 void ImageRotateCW(Image *image) 2682 { 2683 // Security check to avoid program crash 2684 if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; 2685 2686 if (image->mipmaps > 1) TRACELOG(LOG_WARNING, "Image manipulation only applied to base mipmap level"); 2687 if (image->format >= PIXELFORMAT_COMPRESSED_DXT1_RGB) TRACELOG(LOG_WARNING, "Image manipulation not supported for compressed formats"); 2688 else 2689 { 2690 int bytesPerPixel = GetPixelDataSize(1, 1, image->format); 2691 unsigned char *rotatedData = (unsigned char *)RL_MALLOC(image->width*image->height*bytesPerPixel); 2692 2693 for (int y = 0; y < image->height; y++) 2694 { 2695 for (int x = 0; x < image->width; x++) 2696 { 2697 //memcpy(rotatedData + (x*image->height + (image->height - y - 1))*bytesPerPixel, ((unsigned char *)image->data) + (y*image->width + x)*bytesPerPixel, bytesPerPixel); 2698 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]; 2699 } 2700 } 2701 2702 RL_FREE(image->data); 2703 image->data = rotatedData; 2704 int width = image->width; 2705 int height = image-> height; 2706 2707 image->width = height; 2708 image->height = width; 2709 } 2710 } 2711 2712 // Rotate image counter-clockwise 90deg 2713 void ImageRotateCCW(Image *image) 2714 { 2715 // Security check to avoid program crash 2716 if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; 2717 2718 if (image->mipmaps > 1) TRACELOG(LOG_WARNING, "Image manipulation only applied to base mipmap level"); 2719 if (image->format >= PIXELFORMAT_COMPRESSED_DXT1_RGB) TRACELOG(LOG_WARNING, "Image manipulation not supported for compressed formats"); 2720 else 2721 { 2722 int bytesPerPixel = GetPixelDataSize(1, 1, image->format); 2723 unsigned char *rotatedData = (unsigned char *)RL_MALLOC(image->width*image->height*bytesPerPixel); 2724 2725 for (int y = 0; y < image->height; y++) 2726 { 2727 for (int x = 0; x < image->width; x++) 2728 { 2729 //memcpy(rotatedData + (x*image->height + y))*bytesPerPixel, ((unsigned char *)image->data) + (y*image->width + (image->width - x - 1))*bytesPerPixel, bytesPerPixel); 2730 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]; 2731 } 2732 } 2733 2734 RL_FREE(image->data); 2735 image->data = rotatedData; 2736 int width = image->width; 2737 int height = image-> height; 2738 2739 image->width = height; 2740 image->height = width; 2741 } 2742 } 2743 2744 // Modify image color: tint 2745 void ImageColorTint(Image *image, Color color) 2746 { 2747 // Security check to avoid program crash 2748 if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; 2749 2750 Color *pixels = LoadImageColors(*image); 2751 2752 for (int i = 0; i < image->width*image->height; i++) 2753 { 2754 unsigned char r = (unsigned char)(((int)pixels[i].r*(int)color.r)/255); 2755 unsigned char g = (unsigned char)(((int)pixels[i].g*(int)color.g)/255); 2756 unsigned char b = (unsigned char)(((int)pixels[i].b*(int)color.b)/255); 2757 unsigned char a = (unsigned char)(((int)pixels[i].a*(int)color.a)/255); 2758 2759 pixels[i].r = r; 2760 pixels[i].g = g; 2761 pixels[i].b = b; 2762 pixels[i].a = a; 2763 } 2764 2765 int format = image->format; 2766 RL_FREE(image->data); 2767 2768 image->data = pixels; 2769 image->format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8; 2770 2771 ImageFormat(image, format); 2772 } 2773 2774 // Modify image color: invert 2775 void ImageColorInvert(Image *image) 2776 { 2777 // Security check to avoid program crash 2778 if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; 2779 2780 Color *pixels = LoadImageColors(*image); 2781 2782 for (int i = 0; i < image->width*image->height; i++) 2783 { 2784 pixels[i].r = 255 - pixels[i].r; 2785 pixels[i].g = 255 - pixels[i].g; 2786 pixels[i].b = 255 - pixels[i].b; 2787 } 2788 2789 int format = image->format; 2790 RL_FREE(image->data); 2791 2792 image->data = pixels; 2793 image->format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8; 2794 2795 ImageFormat(image, format); 2796 } 2797 2798 // Modify image color: grayscale 2799 void ImageColorGrayscale(Image *image) 2800 { 2801 ImageFormat(image, PIXELFORMAT_UNCOMPRESSED_GRAYSCALE); 2802 } 2803 2804 // Modify image color: contrast 2805 // NOTE: Contrast values between -100 and 100 2806 void ImageColorContrast(Image *image, float contrast) 2807 { 2808 // Security check to avoid program crash 2809 if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; 2810 2811 if (contrast < -100) contrast = -100; 2812 if (contrast > 100) contrast = 100; 2813 2814 contrast = (100.0f + contrast)/100.0f; 2815 contrast *= contrast; 2816 2817 Color *pixels = LoadImageColors(*image); 2818 2819 for (int i = 0; i < image->width*image->height; i++) 2820 { 2821 float pR = (float)pixels[i].r/255.0f; 2822 pR -= 0.5f; 2823 pR *= contrast; 2824 pR += 0.5f; 2825 pR *= 255; 2826 if (pR < 0) pR = 0; 2827 if (pR > 255) pR = 255; 2828 2829 float pG = (float)pixels[i].g/255.0f; 2830 pG -= 0.5f; 2831 pG *= contrast; 2832 pG += 0.5f; 2833 pG *= 255; 2834 if (pG < 0) pG = 0; 2835 if (pG > 255) pG = 255; 2836 2837 float pB = (float)pixels[i].b/255.0f; 2838 pB -= 0.5f; 2839 pB *= contrast; 2840 pB += 0.5f; 2841 pB *= 255; 2842 if (pB < 0) pB = 0; 2843 if (pB > 255) pB = 255; 2844 2845 pixels[i].r = (unsigned char)pR; 2846 pixels[i].g = (unsigned char)pG; 2847 pixels[i].b = (unsigned char)pB; 2848 } 2849 2850 int format = image->format; 2851 RL_FREE(image->data); 2852 2853 image->data = pixels; 2854 image->format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8; 2855 2856 ImageFormat(image, format); 2857 } 2858 2859 // Modify image color: brightness 2860 // NOTE: Brightness values between -255 and 255 2861 void ImageColorBrightness(Image *image, int brightness) 2862 { 2863 // Security check to avoid program crash 2864 if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; 2865 2866 if (brightness < -255) brightness = -255; 2867 if (brightness > 255) brightness = 255; 2868 2869 Color *pixels = LoadImageColors(*image); 2870 2871 for (int i = 0; i < image->width*image->height; i++) 2872 { 2873 int cR = pixels[i].r + brightness; 2874 int cG = pixels[i].g + brightness; 2875 int cB = pixels[i].b + brightness; 2876 2877 if (cR < 0) cR = 1; 2878 if (cR > 255) cR = 255; 2879 2880 if (cG < 0) cG = 1; 2881 if (cG > 255) cG = 255; 2882 2883 if (cB < 0) cB = 1; 2884 if (cB > 255) cB = 255; 2885 2886 pixels[i].r = (unsigned char)cR; 2887 pixels[i].g = (unsigned char)cG; 2888 pixels[i].b = (unsigned char)cB; 2889 } 2890 2891 int format = image->format; 2892 RL_FREE(image->data); 2893 2894 image->data = pixels; 2895 image->format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8; 2896 2897 ImageFormat(image, format); 2898 } 2899 2900 // Modify image color: replace color 2901 void ImageColorReplace(Image *image, Color color, Color replace) 2902 { 2903 // Security check to avoid program crash 2904 if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; 2905 2906 Color *pixels = LoadImageColors(*image); 2907 2908 for (int i = 0; i < image->width*image->height; i++) 2909 { 2910 if ((pixels[i].r == color.r) && 2911 (pixels[i].g == color.g) && 2912 (pixels[i].b == color.b) && 2913 (pixels[i].a == color.a)) 2914 { 2915 pixels[i].r = replace.r; 2916 pixels[i].g = replace.g; 2917 pixels[i].b = replace.b; 2918 pixels[i].a = replace.a; 2919 } 2920 } 2921 2922 int format = image->format; 2923 RL_FREE(image->data); 2924 2925 image->data = pixels; 2926 image->format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8; 2927 2928 ImageFormat(image, format); 2929 } 2930 #endif // SUPPORT_IMAGE_MANIPULATION 2931 2932 // Load color data from image as a Color array (RGBA - 32bit) 2933 // NOTE: Memory allocated should be freed using UnloadImageColors(); 2934 Color *LoadImageColors(Image image) 2935 { 2936 if ((image.width == 0) || (image.height == 0)) return NULL; 2937 2938 Color *pixels = (Color *)RL_MALLOC(image.width*image.height*sizeof(Color)); 2939 2940 if (image.format >= PIXELFORMAT_COMPRESSED_DXT1_RGB) TRACELOG(LOG_WARNING, "IMAGE: Pixel data retrieval not supported for compressed image formats"); 2941 else 2942 { 2943 if ((image.format == PIXELFORMAT_UNCOMPRESSED_R32) || 2944 (image.format == PIXELFORMAT_UNCOMPRESSED_R32G32B32) || 2945 (image.format == PIXELFORMAT_UNCOMPRESSED_R32G32B32A32)) TRACELOG(LOG_WARNING, "IMAGE: Pixel format converted from 32bit to 8bit per channel"); 2946 2947 if ((image.format == PIXELFORMAT_UNCOMPRESSED_R16) || 2948 (image.format == PIXELFORMAT_UNCOMPRESSED_R16G16B16) || 2949 (image.format == PIXELFORMAT_UNCOMPRESSED_R16G16B16A16)) TRACELOG(LOG_WARNING, "IMAGE: Pixel format converted from 16bit to 8bit per channel"); 2950 2951 for (int i = 0, k = 0; i < image.width*image.height; i++) 2952 { 2953 switch (image.format) 2954 { 2955 case PIXELFORMAT_UNCOMPRESSED_GRAYSCALE: 2956 { 2957 pixels[i].r = ((unsigned char *)image.data)[i]; 2958 pixels[i].g = ((unsigned char *)image.data)[i]; 2959 pixels[i].b = ((unsigned char *)image.data)[i]; 2960 pixels[i].a = 255; 2961 2962 } break; 2963 case PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA: 2964 { 2965 pixels[i].r = ((unsigned char *)image.data)[k]; 2966 pixels[i].g = ((unsigned char *)image.data)[k]; 2967 pixels[i].b = ((unsigned char *)image.data)[k]; 2968 pixels[i].a = ((unsigned char *)image.data)[k + 1]; 2969 2970 k += 2; 2971 } break; 2972 case PIXELFORMAT_UNCOMPRESSED_R5G5B5A1: 2973 { 2974 unsigned short pixel = ((unsigned short *)image.data)[i]; 2975 2976 pixels[i].r = (unsigned char)((float)((pixel & 0b1111100000000000) >> 11)*(255/31)); 2977 pixels[i].g = (unsigned char)((float)((pixel & 0b0000011111000000) >> 6)*(255/31)); 2978 pixels[i].b = (unsigned char)((float)((pixel & 0b0000000000111110) >> 1)*(255/31)); 2979 pixels[i].a = (unsigned char)((pixel & 0b0000000000000001)*255); 2980 2981 } break; 2982 case PIXELFORMAT_UNCOMPRESSED_R5G6B5: 2983 { 2984 unsigned short pixel = ((unsigned short *)image.data)[i]; 2985 2986 pixels[i].r = (unsigned char)((float)((pixel & 0b1111100000000000) >> 11)*(255/31)); 2987 pixels[i].g = (unsigned char)((float)((pixel & 0b0000011111100000) >> 5)*(255/63)); 2988 pixels[i].b = (unsigned char)((float)(pixel & 0b0000000000011111)*(255/31)); 2989 pixels[i].a = 255; 2990 2991 } break; 2992 case PIXELFORMAT_UNCOMPRESSED_R4G4B4A4: 2993 { 2994 unsigned short pixel = ((unsigned short *)image.data)[i]; 2995 2996 pixels[i].r = (unsigned char)((float)((pixel & 0b1111000000000000) >> 12)*(255/15)); 2997 pixels[i].g = (unsigned char)((float)((pixel & 0b0000111100000000) >> 8)*(255/15)); 2998 pixels[i].b = (unsigned char)((float)((pixel & 0b0000000011110000) >> 4)*(255/15)); 2999 pixels[i].a = (unsigned char)((float)(pixel & 0b0000000000001111)*(255/15)); 3000 3001 } break; 3002 case PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: 3003 { 3004 pixels[i].r = ((unsigned char *)image.data)[k]; 3005 pixels[i].g = ((unsigned char *)image.data)[k + 1]; 3006 pixels[i].b = ((unsigned char *)image.data)[k + 2]; 3007 pixels[i].a = ((unsigned char *)image.data)[k + 3]; 3008 3009 k += 4; 3010 } break; 3011 case PIXELFORMAT_UNCOMPRESSED_R8G8B8: 3012 { 3013 pixels[i].r = (unsigned char)((unsigned char *)image.data)[k]; 3014 pixels[i].g = (unsigned char)((unsigned char *)image.data)[k + 1]; 3015 pixels[i].b = (unsigned char)((unsigned char *)image.data)[k + 2]; 3016 pixels[i].a = 255; 3017 3018 k += 3; 3019 } break; 3020 case PIXELFORMAT_UNCOMPRESSED_R32: 3021 { 3022 pixels[i].r = (unsigned char)(((float *)image.data)[k]*255.0f); 3023 pixels[i].g = 0; 3024 pixels[i].b = 0; 3025 pixels[i].a = 255; 3026 3027 k += 1; 3028 } break; 3029 case PIXELFORMAT_UNCOMPRESSED_R32G32B32: 3030 { 3031 pixels[i].r = (unsigned char)(((float *)image.data)[k]*255.0f); 3032 pixels[i].g = (unsigned char)(((float *)image.data)[k + 1]*255.0f); 3033 pixels[i].b = (unsigned char)(((float *)image.data)[k + 2]*255.0f); 3034 pixels[i].a = 255; 3035 3036 k += 3; 3037 } break; 3038 case PIXELFORMAT_UNCOMPRESSED_R32G32B32A32: 3039 { 3040 pixels[i].r = (unsigned char)(((float *)image.data)[k]*255.0f); 3041 pixels[i].g = (unsigned char)(((float *)image.data)[k + 1]*255.0f); 3042 pixels[i].b = (unsigned char)(((float *)image.data)[k + 2]*255.0f); 3043 pixels[i].a = (unsigned char)(((float *)image.data)[k + 3]*255.0f); 3044 3045 k += 4; 3046 } break; 3047 case PIXELFORMAT_UNCOMPRESSED_R16: 3048 { 3049 pixels[i].r = (unsigned char)(HalfToFloat(((unsigned short *)image.data)[k])*255.0f); 3050 pixels[i].g = 0; 3051 pixels[i].b = 0; 3052 pixels[i].a = 255; 3053 3054 k += 1; 3055 } break; 3056 case PIXELFORMAT_UNCOMPRESSED_R16G16B16: 3057 { 3058 pixels[i].r = (unsigned char)(HalfToFloat(((unsigned short *)image.data)[k])*255.0f); 3059 pixels[i].g = (unsigned char)(HalfToFloat(((unsigned short *)image.data)[k + 1])*255.0f); 3060 pixels[i].b = (unsigned char)(HalfToFloat(((unsigned short *)image.data)[k + 2])*255.0f); 3061 pixels[i].a = 255; 3062 3063 k += 3; 3064 } break; 3065 case PIXELFORMAT_UNCOMPRESSED_R16G16B16A16: 3066 { 3067 pixels[i].r = (unsigned char)(HalfToFloat(((unsigned short *)image.data)[k])*255.0f); 3068 pixels[i].g = (unsigned char)(HalfToFloat(((unsigned short *)image.data)[k + 1])*255.0f); 3069 pixels[i].b = (unsigned char)(HalfToFloat(((unsigned short *)image.data)[k + 2])*255.0f); 3070 pixels[i].a = (unsigned char)(HalfToFloat(((unsigned short *)image.data)[k + 3])*255.0f); 3071 3072 k += 4; 3073 } break; 3074 default: break; 3075 } 3076 } 3077 } 3078 3079 return pixels; 3080 } 3081 3082 // Load colors palette from image as a Color array (RGBA - 32bit) 3083 // NOTE: Memory allocated should be freed using UnloadImagePalette() 3084 Color *LoadImagePalette(Image image, int maxPaletteSize, int *colorCount) 3085 { 3086 #define COLOR_EQUAL(col1, col2) ((col1.r == col2.r)&&(col1.g == col2.g)&&(col1.b == col2.b)&&(col1.a == col2.a)) 3087 3088 int palCount = 0; 3089 Color *palette = NULL; 3090 Color *pixels = LoadImageColors(image); 3091 3092 if (pixels != NULL) 3093 { 3094 palette = (Color *)RL_MALLOC(maxPaletteSize*sizeof(Color)); 3095 3096 for (int i = 0; i < maxPaletteSize; i++) palette[i] = BLANK; // Set all colors to BLANK 3097 3098 for (int i = 0; i < image.width*image.height; i++) 3099 { 3100 if (pixels[i].a > 0) 3101 { 3102 bool colorInPalette = false; 3103 3104 // Check if the color is already on palette 3105 for (int j = 0; j < maxPaletteSize; j++) 3106 { 3107 if (COLOR_EQUAL(pixels[i], palette[j])) 3108 { 3109 colorInPalette = true; 3110 break; 3111 } 3112 } 3113 3114 // Store color if not on the palette 3115 if (!colorInPalette) 3116 { 3117 palette[palCount] = pixels[i]; // Add pixels[i] to palette 3118 palCount++; 3119 3120 // We reached the limit of colors supported by palette 3121 if (palCount >= maxPaletteSize) 3122 { 3123 i = image.width*image.height; // Finish palette get 3124 TRACELOG(LOG_WARNING, "IMAGE: Palette is greater than %i colors", maxPaletteSize); 3125 } 3126 } 3127 } 3128 } 3129 3130 UnloadImageColors(pixels); 3131 } 3132 3133 *colorCount = palCount; 3134 3135 return palette; 3136 } 3137 3138 // Unload color data loaded with LoadImageColors() 3139 void UnloadImageColors(Color *colors) 3140 { 3141 RL_FREE(colors); 3142 } 3143 3144 // Unload colors palette loaded with LoadImagePalette() 3145 void UnloadImagePalette(Color *colors) 3146 { 3147 RL_FREE(colors); 3148 } 3149 3150 // Get image alpha border rectangle 3151 // NOTE: Threshold is defined as a percentage: 0.0f -> 1.0f 3152 Rectangle GetImageAlphaBorder(Image image, float threshold) 3153 { 3154 Rectangle crop = { 0 }; 3155 3156 Color *pixels = LoadImageColors(image); 3157 3158 if (pixels != NULL) 3159 { 3160 int xMin = 65536; // Define a big enough number 3161 int xMax = 0; 3162 int yMin = 65536; 3163 int yMax = 0; 3164 3165 for (int y = 0; y < image.height; y++) 3166 { 3167 for (int x = 0; x < image.width; x++) 3168 { 3169 if (pixels[y*image.width + x].a > (unsigned char)(threshold*255.0f)) 3170 { 3171 if (x < xMin) xMin = x; 3172 if (x > xMax) xMax = x; 3173 if (y < yMin) yMin = y; 3174 if (y > yMax) yMax = y; 3175 } 3176 } 3177 } 3178 3179 // Check for empty blank image 3180 if ((xMin != 65536) && (xMax != 65536)) 3181 { 3182 crop = (Rectangle){ (float)xMin, (float)yMin, (float)((xMax + 1) - xMin), (float)((yMax + 1) - yMin) }; 3183 } 3184 3185 UnloadImageColors(pixels); 3186 } 3187 3188 return crop; 3189 } 3190 3191 // Get image pixel color at (x, y) position 3192 Color GetImageColor(Image image, int x, int y) 3193 { 3194 Color color = { 0 }; 3195 3196 if ((x >=0) && (x < image.width) && (y >= 0) && (y < image.height)) 3197 { 3198 switch (image.format) 3199 { 3200 case PIXELFORMAT_UNCOMPRESSED_GRAYSCALE: 3201 { 3202 color.r = ((unsigned char *)image.data)[y*image.width + x]; 3203 color.g = ((unsigned char *)image.data)[y*image.width + x]; 3204 color.b = ((unsigned char *)image.data)[y*image.width + x]; 3205 color.a = 255; 3206 3207 } break; 3208 case PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA: 3209 { 3210 color.r = ((unsigned char *)image.data)[(y*image.width + x)*2]; 3211 color.g = ((unsigned char *)image.data)[(y*image.width + x)*2]; 3212 color.b = ((unsigned char *)image.data)[(y*image.width + x)*2]; 3213 color.a = ((unsigned char *)image.data)[(y*image.width + x)*2 + 1]; 3214 3215 } break; 3216 case PIXELFORMAT_UNCOMPRESSED_R5G5B5A1: 3217 { 3218 unsigned short pixel = ((unsigned short *)image.data)[y*image.width + x]; 3219 3220 color.r = (unsigned char)((float)((pixel & 0b1111100000000000) >> 11)*(255/31)); 3221 color.g = (unsigned char)((float)((pixel & 0b0000011111000000) >> 6)*(255/31)); 3222 color.b = (unsigned char)((float)((pixel & 0b0000000000111110) >> 1)*(255/31)); 3223 color.a = (unsigned char)((pixel & 0b0000000000000001)*255); 3224 3225 } break; 3226 case PIXELFORMAT_UNCOMPRESSED_R5G6B5: 3227 { 3228 unsigned short pixel = ((unsigned short *)image.data)[y*image.width + x]; 3229 3230 color.r = (unsigned char)((float)((pixel & 0b1111100000000000) >> 11)*(255/31)); 3231 color.g = (unsigned char)((float)((pixel & 0b0000011111100000) >> 5)*(255/63)); 3232 color.b = (unsigned char)((float)(pixel & 0b0000000000011111)*(255/31)); 3233 color.a = 255; 3234 3235 } break; 3236 case PIXELFORMAT_UNCOMPRESSED_R4G4B4A4: 3237 { 3238 unsigned short pixel = ((unsigned short *)image.data)[y*image.width + x]; 3239 3240 color.r = (unsigned char)((float)((pixel & 0b1111000000000000) >> 12)*(255/15)); 3241 color.g = (unsigned char)((float)((pixel & 0b0000111100000000) >> 8)*(255/15)); 3242 color.b = (unsigned char)((float)((pixel & 0b0000000011110000) >> 4)*(255/15)); 3243 color.a = (unsigned char)((float)(pixel & 0b0000000000001111)*(255/15)); 3244 3245 } break; 3246 case PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: 3247 { 3248 color.r = ((unsigned char *)image.data)[(y*image.width + x)*4]; 3249 color.g = ((unsigned char *)image.data)[(y*image.width + x)*4 + 1]; 3250 color.b = ((unsigned char *)image.data)[(y*image.width + x)*4 + 2]; 3251 color.a = ((unsigned char *)image.data)[(y*image.width + x)*4 + 3]; 3252 3253 } break; 3254 case PIXELFORMAT_UNCOMPRESSED_R8G8B8: 3255 { 3256 color.r = (unsigned char)((unsigned char *)image.data)[(y*image.width + x)*3]; 3257 color.g = (unsigned char)((unsigned char *)image.data)[(y*image.width + x)*3 + 1]; 3258 color.b = (unsigned char)((unsigned char *)image.data)[(y*image.width + x)*3 + 2]; 3259 color.a = 255; 3260 3261 } break; 3262 case PIXELFORMAT_UNCOMPRESSED_R32: 3263 { 3264 color.r = (unsigned char)(((float *)image.data)[y*image.width + x]*255.0f); 3265 color.g = 0; 3266 color.b = 0; 3267 color.a = 255; 3268 3269 } break; 3270 case PIXELFORMAT_UNCOMPRESSED_R32G32B32: 3271 { 3272 color.r = (unsigned char)(((float *)image.data)[(y*image.width + x)*3]*255.0f); 3273 color.g = (unsigned char)(((float *)image.data)[(y*image.width + x)*3 + 1]*255.0f); 3274 color.b = (unsigned char)(((float *)image.data)[(y*image.width + x)*3 + 2]*255.0f); 3275 color.a = 255; 3276 3277 } break; 3278 case PIXELFORMAT_UNCOMPRESSED_R32G32B32A32: 3279 { 3280 color.r = (unsigned char)(((float *)image.data)[(y*image.width + x)*4]*255.0f); 3281 color.g = (unsigned char)(((float *)image.data)[(y*image.width + x)*4]*255.0f); 3282 color.b = (unsigned char)(((float *)image.data)[(y*image.width + x)*4]*255.0f); 3283 color.a = (unsigned char)(((float *)image.data)[(y*image.width + x)*4]*255.0f); 3284 3285 } break; 3286 case PIXELFORMAT_UNCOMPRESSED_R16: 3287 { 3288 color.r = (unsigned char)(HalfToFloat(((unsigned short *)image.data)[y*image.width + x])*255.0f); 3289 color.g = 0; 3290 color.b = 0; 3291 color.a = 255; 3292 3293 } break; 3294 case PIXELFORMAT_UNCOMPRESSED_R16G16B16: 3295 { 3296 color.r = (unsigned char)(HalfToFloat(((unsigned short *)image.data)[(y*image.width + x)*3])*255.0f); 3297 color.g = (unsigned char)(HalfToFloat(((unsigned short *)image.data)[(y*image.width + x)*3 + 1])*255.0f); 3298 color.b = (unsigned char)(HalfToFloat(((unsigned short *)image.data)[(y*image.width + x)*3 + 2])*255.0f); 3299 color.a = 255; 3300 3301 } break; 3302 case PIXELFORMAT_UNCOMPRESSED_R16G16B16A16: 3303 { 3304 color.r = (unsigned char)(HalfToFloat(((unsigned short *)image.data)[(y*image.width + x)*4])*255.0f); 3305 color.g = (unsigned char)(HalfToFloat(((unsigned short *)image.data)[(y*image.width + x)*4])*255.0f); 3306 color.b = (unsigned char)(HalfToFloat(((unsigned short *)image.data)[(y*image.width + x)*4])*255.0f); 3307 color.a = (unsigned char)(HalfToFloat(((unsigned short *)image.data)[(y*image.width + x)*4])*255.0f); 3308 3309 } break; 3310 default: TRACELOG(LOG_WARNING, "Compressed image format does not support color reading"); break; 3311 } 3312 } 3313 else TRACELOG(LOG_WARNING, "Requested image pixel (%i, %i) out of bounds", x, y); 3314 3315 return color; 3316 } 3317 3318 //------------------------------------------------------------------------------------ 3319 // Image drawing functions 3320 //------------------------------------------------------------------------------------ 3321 // Clear image background with given color 3322 void ImageClearBackground(Image *dst, Color color) 3323 { 3324 // Security check to avoid program crash 3325 if ((dst->data == NULL) || (dst->width == 0) || (dst->height == 0)) return; 3326 3327 // Fill in first pixel based on image format 3328 ImageDrawPixel(dst, 0, 0, color); 3329 3330 unsigned char *pSrcPixel = (unsigned char *)dst->data; 3331 int bytesPerPixel = GetPixelDataSize(1, 1, dst->format); 3332 3333 // Repeat the first pixel data throughout the image 3334 for (int i = 1; i < dst->width*dst->height; i++) 3335 { 3336 memcpy(pSrcPixel + i*bytesPerPixel, pSrcPixel, bytesPerPixel); 3337 } 3338 } 3339 3340 // Draw pixel within an image 3341 // NOTE: Compressed image formats not supported 3342 void ImageDrawPixel(Image *dst, int x, int y, Color color) 3343 { 3344 // Security check to avoid program crash 3345 if ((dst->data == NULL) || (x < 0) || (x >= dst->width) || (y < 0) || (y >= dst->height)) return; 3346 3347 switch (dst->format) 3348 { 3349 case PIXELFORMAT_UNCOMPRESSED_GRAYSCALE: 3350 { 3351 // NOTE: Calculate grayscale equivalent color 3352 Vector3 coln = { (float)color.r/255.0f, (float)color.g/255.0f, (float)color.b/255.0f }; 3353 unsigned char gray = (unsigned char)((coln.x*0.299f + coln.y*0.587f + coln.z*0.114f)*255.0f); 3354 3355 ((unsigned char *)dst->data)[y*dst->width + x] = gray; 3356 3357 } break; 3358 case PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA: 3359 { 3360 // NOTE: Calculate grayscale equivalent color 3361 Vector3 coln = { (float)color.r/255.0f, (float)color.g/255.0f, (float)color.b/255.0f }; 3362 unsigned char gray = (unsigned char)((coln.x*0.299f + coln.y*0.587f + coln.z*0.114f)*255.0f); 3363 3364 ((unsigned char *)dst->data)[(y*dst->width + x)*2] = gray; 3365 ((unsigned char *)dst->data)[(y*dst->width + x)*2 + 1] = color.a; 3366 3367 } break; 3368 case PIXELFORMAT_UNCOMPRESSED_R5G6B5: 3369 { 3370 // NOTE: Calculate R5G6B5 equivalent color 3371 Vector3 coln = { (float)color.r/255.0f, (float)color.g/255.0f, (float)color.b/255.0f }; 3372 3373 unsigned char r = (unsigned char)(round(coln.x*31.0f)); 3374 unsigned char g = (unsigned char)(round(coln.y*63.0f)); 3375 unsigned char b = (unsigned char)(round(coln.z*31.0f)); 3376 3377 ((unsigned short *)dst->data)[y*dst->width + x] = (unsigned short)r << 11 | (unsigned short)g << 5 | (unsigned short)b; 3378 3379 } break; 3380 case PIXELFORMAT_UNCOMPRESSED_R5G5B5A1: 3381 { 3382 // NOTE: Calculate R5G5B5A1 equivalent color 3383 Vector4 coln = { (float)color.r/255.0f, (float)color.g/255.0f, (float)color.b/255.0f, (float)color.a/255.0f }; 3384 3385 unsigned char r = (unsigned char)(round(coln.x*31.0f)); 3386 unsigned char g = (unsigned char)(round(coln.y*31.0f)); 3387 unsigned char b = (unsigned char)(round(coln.z*31.0f)); 3388 unsigned char a = (coln.w > ((float)PIXELFORMAT_UNCOMPRESSED_R5G5B5A1_ALPHA_THRESHOLD/255.0f))? 1 : 0; 3389 3390 ((unsigned short *)dst->data)[y*dst->width + x] = (unsigned short)r << 11 | (unsigned short)g << 6 | (unsigned short)b << 1 | (unsigned short)a; 3391 3392 } break; 3393 case PIXELFORMAT_UNCOMPRESSED_R4G4B4A4: 3394 { 3395 // NOTE: Calculate R5G5B5A1 equivalent color 3396 Vector4 coln = { (float)color.r/255.0f, (float)color.g/255.0f, (float)color.b/255.0f, (float)color.a/255.0f }; 3397 3398 unsigned char r = (unsigned char)(round(coln.x*15.0f)); 3399 unsigned char g = (unsigned char)(round(coln.y*15.0f)); 3400 unsigned char b = (unsigned char)(round(coln.z*15.0f)); 3401 unsigned char a = (unsigned char)(round(coln.w*15.0f)); 3402 3403 ((unsigned short *)dst->data)[y*dst->width + x] = (unsigned short)r << 12 | (unsigned short)g << 8 | (unsigned short)b << 4 | (unsigned short)a; 3404 3405 } break; 3406 case PIXELFORMAT_UNCOMPRESSED_R8G8B8: 3407 { 3408 ((unsigned char *)dst->data)[(y*dst->width + x)*3] = color.r; 3409 ((unsigned char *)dst->data)[(y*dst->width + x)*3 + 1] = color.g; 3410 ((unsigned char *)dst->data)[(y*dst->width + x)*3 + 2] = color.b; 3411 3412 } break; 3413 case PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: 3414 { 3415 ((unsigned char *)dst->data)[(y*dst->width + x)*4] = color.r; 3416 ((unsigned char *)dst->data)[(y*dst->width + x)*4 + 1] = color.g; 3417 ((unsigned char *)dst->data)[(y*dst->width + x)*4 + 2] = color.b; 3418 ((unsigned char *)dst->data)[(y*dst->width + x)*4 + 3] = color.a; 3419 3420 } break; 3421 case PIXELFORMAT_UNCOMPRESSED_R32: 3422 { 3423 // NOTE: Calculate grayscale equivalent color (normalized to 32bit) 3424 Vector3 coln = { (float)color.r/255.0f, (float)color.g/255.0f, (float)color.b/255.0f }; 3425 3426 ((float *)dst->data)[y*dst->width + x] = coln.x*0.299f + coln.y*0.587f + coln.z*0.114f; 3427 3428 } break; 3429 case PIXELFORMAT_UNCOMPRESSED_R32G32B32: 3430 { 3431 // NOTE: Calculate R32G32B32 equivalent color (normalized to 32bit) 3432 Vector3 coln = { (float)color.r/255.0f, (float)color.g/255.0f, (float)color.b/255.0f }; 3433 3434 ((float *)dst->data)[(y*dst->width + x)*3] = coln.x; 3435 ((float *)dst->data)[(y*dst->width + x)*3 + 1] = coln.y; 3436 ((float *)dst->data)[(y*dst->width + x)*3 + 2] = coln.z; 3437 } break; 3438 case PIXELFORMAT_UNCOMPRESSED_R32G32B32A32: 3439 { 3440 // NOTE: Calculate R32G32B32A32 equivalent color (normalized to 32bit) 3441 Vector4 coln = { (float)color.r/255.0f, (float)color.g/255.0f, (float)color.b/255.0f, (float)color.a/255.0f }; 3442 3443 ((float *)dst->data)[(y*dst->width + x)*4] = coln.x; 3444 ((float *)dst->data)[(y*dst->width + x)*4 + 1] = coln.y; 3445 ((float *)dst->data)[(y*dst->width + x)*4 + 2] = coln.z; 3446 ((float *)dst->data)[(y*dst->width + x)*4 + 3] = coln.w; 3447 3448 } break; 3449 case PIXELFORMAT_UNCOMPRESSED_R16: 3450 { 3451 // NOTE: Calculate grayscale equivalent color (normalized to 32bit) 3452 Vector3 coln = { (float)color.r/255.0f, (float)color.g/255.0f, (float)color.b/255.0f }; 3453 3454 ((unsigned short*)dst->data)[y*dst->width + x] = FloatToHalf(coln.x*0.299f + coln.y*0.587f + coln.z*0.114f); 3455 3456 } break; 3457 case PIXELFORMAT_UNCOMPRESSED_R16G16B16: 3458 { 3459 // NOTE: Calculate R32G32B32 equivalent color (normalized to 32bit) 3460 Vector3 coln = { (float)color.r/255.0f, (float)color.g/255.0f, (float)color.b/255.0f }; 3461 3462 ((unsigned short *)dst->data)[(y*dst->width + x)*3] = FloatToHalf(coln.x); 3463 ((unsigned short *)dst->data)[(y*dst->width + x)*3 + 1] = FloatToHalf(coln.y); 3464 ((unsigned short *)dst->data)[(y*dst->width + x)*3 + 2] = FloatToHalf(coln.z); 3465 } break; 3466 case PIXELFORMAT_UNCOMPRESSED_R16G16B16A16: 3467 { 3468 // NOTE: Calculate R32G32B32A32 equivalent color (normalized to 32bit) 3469 Vector4 coln = { (float)color.r/255.0f, (float)color.g/255.0f, (float)color.b/255.0f, (float)color.a/255.0f }; 3470 3471 ((unsigned short *)dst->data)[(y*dst->width + x)*4] = FloatToHalf(coln.x); 3472 ((unsigned short *)dst->data)[(y*dst->width + x)*4 + 1] = FloatToHalf(coln.y); 3473 ((unsigned short *)dst->data)[(y*dst->width + x)*4 + 2] = FloatToHalf(coln.z); 3474 ((unsigned short *)dst->data)[(y*dst->width + x)*4 + 3] = FloatToHalf(coln.w); 3475 3476 } break; 3477 default: break; 3478 } 3479 } 3480 3481 // Draw pixel within an image (Vector version) 3482 void ImageDrawPixelV(Image *dst, Vector2 position, Color color) 3483 { 3484 ImageDrawPixel(dst, (int)position.x, (int)position.y, color); 3485 } 3486 3487 // Draw line within an image 3488 void ImageDrawLine(Image *dst, int startPosX, int startPosY, int endPosX, int endPosY, Color color) 3489 { 3490 // Calculate differences in coordinates 3491 int shortLen = endPosY - startPosY; 3492 int longLen = endPosX - startPosX; 3493 bool yLonger = false; 3494 3495 // Determine if the line is more vertical than horizontal 3496 if (abs(shortLen) > abs(longLen)) 3497 { 3498 // Swap the lengths if the line is more vertical 3499 int temp = shortLen; 3500 shortLen = longLen; 3501 longLen = temp; 3502 yLonger = true; 3503 } 3504 3505 // Initialize variables for drawing loop 3506 int endVal = longLen; 3507 int sgnInc = 1; 3508 3509 // Adjust direction increment based on longLen sign 3510 if (longLen < 0) 3511 { 3512 longLen = -longLen; 3513 sgnInc = -1; 3514 } 3515 3516 // Calculate fixed-point increment for shorter length 3517 int decInc = (longLen == 0)? 0 : (shortLen << 16)/longLen; 3518 3519 // Draw the line pixel by pixel 3520 if (yLonger) 3521 { 3522 // If line is more vertical, iterate over y-axis 3523 for (int i = 0, j = 0; i != endVal; i += sgnInc, j += decInc) 3524 { 3525 // Calculate pixel position and draw it 3526 ImageDrawPixel(dst, startPosX + (j >> 16), startPosY + i, color); 3527 } 3528 } 3529 else 3530 { 3531 // If line is more horizontal, iterate over x-axis 3532 for (int i = 0, j = 0; i != endVal; i += sgnInc, j += decInc) 3533 { 3534 // Calculate pixel position and draw it 3535 ImageDrawPixel(dst, startPosX + i, startPosY + (j >> 16), color); 3536 } 3537 } 3538 } 3539 3540 // Draw line within an image (Vector version) 3541 void ImageDrawLineV(Image *dst, Vector2 start, Vector2 end, Color color) 3542 { 3543 // Round start and end positions to nearest integer coordinates 3544 int x1 = (int)(start.x + 0.5f); 3545 int y1 = (int)(start.y + 0.5f); 3546 int x2 = (int)(end.x + 0.5f); 3547 int y2 = (int)(end.y + 0.5f); 3548 3549 // Draw a vertical line using ImageDrawLine function 3550 ImageDrawLine(dst, x1, y1, x2, y2, color); 3551 } 3552 3553 // Draw a line defining thickness within an image 3554 void ImageDrawLineEx(Image *dst, Vector2 start, Vector2 end, int thick, Color color) 3555 { 3556 // Round start and end positions to nearest integer coordinates 3557 int x1 = (int)(start.x + 0.5f); 3558 int y1 = (int)(start.y + 0.5f); 3559 int x2 = (int)(end.x + 0.5f); 3560 int y2 = (int)(end.y + 0.5f); 3561 3562 // Calculate differences in x and y coordinates 3563 int dx = x2 - x1; 3564 int dy = y2 - y1; 3565 3566 // Draw the main line between (x1, y1) and (x2, y2) 3567 ImageDrawLine(dst, x1, y1, x2, y2, color); 3568 3569 // Determine if the line is more horizontal or vertical 3570 if (dx != 0 && abs(dy/dx) < 1) 3571 { 3572 // Line is more horizontal 3573 // Calculate half the width of the line 3574 int wy = (thick - 1)*(int)sqrtf((float)(dx*dx + dy*dy))/(2*abs(dx)); 3575 3576 // Draw additional lines above and below the main line 3577 for (int i = 1; i <= wy; i++) 3578 { 3579 ImageDrawLine(dst, x1, y1 - i, x2, y2 - i, color); // Draw above the main line 3580 ImageDrawLine(dst, x1, y1 + i, x2, y2 + i, color); // Draw below the main line 3581 } 3582 } 3583 else if (dy != 0) 3584 { 3585 // Line is more vertical or perfectly horizontal 3586 // Calculate half the width of the line 3587 int wx = (thick - 1)*(int)sqrtf((float)(dx*dx + dy*dy))/(2*abs(dy)); 3588 3589 // Draw additional lines to the left and right of the main line 3590 for (int i = 1; i <= wx; i++) 3591 { 3592 ImageDrawLine(dst, x1 - i, y1, x2 - i, y2, color); // Draw left of the main line 3593 ImageDrawLine(dst, x1 + i, y1, x2 + i, y2, color); // Draw right of the main line 3594 } 3595 } 3596 } 3597 3598 // Draw circle within an image 3599 void ImageDrawCircle(Image* dst, int centerX, int centerY, int radius, Color color) 3600 { 3601 int x = 0; 3602 int y = radius; 3603 int decesionParameter = 3 - 2*radius; 3604 3605 while (y >= x) 3606 { 3607 ImageDrawRectangle(dst, centerX - x, centerY + y, x*2, 1, color); 3608 ImageDrawRectangle(dst, centerX - x, centerY - y, x*2, 1, color); 3609 ImageDrawRectangle(dst, centerX - y, centerY + x, y*2, 1, color); 3610 ImageDrawRectangle(dst, centerX - y, centerY - x, y*2, 1, color); 3611 x++; 3612 3613 if (decesionParameter > 0) 3614 { 3615 y--; 3616 decesionParameter = decesionParameter + 4*(x - y) + 10; 3617 } 3618 else decesionParameter = decesionParameter + 4*x + 6; 3619 } 3620 } 3621 3622 // Draw circle within an image (Vector version) 3623 void ImageDrawCircleV(Image* dst, Vector2 center, int radius, Color color) 3624 { 3625 ImageDrawCircle(dst, (int)center.x, (int)center.y, radius, color); 3626 } 3627 3628 // Draw circle outline within an image 3629 void ImageDrawCircleLines(Image *dst, int centerX, int centerY, int radius, Color color) 3630 { 3631 int x = 0; 3632 int y = radius; 3633 int decesionParameter = 3 - 2*radius; 3634 3635 while (y >= x) 3636 { 3637 ImageDrawPixel(dst, centerX + x, centerY + y, color); 3638 ImageDrawPixel(dst, centerX - x, centerY + y, color); 3639 ImageDrawPixel(dst, centerX + x, centerY - y, color); 3640 ImageDrawPixel(dst, centerX - x, centerY - y, color); 3641 ImageDrawPixel(dst, centerX + y, centerY + x, color); 3642 ImageDrawPixel(dst, centerX - y, centerY + x, color); 3643 ImageDrawPixel(dst, centerX + y, centerY - x, color); 3644 ImageDrawPixel(dst, centerX - y, centerY - x, color); 3645 x++; 3646 3647 if (decesionParameter > 0) 3648 { 3649 y--; 3650 decesionParameter = decesionParameter + 4*(x - y) + 10; 3651 } 3652 else decesionParameter = decesionParameter + 4*x + 6; 3653 } 3654 } 3655 3656 // Draw circle outline within an image (Vector version) 3657 void ImageDrawCircleLinesV(Image *dst, Vector2 center, int radius, Color color) 3658 { 3659 ImageDrawCircleLines(dst, (int)center.x, (int)center.y, radius, color); 3660 } 3661 3662 // Draw rectangle within an image 3663 void ImageDrawRectangle(Image *dst, int posX, int posY, int width, int height, Color color) 3664 { 3665 ImageDrawRectangleRec(dst, (Rectangle){ (float)posX, (float)posY, (float)width, (float)height }, color); 3666 } 3667 3668 // Draw rectangle within an image (Vector version) 3669 void ImageDrawRectangleV(Image *dst, Vector2 position, Vector2 size, Color color) 3670 { 3671 ImageDrawRectangle(dst, (int)position.x, (int)position.y, (int)size.x, (int)size.y, color); 3672 } 3673 3674 // Draw rectangle within an image 3675 void ImageDrawRectangleRec(Image *dst, Rectangle rec, Color color) 3676 { 3677 // Security check to avoid program crash 3678 if ((dst->data == NULL) || (dst->width == 0) || (dst->height == 0)) return; 3679 3680 // Security check to avoid drawing out of bounds in case of bad user data 3681 if (rec.x < 0) { rec.width += rec.x; rec.x = 0; } 3682 if (rec.y < 0) { rec.height += rec.y; rec.y = 0; } 3683 if (rec.width < 0) rec.width = 0; 3684 if (rec.height < 0) rec.height = 0; 3685 3686 // Clamp the size the the image bounds 3687 if ((rec.x + rec.width) >= dst->width) rec.width = dst->width - rec.x; 3688 if ((rec.y + rec.height) >= dst->height) rec.height = dst->height - rec.y; 3689 3690 // Check if the rect is even inside the image 3691 if ((rec.x >= dst->width) || (rec.y >= dst->height)) return; 3692 if (((rec.x + rec.width) <= 0) || (rec.y + rec.height <= 0)) return; 3693 3694 int sy = (int)rec.y; 3695 int sx = (int)rec.x; 3696 3697 int bytesPerPixel = GetPixelDataSize(1, 1, dst->format); 3698 3699 // Fill in the first pixel of the first row based on image format 3700 ImageDrawPixel(dst, sx, sy, color); 3701 3702 int bytesOffset = ((sy*dst->width) + sx)*bytesPerPixel; 3703 unsigned char *pSrcPixel = (unsigned char *)dst->data + bytesOffset; 3704 3705 // Repeat the first pixel data throughout the row 3706 for (int x = 1; x < (int)rec.width; x++) 3707 { 3708 memcpy(pSrcPixel + x*bytesPerPixel, pSrcPixel, bytesPerPixel); 3709 } 3710 3711 // Repeat the first row data for all other rows 3712 int bytesPerRow = bytesPerPixel*(int)rec.width; 3713 for (int y = 1; y < (int)rec.height; y++) 3714 { 3715 memcpy(pSrcPixel + (y*dst->width)*bytesPerPixel, pSrcPixel, bytesPerRow); 3716 } 3717 } 3718 3719 // Draw rectangle lines within an image 3720 void ImageDrawRectangleLines(Image *dst, Rectangle rec, int thick, Color color) 3721 { 3722 ImageDrawRectangle(dst, (int)rec.x, (int)rec.y, (int)rec.width, thick, color); 3723 ImageDrawRectangle(dst, (int)rec.x, (int)(rec.y + thick), thick, (int)(rec.height - thick*2), color); 3724 ImageDrawRectangle(dst, (int)(rec.x + rec.width - thick), (int)(rec.y + thick), thick, (int)(rec.height - thick*2), color); 3725 ImageDrawRectangle(dst, (int)rec.x, (int)(rec.y + rec.height - thick), (int)rec.width, thick, color); 3726 } 3727 3728 // Draw triangle within an image 3729 void ImageDrawTriangle(Image *dst, Vector2 v1, Vector2 v2, Vector2 v3, Color color) 3730 { 3731 // Calculate the 2D bounding box of the triangle 3732 // Determine the minimum and maximum x and y coordinates of the triangle vertices 3733 int xMin = (int)((v1.x < v2.x)? ((v1.x < v3.x)? v1.x : v3.x) : ((v2.x < v3.x)? v2.x : v3.x)); 3734 int yMin = (int)((v1.y < v2.y)? ((v1.y < v3.y)? v1.y : v3.y) : ((v2.y < v3.y)? v2.y : v3.y)); 3735 int xMax = (int)((v1.x > v2.x)? ((v1.x > v3.x)? v1.x : v3.x) : ((v2.x > v3.x)? v2.x : v3.x)); 3736 int yMax = (int)((v1.y > v2.y)? ((v1.y > v3.y)? v1.y : v3.y) : ((v2.y > v3.y)? v2.y : v3.y)); 3737 3738 // Clamp the bounding box to the image dimensions 3739 if (xMin < 0) xMin = 0; 3740 if (yMin < 0) yMin = 0; 3741 if (xMax > dst->width) xMax = dst->width; 3742 if (yMax > dst->height) yMax = dst->height; 3743 3744 // Check the order of the vertices to determine if it's a front or back face 3745 // NOTE: if signedArea is equal to 0, the face is degenerate 3746 float signedArea = (v2.x - v1.x)*(v3.y - v1.y) - (v3.x - v1.x)*(v2.y - v1.y); 3747 bool isBackFace = (signedArea > 0); 3748 3749 // Barycentric interpolation setup 3750 // Calculate the step increments for the barycentric coordinates 3751 int w1XStep = (int)(v3.y - v2.y), w1YStep = (int)(v2.x - v3.x); 3752 int w2XStep = (int)(v1.y - v3.y), w2YStep = (int)(v3.x - v1.x); 3753 int w3XStep = (int)(v2.y - v1.y), w3YStep = (int)(v1.x - v2.x); 3754 3755 // If the triangle is a back face, invert the steps 3756 if (isBackFace) 3757 { 3758 w1XStep = -w1XStep, w1YStep = -w1YStep; 3759 w2XStep = -w2XStep, w2YStep = -w2YStep; 3760 w3XStep = -w3XStep, w3YStep = -w3YStep; 3761 } 3762 3763 // Calculate the initial barycentric coordinates for the top-left point of the bounding box 3764 int w1Row = (int)((xMin - v2.x)*w1XStep + w1YStep*(yMin - v2.y)); 3765 int w2Row = (int)((xMin - v3.x)*w2XStep + w2YStep*(yMin - v3.y)); 3766 int w3Row = (int)((xMin - v1.x)*w3XStep + w3YStep*(yMin - v1.y)); 3767 3768 // Rasterization loop 3769 // Iterate through each pixel in the bounding box 3770 for (int y = yMin; y <= yMax; y++) 3771 { 3772 int w1 = w1Row; 3773 int w2 = w2Row; 3774 int w3 = w3Row; 3775 3776 for (int x = xMin; x <= xMax; x++) 3777 { 3778 // Check if the pixel is inside the triangle using barycentric coordinates 3779 // If it is then we can draw the pixel with the given color 3780 if ((w1 | w2 | w3) >= 0) ImageDrawPixel(dst, x, y, color); 3781 3782 // Increment the barycentric coordinates for the next pixel 3783 w1 += w1XStep; 3784 w2 += w2XStep; 3785 w3 += w3XStep; 3786 } 3787 3788 // Move to the next row in the bounding box 3789 w1Row += w1YStep; 3790 w2Row += w2YStep; 3791 w3Row += w3YStep; 3792 } 3793 } 3794 3795 // Draw triangle with interpolated colors within an image 3796 void ImageDrawTriangleEx(Image *dst, Vector2 v1, Vector2 v2, Vector2 v3, Color c1, Color c2, Color c3) 3797 { 3798 // Calculate the 2D bounding box of the triangle 3799 // Determine the minimum and maximum x and y coordinates of the triangle vertices 3800 int xMin = (int)((v1.x < v2.x)? ((v1.x < v3.x)? v1.x : v3.x) : ((v2.x < v3.x)? v2.x : v3.x)); 3801 int yMin = (int)((v1.y < v2.y)? ((v1.y < v3.y)? v1.y : v3.y) : ((v2.y < v3.y)? v2.y : v3.y)); 3802 int xMax = (int)((v1.x > v2.x)? ((v1.x > v3.x)? v1.x : v3.x) : ((v2.x > v3.x)? v2.x : v3.x)); 3803 int yMax = (int)((v1.y > v2.y)? ((v1.y > v3.y)? v1.y : v3.y) : ((v2.y > v3.y)? v2.y : v3.y)); 3804 3805 // Clamp the bounding box to the image dimensions 3806 if (xMin < 0) xMin = 0; 3807 if (yMin < 0) yMin = 0; 3808 if (xMax > dst->width) xMax = dst->width; 3809 if (yMax > dst->height) yMax = dst->height; 3810 3811 // Check the order of the vertices to determine if it's a front or back face 3812 // NOTE: if signedArea is equal to 0, the face is degenerate 3813 float signedArea = (v2.x - v1.x)*(v3.y - v1.y) - (v3.x - v1.x)*(v2.y - v1.y); 3814 bool isBackFace = (signedArea > 0); 3815 3816 // Barycentric interpolation setup 3817 // Calculate the step increments for the barycentric coordinates 3818 int w1XStep = (int)(v3.y - v2.y), w1YStep = (int)(v2.x - v3.x); 3819 int w2XStep = (int)(v1.y - v3.y), w2YStep = (int)(v3.x - v1.x); 3820 int w3XStep = (int)(v2.y - v1.y), w3YStep = (int)(v1.x - v2.x); 3821 3822 // If the triangle is a back face, invert the steps 3823 if (isBackFace) 3824 { 3825 w1XStep = -w1XStep, w1YStep = -w1YStep; 3826 w2XStep = -w2XStep, w2YStep = -w2YStep; 3827 w3XStep = -w3XStep, w3YStep = -w3YStep; 3828 } 3829 3830 // Calculate the initial barycentric coordinates for the top-left point of the bounding box 3831 int w1Row = (int)((xMin - v2.x)*w1XStep + w1YStep*(yMin - v2.y)); 3832 int w2Row = (int)((xMin - v3.x)*w2XStep + w2YStep*(yMin - v3.y)); 3833 int w3Row = (int)((xMin - v1.x)*w3XStep + w3YStep*(yMin - v1.y)); 3834 3835 // Calculate the inverse of the sum of the barycentric coordinates for normalization 3836 // NOTE 1: Here, we act as if we multiply by 255 the reciprocal, which avoids additional 3837 // calculations in the loop. This is acceptable because we are only interpolating colors. 3838 // NOTE 2: This sum remains constant throughout the triangle 3839 float wInvSum = 255.0f/(w1Row + w2Row + w3Row); 3840 3841 // Rasterization loop 3842 // Iterate through each pixel in the bounding box 3843 for (int y = yMin; y <= yMax; y++) 3844 { 3845 int w1 = w1Row; 3846 int w2 = w2Row; 3847 int w3 = w3Row; 3848 3849 for (int x = xMin; x <= xMax; x++) 3850 { 3851 // Check if the pixel is inside the triangle using barycentric coordinates 3852 if ((w1 | w2 | w3) >= 0) 3853 { 3854 // Compute the normalized barycentric coordinates 3855 unsigned char aW1 = (unsigned char)((float)w1*wInvSum); 3856 unsigned char aW2 = (unsigned char)((float)w2*wInvSum); 3857 unsigned char aW3 = (unsigned char)((float)w3*wInvSum); 3858 3859 // Interpolate the color using the barycentric coordinates 3860 Color finalColor = { 0 }; 3861 finalColor.r = (c1.r*aW1 + c2.r*aW2 + c3.r*aW3)/255; 3862 finalColor.g = (c1.g*aW1 + c2.g*aW2 + c3.g*aW3)/255; 3863 finalColor.b = (c1.b*aW1 + c2.b*aW2 + c3.b*aW3)/255; 3864 finalColor.a = (c1.a*aW1 + c2.a*aW2 + c3.a*aW3)/255; 3865 3866 // Draw the pixel with the interpolated color 3867 ImageDrawPixel(dst, x, y, finalColor); 3868 } 3869 3870 // Increment the barycentric coordinates for the next pixel 3871 w1 += w1XStep; 3872 w2 += w2XStep; 3873 w3 += w3XStep; 3874 } 3875 3876 // Move to the next row in the bounding box 3877 w1Row += w1YStep; 3878 w2Row += w2YStep; 3879 w3Row += w3YStep; 3880 } 3881 } 3882 3883 // Draw triangle outline within an image 3884 void ImageDrawTriangleLines(Image *dst, Vector2 v1, Vector2 v2, Vector2 v3, Color color) 3885 { 3886 ImageDrawLine(dst, (int)v1.x, (int)v1.y, (int)v2.x, (int)v2.y, color); 3887 ImageDrawLine(dst, (int)v2.x, (int)v2.y, (int)v3.x, (int)v3.y, color); 3888 ImageDrawLine(dst, (int)v3.x, (int)v3.y, (int)v1.x, (int)v1.y, color); 3889 } 3890 3891 // Draw a triangle fan defined by points within an image (first vertex is the center) 3892 void ImageDrawTriangleFan(Image *dst, Vector2 *points, int pointCount, Color color) 3893 { 3894 if (pointCount >= 3) 3895 { 3896 for (int i = 1; i < pointCount - 1; i++) 3897 { 3898 ImageDrawTriangle(dst, points[0], points[i], points[i + 1], color); 3899 } 3900 } 3901 } 3902 3903 // Draw a triangle strip defined by points within an image 3904 void ImageDrawTriangleStrip(Image *dst, Vector2 *points, int pointCount, Color color) 3905 { 3906 if (pointCount >= 3) 3907 { 3908 for (int i = 2; i < pointCount; i++) 3909 { 3910 if ((i%2) == 0) ImageDrawTriangle(dst, points[i], points[i - 2], points[i - 1], color); 3911 else ImageDrawTriangle(dst, points[i], points[i - 1], points[i - 2], color); 3912 } 3913 } 3914 } 3915 3916 // Draw an image (source) within an image (destination) 3917 // NOTE: Color tint is applied to source image 3918 void ImageDraw(Image *dst, Image src, Rectangle srcRec, Rectangle dstRec, Color tint) 3919 { 3920 // Security check to avoid program crash 3921 if ((dst->data == NULL) || (dst->width == 0) || (dst->height == 0) || 3922 (src.data == NULL) || (src.width == 0) || (src.height == 0)) return; 3923 3924 if (dst->format >= PIXELFORMAT_COMPRESSED_DXT1_RGB) TRACELOG(LOG_WARNING, "Image drawing not supported for compressed formats"); 3925 else 3926 { 3927 Image srcMod = { 0 }; // Source copy (in case it was required) 3928 Image *srcPtr = &src; // Pointer to source image 3929 bool useSrcMod = false; // Track source copy required 3930 3931 // Source rectangle out-of-bounds security checks 3932 if (srcRec.x < 0) { srcRec.width += srcRec.x; srcRec.x = 0; } 3933 if (srcRec.y < 0) { srcRec.height += srcRec.y; srcRec.y = 0; } 3934 if ((srcRec.x + srcRec.width) > src.width) srcRec.width = src.width - srcRec.x; 3935 if ((srcRec.y + srcRec.height) > src.height) srcRec.height = src.height - srcRec.y; 3936 3937 // Check if source rectangle needs to be resized to destination rectangle 3938 // In that case, we make a copy of source, and we apply all required transform 3939 if (((int)srcRec.width != (int)dstRec.width) || ((int)srcRec.height != (int)dstRec.height)) 3940 { 3941 srcMod = ImageFromImage(src, srcRec); // Create image from another image 3942 ImageResize(&srcMod, (int)dstRec.width, (int)dstRec.height); // Resize to destination rectangle 3943 srcRec = (Rectangle){ 0, 0, (float)srcMod.width, (float)srcMod.height }; 3944 3945 srcPtr = &srcMod; 3946 useSrcMod = true; 3947 } 3948 3949 // Destination rectangle out-of-bounds security checks 3950 if (dstRec.x < 0) 3951 { 3952 srcRec.x -= dstRec.x; 3953 srcRec.width += dstRec.x; 3954 dstRec.x = 0; 3955 } 3956 else if ((dstRec.x + srcRec.width) > dst->width) srcRec.width = dst->width - dstRec.x; 3957 3958 if (dstRec.y < 0) 3959 { 3960 srcRec.y -= dstRec.y; 3961 srcRec.height += dstRec.y; 3962 dstRec.y = 0; 3963 } 3964 else if ((dstRec.y + srcRec.height) > dst->height) srcRec.height = dst->height - dstRec.y; 3965 3966 if (dst->width < srcRec.width) srcRec.width = (float)dst->width; 3967 if (dst->height < srcRec.height) srcRec.height = (float)dst->height; 3968 3969 // This blitting method is quite fast! The process followed is: 3970 // for every pixel -> [get_src_format/get_dst_format -> blend -> format_to_dst] 3971 // Some optimization ideas: 3972 // [x] Avoid creating source copy if not required (no resize required) 3973 // [x] Optimize ImageResize() for pixel format (alternative: ImageResizeNN()) 3974 // [x] Optimize ColorAlphaBlend() to avoid processing (alpha = 0) and (alpha = 1) 3975 // [x] Optimize ColorAlphaBlend() for faster operations (maybe avoiding divs?) 3976 // [x] Consider fast path: no alpha blending required cases (src has no alpha) 3977 // [x] Consider fast path: same src/dst format with no alpha -> direct line copy 3978 // [-] GetPixelColor(): Get Vector4 instead of Color, easier for ColorAlphaBlend() 3979 // [ ] Support f32bit channels drawing 3980 3981 // TODO: Support PIXELFORMAT_UNCOMPRESSED_R32G32B32A32 and PIXELFORMAT_UNCOMPRESSED_R1616B16A16 3982 3983 Color colSrc, colDst, blend; 3984 bool blendRequired = true; 3985 3986 // Fast path: Avoid blend if source has no alpha to blend 3987 if ((tint.a == 255) && 3988 ((srcPtr->format == PIXELFORMAT_UNCOMPRESSED_GRAYSCALE) || 3989 (srcPtr->format == PIXELFORMAT_UNCOMPRESSED_R5G6B5) || 3990 (srcPtr->format == PIXELFORMAT_UNCOMPRESSED_R8G8B8) || 3991 (srcPtr->format == PIXELFORMAT_UNCOMPRESSED_R32) || 3992 (srcPtr->format == PIXELFORMAT_UNCOMPRESSED_R32G32B32) || 3993 (srcPtr->format == PIXELFORMAT_UNCOMPRESSED_R16) || 3994 (srcPtr->format == PIXELFORMAT_UNCOMPRESSED_R16G16B16))) 3995 blendRequired = false; 3996 3997 int strideDst = GetPixelDataSize(dst->width, 1, dst->format); 3998 int bytesPerPixelDst = strideDst/(dst->width); 3999 4000 int strideSrc = GetPixelDataSize(srcPtr->width, 1, srcPtr->format); 4001 int bytesPerPixelSrc = strideSrc/(srcPtr->width); 4002 4003 unsigned char *pSrcBase = (unsigned char *)srcPtr->data + ((int)srcRec.y*srcPtr->width + (int)srcRec.x)*bytesPerPixelSrc; 4004 unsigned char *pDstBase = (unsigned char *)dst->data + ((int)dstRec.y*dst->width + (int)dstRec.x)*bytesPerPixelDst; 4005 4006 for (int y = 0; y < (int)srcRec.height; y++) 4007 { 4008 unsigned char *pSrc = pSrcBase; 4009 unsigned char *pDst = pDstBase; 4010 4011 // Fast path: Avoid moving pixel by pixel if no blend required and same format 4012 if (!blendRequired && (srcPtr->format == dst->format)) memcpy(pDst, pSrc, (int)(srcRec.width)*bytesPerPixelSrc); 4013 else 4014 { 4015 for (int x = 0; x < (int)srcRec.width; x++) 4016 { 4017 colSrc = GetPixelColor(pSrc, srcPtr->format); 4018 colDst = GetPixelColor(pDst, dst->format); 4019 4020 // Fast path: Avoid blend if source has no alpha to blend 4021 if (blendRequired) blend = ColorAlphaBlend(colDst, colSrc, tint); 4022 else blend = colSrc; 4023 4024 SetPixelColor(pDst, blend, dst->format); 4025 4026 pDst += bytesPerPixelDst; 4027 pSrc += bytesPerPixelSrc; 4028 } 4029 } 4030 4031 pSrcBase += strideSrc; 4032 pDstBase += strideDst; 4033 } 4034 4035 if (useSrcMod) UnloadImage(srcMod); // Unload source modified image 4036 4037 if ((dst->mipmaps > 1) && (src.mipmaps > 1)) 4038 { 4039 Image mipmapDst = *dst; 4040 mipmapDst.data = (char *)mipmapDst.data + GetPixelDataSize(mipmapDst.width, mipmapDst.height, mipmapDst.format); 4041 mipmapDst.width /= 2; 4042 mipmapDst.height /= 2; 4043 mipmapDst.mipmaps--; 4044 4045 Image mipmapSrc = src; 4046 mipmapSrc.data = (char *)mipmapSrc.data + GetPixelDataSize(mipmapSrc.width, mipmapSrc.height, mipmapSrc.format); 4047 mipmapSrc.width /= 2; 4048 mipmapSrc.height /= 2; 4049 mipmapSrc.mipmaps--; 4050 4051 Rectangle mipmapSrcRec = srcRec; 4052 mipmapSrcRec.width /= 2; 4053 mipmapSrcRec.height /= 2; 4054 mipmapSrcRec.x /= 2; 4055 mipmapSrcRec.y /= 2; 4056 4057 Rectangle mipmapDstRec = dstRec; 4058 mipmapDstRec.width /= 2; 4059 mipmapDstRec.height /= 2; 4060 mipmapDstRec.x /= 2; 4061 mipmapDstRec.y /= 2; 4062 4063 ImageDraw(&mipmapDst, mipmapSrc, mipmapSrcRec, mipmapDstRec, tint); 4064 } 4065 } 4066 } 4067 4068 // Draw text (default font) within an image (destination) 4069 void ImageDrawText(Image *dst, const char *text, int posX, int posY, int fontSize, Color color) 4070 { 4071 #if defined(SUPPORT_MODULE_RTEXT) && defined(SUPPORT_DEFAULT_FONT) 4072 // Make sure default font is loaded to be used on image text drawing 4073 if (GetFontDefault().texture.id == 0) LoadFontDefault(); 4074 4075 Vector2 position = { (float)posX, (float)posY }; 4076 ImageDrawTextEx(dst, GetFontDefault(), text, position, (float)fontSize, 1.0f, color); // WARNING: Module required: rtext 4077 #else 4078 TRACELOG(LOG_WARNING, "IMAGE: ImageDrawText() requires module: rtext"); 4079 #endif 4080 } 4081 4082 // Draw text (custom sprite font) within an image (destination) 4083 void ImageDrawTextEx(Image *dst, Font font, const char *text, Vector2 position, float fontSize, float spacing, Color tint) 4084 { 4085 Image imText = ImageTextEx(font, text, fontSize, spacing, tint); 4086 4087 Rectangle srcRec = { 0.0f, 0.0f, (float)imText.width, (float)imText.height }; 4088 Rectangle dstRec = { position.x, position.y, (float)imText.width, (float)imText.height }; 4089 4090 ImageDraw(dst, imText, srcRec, dstRec, WHITE); 4091 4092 UnloadImage(imText); 4093 } 4094 4095 //------------------------------------------------------------------------------------ 4096 // Texture loading functions 4097 //------------------------------------------------------------------------------------ 4098 // Load texture from file into GPU memory (VRAM) 4099 Texture2D LoadTexture(const char *fileName) 4100 { 4101 Texture2D texture = { 0 }; 4102 4103 Image image = LoadImage(fileName); 4104 4105 if (image.data != NULL) 4106 { 4107 texture = LoadTextureFromImage(image); 4108 UnloadImage(image); 4109 } 4110 4111 return texture; 4112 } 4113 4114 // Load a texture from image data 4115 // NOTE: image is not unloaded, it must be done manually 4116 Texture2D LoadTextureFromImage(Image image) 4117 { 4118 Texture2D texture = { 0 }; 4119 4120 if ((image.width != 0) && (image.height != 0)) 4121 { 4122 texture.id = rlLoadTexture(image.data, image.width, image.height, image.format, image.mipmaps); 4123 } 4124 else TRACELOG(LOG_WARNING, "IMAGE: Data is not valid to load texture"); 4125 4126 texture.width = image.width; 4127 texture.height = image.height; 4128 texture.mipmaps = image.mipmaps; 4129 texture.format = image.format; 4130 4131 return texture; 4132 } 4133 4134 // Load cubemap from image, multiple image cubemap layouts supported 4135 TextureCubemap LoadTextureCubemap(Image image, int layout) 4136 { 4137 TextureCubemap cubemap = { 0 }; 4138 4139 if (layout == CUBEMAP_LAYOUT_AUTO_DETECT) // Try to automatically guess layout type 4140 { 4141 // Check image width/height to determine the type of cubemap provided 4142 if (image.width > image.height) 4143 { 4144 if ((image.width/6) == image.height) { layout = CUBEMAP_LAYOUT_LINE_HORIZONTAL; cubemap.width = image.width/6; } 4145 else if ((image.width/4) == (image.height/3)) { layout = CUBEMAP_LAYOUT_CROSS_FOUR_BY_THREE; cubemap.width = image.width/4; } 4146 } 4147 else if (image.height > image.width) 4148 { 4149 if ((image.height/6) == image.width) { layout = CUBEMAP_LAYOUT_LINE_VERTICAL; cubemap.width = image.height/6; } 4150 else if ((image.width/3) == (image.height/4)) { layout = CUBEMAP_LAYOUT_CROSS_THREE_BY_FOUR; cubemap.width = image.width/3; } 4151 } 4152 } 4153 else 4154 { 4155 if (layout == CUBEMAP_LAYOUT_LINE_VERTICAL) cubemap.width = image.height/6; 4156 if (layout == CUBEMAP_LAYOUT_LINE_HORIZONTAL) cubemap.width = image.width/6; 4157 if (layout == CUBEMAP_LAYOUT_CROSS_THREE_BY_FOUR) cubemap.width = image.width/3; 4158 if (layout == CUBEMAP_LAYOUT_CROSS_FOUR_BY_THREE) cubemap.width = image.width/4; 4159 } 4160 4161 cubemap.height = cubemap.width; 4162 4163 // Layout provided or already auto-detected 4164 if (layout != CUBEMAP_LAYOUT_AUTO_DETECT) 4165 { 4166 int size = cubemap.width; 4167 4168 Image faces = { 0 }; // Vertical column image 4169 Rectangle faceRecs[6] = { 0 }; // Face source rectangles 4170 4171 for (int i = 0; i < 6; i++) faceRecs[i] = (Rectangle){ 0, 0, (float)size, (float)size }; 4172 4173 if (layout == CUBEMAP_LAYOUT_LINE_VERTICAL) 4174 { 4175 faces = ImageCopy(image); // Image data already follows expected convention 4176 } 4177 /*else if (layout == CUBEMAP_LAYOUT_PANORAMA) 4178 { 4179 // TODO: implement panorama by converting image to square faces... 4180 // Ref: https://github.com/denivip/panorama/blob/master/panorama.cpp 4181 } */ 4182 else 4183 { 4184 if (layout == CUBEMAP_LAYOUT_LINE_HORIZONTAL) for (int i = 0; i < 6; i++) faceRecs[i].x = (float)size*i; 4185 else if (layout == CUBEMAP_LAYOUT_CROSS_THREE_BY_FOUR) 4186 { 4187 faceRecs[0].x = (float)size; faceRecs[0].y = (float)size; 4188 faceRecs[1].x = (float)size; faceRecs[1].y = (float)size*3; 4189 faceRecs[2].x = (float)size; faceRecs[2].y = 0; 4190 faceRecs[3].x = (float)size; faceRecs[3].y = (float)size*2; 4191 faceRecs[4].x = 0; faceRecs[4].y = (float)size; 4192 faceRecs[5].x = (float)size*2; faceRecs[5].y = (float)size; 4193 } 4194 else if (layout == CUBEMAP_LAYOUT_CROSS_FOUR_BY_THREE) 4195 { 4196 faceRecs[0].x = (float)size*2; faceRecs[0].y = (float)size; 4197 faceRecs[1].x = 0; faceRecs[1].y = (float)size; 4198 faceRecs[2].x = (float)size; faceRecs[2].y = 0; 4199 faceRecs[3].x = (float)size; faceRecs[3].y = (float)size*2; 4200 faceRecs[4].x = (float)size; faceRecs[4].y = (float)size; 4201 faceRecs[5].x = (float)size*3; faceRecs[5].y = (float)size; 4202 } 4203 4204 // Convert image data to 6 faces in a vertical column, that's the optimum layout for loading 4205 faces = GenImageColor(size, size*6, MAGENTA); 4206 ImageFormat(&faces, image.format); 4207 4208 Image mipmapped = ImageCopy(image); 4209 ImageMipmaps(&mipmapped); 4210 ImageMipmaps(&faces); 4211 4212 // NOTE: Image formatting does not work with compressed textures 4213 4214 for (int i = 0; i < 6; i++) ImageDraw(&faces, mipmapped, faceRecs[i], (Rectangle){ 0, (float)size*i, (float)size, (float)size }, WHITE); 4215 4216 UnloadImage(mipmapped); 4217 } 4218 4219 // NOTE: Cubemap data is expected to be provided as 6 images in a single data array, 4220 // one after the other (that's a vertical image), following convention: +X, -X, +Y, -Y, +Z, -Z 4221 cubemap.id = rlLoadTextureCubemap(faces.data, size, faces.format, faces.mipmaps); 4222 4223 if (cubemap.id != 0) 4224 { 4225 cubemap.format = faces.format; 4226 cubemap.mipmaps = 1; 4227 } 4228 else TRACELOG(LOG_WARNING, "IMAGE: Failed to load cubemap image"); 4229 4230 UnloadImage(faces); 4231 } 4232 else TRACELOG(LOG_WARNING, "IMAGE: Failed to detect cubemap image layout"); 4233 4234 return cubemap; 4235 } 4236 4237 // Load texture for rendering (framebuffer) 4238 // NOTE: Render texture is loaded by default with RGBA color attachment and depth RenderBuffer 4239 RenderTexture2D LoadRenderTexture(int width, int height) 4240 { 4241 RenderTexture2D target = { 0 }; 4242 4243 target.id = rlLoadFramebuffer(); // Load an empty framebuffer 4244 4245 if (target.id > 0) 4246 { 4247 rlEnableFramebuffer(target.id); 4248 4249 // Create color texture (default to RGBA) 4250 target.texture.id = rlLoadTexture(NULL, width, height, PIXELFORMAT_UNCOMPRESSED_R8G8B8A8, 1); 4251 target.texture.width = width; 4252 target.texture.height = height; 4253 target.texture.format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8; 4254 target.texture.mipmaps = 1; 4255 4256 // Create depth renderbuffer/texture 4257 target.depth.id = rlLoadTextureDepth(width, height, true); 4258 target.depth.width = width; 4259 target.depth.height = height; 4260 target.depth.format = 19; //DEPTH_COMPONENT_24BIT? 4261 target.depth.mipmaps = 1; 4262 4263 // Attach color texture and depth renderbuffer/texture to FBO 4264 rlFramebufferAttach(target.id, target.texture.id, RL_ATTACHMENT_COLOR_CHANNEL0, RL_ATTACHMENT_TEXTURE2D, 0); 4265 rlFramebufferAttach(target.id, target.depth.id, RL_ATTACHMENT_DEPTH, RL_ATTACHMENT_RENDERBUFFER, 0); 4266 4267 // Check if fbo is complete with attachments (valid) 4268 if (rlFramebufferComplete(target.id)) TRACELOG(LOG_INFO, "FBO: [ID %i] Framebuffer object created successfully", target.id); 4269 4270 rlDisableFramebuffer(); 4271 } 4272 else TRACELOG(LOG_WARNING, "FBO: Framebuffer object can not be created"); 4273 4274 return target; 4275 } 4276 4277 // Check if a texture is valid (loaded in GPU) 4278 bool IsTextureValid(Texture2D texture) 4279 { 4280 bool result = false; 4281 4282 // TODO: Validate maximum texture size supported by GPU 4283 4284 if ((texture.id > 0) && // Validate OpenGL id (texture uplaoded to GPU) 4285 (texture.width > 0) && // Validate texture width 4286 (texture.height > 0) && // Validate texture height 4287 (texture.format > 0) && // Validate texture pixel format 4288 (texture.mipmaps > 0)) result = true; // Validate texture mipmaps (at least 1 for basic mipmap level) 4289 4290 return result; 4291 } 4292 4293 // Unload texture from GPU memory (VRAM) 4294 void UnloadTexture(Texture2D texture) 4295 { 4296 if (texture.id > 0) 4297 { 4298 rlUnloadTexture(texture.id); 4299 4300 TRACELOG(LOG_INFO, "TEXTURE: [ID %i] Unloaded texture data from VRAM (GPU)", texture.id); 4301 } 4302 } 4303 4304 // Check if a render texture is valid (loaded in GPU) 4305 bool IsRenderTextureValid(RenderTexture2D target) 4306 { 4307 bool result = false; 4308 4309 if ((target.id > 0) && // Validate OpenGL id (loaded on GPU) 4310 IsTextureValid(target.depth) && // Validate FBO depth texture/renderbuffer attachment 4311 IsTextureValid(target.texture)) result = true; // Validate FBO texture attachment 4312 4313 return result; 4314 } 4315 4316 // Unload render texture from GPU memory (VRAM) 4317 void UnloadRenderTexture(RenderTexture2D target) 4318 { 4319 if (target.id > 0) 4320 { 4321 if (target.texture.id > 0) 4322 { 4323 // Color texture attached to FBO is deleted 4324 rlUnloadTexture(target.texture.id); 4325 } 4326 4327 // NOTE: Depth texture/renderbuffer is automatically 4328 // queried and deleted before deleting framebuffer 4329 rlUnloadFramebuffer(target.id); 4330 } 4331 } 4332 4333 // Update GPU texture with new data 4334 // NOTE: pixels data must match texture.format 4335 void UpdateTexture(Texture2D texture, const void *pixels) 4336 { 4337 rlUpdateTexture(texture.id, 0, 0, texture.width, texture.height, texture.format, pixels); 4338 } 4339 4340 // Update GPU texture rectangle with new data 4341 // NOTE: pixels data must match texture.format 4342 void UpdateTextureRec(Texture2D texture, Rectangle rec, const void *pixels) 4343 { 4344 rlUpdateTexture(texture.id, (int)rec.x, (int)rec.y, (int)rec.width, (int)rec.height, texture.format, pixels); 4345 } 4346 4347 //------------------------------------------------------------------------------------ 4348 // Texture configuration functions 4349 //------------------------------------------------------------------------------------ 4350 // Generate GPU mipmaps for a texture 4351 void GenTextureMipmaps(Texture2D *texture) 4352 { 4353 // NOTE: NPOT textures support check inside function 4354 // On WebGL (OpenGL ES 2.0) NPOT textures support is limited 4355 rlGenTextureMipmaps(texture->id, texture->width, texture->height, texture->format, &texture->mipmaps); 4356 } 4357 4358 // Set texture scaling filter mode 4359 void SetTextureFilter(Texture2D texture, int filter) 4360 { 4361 switch (filter) 4362 { 4363 case TEXTURE_FILTER_POINT: 4364 { 4365 if (texture.mipmaps > 1) 4366 { 4367 // RL_TEXTURE_FILTER_MIP_NEAREST - tex filter: POINT, mipmaps filter: POINT (sharp switching between mipmaps) 4368 rlTextureParameters(texture.id, RL_TEXTURE_MIN_FILTER, RL_TEXTURE_FILTER_MIP_NEAREST); 4369 4370 // RL_TEXTURE_FILTER_NEAREST - tex filter: POINT (no filter), no mipmaps 4371 rlTextureParameters(texture.id, RL_TEXTURE_MAG_FILTER, RL_TEXTURE_FILTER_NEAREST); 4372 } 4373 else 4374 { 4375 // RL_TEXTURE_FILTER_NEAREST - tex filter: POINT (no filter), no mipmaps 4376 rlTextureParameters(texture.id, RL_TEXTURE_MIN_FILTER, RL_TEXTURE_FILTER_NEAREST); 4377 rlTextureParameters(texture.id, RL_TEXTURE_MAG_FILTER, RL_TEXTURE_FILTER_NEAREST); 4378 } 4379 } break; 4380 case TEXTURE_FILTER_BILINEAR: 4381 { 4382 if (texture.mipmaps > 1) 4383 { 4384 // RL_TEXTURE_FILTER_LINEAR_MIP_NEAREST - tex filter: BILINEAR, mipmaps filter: POINT (sharp switching between mipmaps) 4385 // Alternative: RL_TEXTURE_FILTER_NEAREST_MIP_LINEAR - tex filter: POINT, mipmaps filter: BILINEAR (smooth transition between mipmaps) 4386 rlTextureParameters(texture.id, RL_TEXTURE_MIN_FILTER, RL_TEXTURE_FILTER_LINEAR_MIP_NEAREST); 4387 4388 // RL_TEXTURE_FILTER_LINEAR - tex filter: BILINEAR, no mipmaps 4389 rlTextureParameters(texture.id, RL_TEXTURE_MAG_FILTER, RL_TEXTURE_FILTER_LINEAR); 4390 } 4391 else 4392 { 4393 // RL_TEXTURE_FILTER_LINEAR - tex filter: BILINEAR, no mipmaps 4394 rlTextureParameters(texture.id, RL_TEXTURE_MIN_FILTER, RL_TEXTURE_FILTER_LINEAR); 4395 rlTextureParameters(texture.id, RL_TEXTURE_MAG_FILTER, RL_TEXTURE_FILTER_LINEAR); 4396 } 4397 } break; 4398 case TEXTURE_FILTER_TRILINEAR: 4399 { 4400 if (texture.mipmaps > 1) 4401 { 4402 // RL_TEXTURE_FILTER_MIP_LINEAR - tex filter: BILINEAR, mipmaps filter: BILINEAR (smooth transition between mipmaps) 4403 rlTextureParameters(texture.id, RL_TEXTURE_MIN_FILTER, RL_TEXTURE_FILTER_MIP_LINEAR); 4404 4405 // RL_TEXTURE_FILTER_LINEAR - tex filter: BILINEAR, no mipmaps 4406 rlTextureParameters(texture.id, RL_TEXTURE_MAG_FILTER, RL_TEXTURE_FILTER_LINEAR); 4407 } 4408 else 4409 { 4410 TRACELOG(LOG_WARNING, "TEXTURE: [ID %i] No mipmaps available for TRILINEAR texture filtering", texture.id); 4411 4412 // RL_TEXTURE_FILTER_LINEAR - tex filter: BILINEAR, no mipmaps 4413 rlTextureParameters(texture.id, RL_TEXTURE_MIN_FILTER, RL_TEXTURE_FILTER_LINEAR); 4414 rlTextureParameters(texture.id, RL_TEXTURE_MAG_FILTER, RL_TEXTURE_FILTER_LINEAR); 4415 } 4416 } break; 4417 case TEXTURE_FILTER_ANISOTROPIC_4X: rlTextureParameters(texture.id, RL_TEXTURE_FILTER_ANISOTROPIC, 4); break; 4418 case TEXTURE_FILTER_ANISOTROPIC_8X: rlTextureParameters(texture.id, RL_TEXTURE_FILTER_ANISOTROPIC, 8); break; 4419 case TEXTURE_FILTER_ANISOTROPIC_16X: rlTextureParameters(texture.id, RL_TEXTURE_FILTER_ANISOTROPIC, 16); break; 4420 default: break; 4421 } 4422 } 4423 4424 // Set texture wrapping mode 4425 void SetTextureWrap(Texture2D texture, int wrap) 4426 { 4427 switch (wrap) 4428 { 4429 case TEXTURE_WRAP_REPEAT: 4430 { 4431 // NOTE: It only works if NPOT textures are supported, i.e. OpenGL ES 2.0 could not support it 4432 rlTextureParameters(texture.id, RL_TEXTURE_WRAP_S, RL_TEXTURE_WRAP_REPEAT); 4433 rlTextureParameters(texture.id, RL_TEXTURE_WRAP_T, RL_TEXTURE_WRAP_REPEAT); 4434 } break; 4435 case TEXTURE_WRAP_CLAMP: 4436 { 4437 rlTextureParameters(texture.id, RL_TEXTURE_WRAP_S, RL_TEXTURE_WRAP_CLAMP); 4438 rlTextureParameters(texture.id, RL_TEXTURE_WRAP_T, RL_TEXTURE_WRAP_CLAMP); 4439 } break; 4440 case TEXTURE_WRAP_MIRROR_REPEAT: 4441 { 4442 rlTextureParameters(texture.id, RL_TEXTURE_WRAP_S, RL_TEXTURE_WRAP_MIRROR_REPEAT); 4443 rlTextureParameters(texture.id, RL_TEXTURE_WRAP_T, RL_TEXTURE_WRAP_MIRROR_REPEAT); 4444 } break; 4445 case TEXTURE_WRAP_MIRROR_CLAMP: 4446 { 4447 rlTextureParameters(texture.id, RL_TEXTURE_WRAP_S, RL_TEXTURE_WRAP_MIRROR_CLAMP); 4448 rlTextureParameters(texture.id, RL_TEXTURE_WRAP_T, RL_TEXTURE_WRAP_MIRROR_CLAMP); 4449 } break; 4450 default: break; 4451 } 4452 } 4453 4454 //------------------------------------------------------------------------------------ 4455 // Texture drawing functions 4456 //------------------------------------------------------------------------------------ 4457 // Draw a texture 4458 void DrawTexture(Texture2D texture, int posX, int posY, Color tint) 4459 { 4460 DrawTextureEx(texture, (Vector2){ (float)posX, (float)posY }, 0.0f, 1.0f, tint); 4461 } 4462 4463 // Draw a texture with position defined as Vector2 4464 void DrawTextureV(Texture2D texture, Vector2 position, Color tint) 4465 { 4466 DrawTextureEx(texture, position, 0, 1.0f, tint); 4467 } 4468 4469 // Draw a texture with extended parameters 4470 void DrawTextureEx(Texture2D texture, Vector2 position, float rotation, float scale, Color tint) 4471 { 4472 Rectangle source = { 0.0f, 0.0f, (float)texture.width, (float)texture.height }; 4473 Rectangle dest = { position.x, position.y, (float)texture.width*scale, (float)texture.height*scale }; 4474 Vector2 origin = { 0.0f, 0.0f }; 4475 4476 DrawTexturePro(texture, source, dest, origin, rotation, tint); 4477 } 4478 4479 // Draw a part of a texture (defined by a rectangle) 4480 void DrawTextureRec(Texture2D texture, Rectangle source, Vector2 position, Color tint) 4481 { 4482 Rectangle dest = { position.x, position.y, fabsf(source.width), fabsf(source.height) }; 4483 Vector2 origin = { 0.0f, 0.0f }; 4484 4485 DrawTexturePro(texture, source, dest, origin, 0.0f, tint); 4486 } 4487 4488 // Draw a part of a texture (defined by a rectangle) with 'pro' parameters 4489 // NOTE: origin is relative to destination rectangle size 4490 void DrawTexturePro(Texture2D texture, Rectangle source, Rectangle dest, Vector2 origin, float rotation, Color tint) 4491 { 4492 // Check if texture is valid 4493 if (texture.id > 0) 4494 { 4495 float width = (float)texture.width; 4496 float height = (float)texture.height; 4497 4498 bool flipX = false; 4499 4500 if (source.width < 0) { flipX = true; source.width *= -1; } 4501 if (source.height < 0) source.y -= source.height; 4502 4503 if (dest.width < 0) dest.width *= -1; 4504 if (dest.height < 0) dest.height *= -1; 4505 4506 Vector2 topLeft = { 0 }; 4507 Vector2 topRight = { 0 }; 4508 Vector2 bottomLeft = { 0 }; 4509 Vector2 bottomRight = { 0 }; 4510 4511 // Only calculate rotation if needed 4512 if (rotation == 0.0f) 4513 { 4514 float x = dest.x - origin.x; 4515 float y = dest.y - origin.y; 4516 topLeft = (Vector2){ x, y }; 4517 topRight = (Vector2){ x + dest.width, y }; 4518 bottomLeft = (Vector2){ x, y + dest.height }; 4519 bottomRight = (Vector2){ x + dest.width, y + dest.height }; 4520 } 4521 else 4522 { 4523 float sinRotation = sinf(rotation*DEG2RAD); 4524 float cosRotation = cosf(rotation*DEG2RAD); 4525 float x = dest.x; 4526 float y = dest.y; 4527 float dx = -origin.x; 4528 float dy = -origin.y; 4529 4530 topLeft.x = x + dx*cosRotation - dy*sinRotation; 4531 topLeft.y = y + dx*sinRotation + dy*cosRotation; 4532 4533 topRight.x = x + (dx + dest.width)*cosRotation - dy*sinRotation; 4534 topRight.y = y + (dx + dest.width)*sinRotation + dy*cosRotation; 4535 4536 bottomLeft.x = x + dx*cosRotation - (dy + dest.height)*sinRotation; 4537 bottomLeft.y = y + dx*sinRotation + (dy + dest.height)*cosRotation; 4538 4539 bottomRight.x = x + (dx + dest.width)*cosRotation - (dy + dest.height)*sinRotation; 4540 bottomRight.y = y + (dx + dest.width)*sinRotation + (dy + dest.height)*cosRotation; 4541 } 4542 4543 rlSetTexture(texture.id); 4544 rlBegin(RL_QUADS); 4545 4546 rlColor4ub(tint.r, tint.g, tint.b, tint.a); 4547 rlNormal3f(0.0f, 0.0f, 1.0f); // Normal vector pointing towards viewer 4548 4549 // Top-left corner for texture and quad 4550 if (flipX) rlTexCoord2f((source.x + source.width)/width, source.y/height); 4551 else rlTexCoord2f(source.x/width, source.y/height); 4552 rlVertex2f(topLeft.x, topLeft.y); 4553 4554 // Bottom-left corner for texture and quad 4555 if (flipX) rlTexCoord2f((source.x + source.width)/width, (source.y + source.height)/height); 4556 else rlTexCoord2f(source.x/width, (source.y + source.height)/height); 4557 rlVertex2f(bottomLeft.x, bottomLeft.y); 4558 4559 // Bottom-right corner for texture and quad 4560 if (flipX) rlTexCoord2f(source.x/width, (source.y + source.height)/height); 4561 else rlTexCoord2f((source.x + source.width)/width, (source.y + source.height)/height); 4562 rlVertex2f(bottomRight.x, bottomRight.y); 4563 4564 // Top-right corner for texture and quad 4565 if (flipX) rlTexCoord2f(source.x/width, source.y/height); 4566 else rlTexCoord2f((source.x + source.width)/width, source.y/height); 4567 rlVertex2f(topRight.x, topRight.y); 4568 4569 rlEnd(); 4570 rlSetTexture(0); 4571 4572 // NOTE: Vertex position can be transformed using matrices 4573 // but the process is way more costly than just calculating 4574 // the vertex positions manually, like done above 4575 // I leave here the old implementation for educational purposes, 4576 // just in case someone wants to do some performance test 4577 /* 4578 rlSetTexture(texture.id); 4579 rlPushMatrix(); 4580 rlTranslatef(dest.x, dest.y, 0.0f); 4581 if (rotation != 0.0f) rlRotatef(rotation, 0.0f, 0.0f, 1.0f); 4582 rlTranslatef(-origin.x, -origin.y, 0.0f); 4583 4584 rlBegin(RL_QUADS); 4585 rlColor4ub(tint.r, tint.g, tint.b, tint.a); 4586 rlNormal3f(0.0f, 0.0f, 1.0f); // Normal vector pointing towards viewer 4587 4588 // Bottom-left corner for texture and quad 4589 if (flipX) rlTexCoord2f((source.x + source.width)/width, source.y/height); 4590 else rlTexCoord2f(source.x/width, source.y/height); 4591 rlVertex2f(0.0f, 0.0f); 4592 4593 // Bottom-right corner for texture and quad 4594 if (flipX) rlTexCoord2f((source.x + source.width)/width, (source.y + source.height)/height); 4595 else rlTexCoord2f(source.x/width, (source.y + source.height)/height); 4596 rlVertex2f(0.0f, dest.height); 4597 4598 // Top-right corner for texture and quad 4599 if (flipX) rlTexCoord2f(source.x/width, (source.y + source.height)/height); 4600 else rlTexCoord2f((source.x + source.width)/width, (source.y + source.height)/height); 4601 rlVertex2f(dest.width, dest.height); 4602 4603 // Top-left corner for texture and quad 4604 if (flipX) rlTexCoord2f(source.x/width, source.y/height); 4605 else rlTexCoord2f((source.x + source.width)/width, source.y/height); 4606 rlVertex2f(dest.width, 0.0f); 4607 rlEnd(); 4608 rlPopMatrix(); 4609 rlSetTexture(0); 4610 */ 4611 } 4612 } 4613 4614 // Draws a texture (or part of it) that stretches or shrinks nicely using n-patch info 4615 void DrawTextureNPatch(Texture2D texture, NPatchInfo nPatchInfo, Rectangle dest, Vector2 origin, float rotation, Color tint) 4616 { 4617 if (texture.id > 0) 4618 { 4619 float width = (float)texture.width; 4620 float height = (float)texture.height; 4621 4622 float patchWidth = ((int)dest.width <= 0)? 0.0f : dest.width; 4623 float patchHeight = ((int)dest.height <= 0)? 0.0f : dest.height; 4624 4625 if (nPatchInfo.source.width < 0) nPatchInfo.source.x -= nPatchInfo.source.width; 4626 if (nPatchInfo.source.height < 0) nPatchInfo.source.y -= nPatchInfo.source.height; 4627 if (nPatchInfo.layout == NPATCH_THREE_PATCH_HORIZONTAL) patchHeight = nPatchInfo.source.height; 4628 if (nPatchInfo.layout == NPATCH_THREE_PATCH_VERTICAL) patchWidth = nPatchInfo.source.width; 4629 4630 bool drawCenter = true; 4631 bool drawMiddle = true; 4632 float leftBorder = (float)nPatchInfo.left; 4633 float topBorder = (float)nPatchInfo.top; 4634 float rightBorder = (float)nPatchInfo.right; 4635 float bottomBorder = (float)nPatchInfo.bottom; 4636 4637 // Adjust the lateral (left and right) border widths in case patchWidth < texture.width 4638 if (patchWidth <= (leftBorder + rightBorder) && nPatchInfo.layout != NPATCH_THREE_PATCH_VERTICAL) 4639 { 4640 drawCenter = false; 4641 leftBorder = (leftBorder/(leftBorder + rightBorder))*patchWidth; 4642 rightBorder = patchWidth - leftBorder; 4643 } 4644 4645 // Adjust the lateral (top and bottom) border heights in case patchHeight < texture.height 4646 if (patchHeight <= (topBorder + bottomBorder) && nPatchInfo.layout != NPATCH_THREE_PATCH_HORIZONTAL) 4647 { 4648 drawMiddle = false; 4649 topBorder = (topBorder/(topBorder + bottomBorder))*patchHeight; 4650 bottomBorder = patchHeight - topBorder; 4651 } 4652 4653 Vector2 vertA, vertB, vertC, vertD; 4654 vertA.x = 0.0f; // outer left 4655 vertA.y = 0.0f; // outer top 4656 vertB.x = leftBorder; // inner left 4657 vertB.y = topBorder; // inner top 4658 vertC.x = patchWidth - rightBorder; // inner right 4659 vertC.y = patchHeight - bottomBorder; // inner bottom 4660 vertD.x = patchWidth; // outer right 4661 vertD.y = patchHeight; // outer bottom 4662 4663 Vector2 coordA, coordB, coordC, coordD; 4664 coordA.x = nPatchInfo.source.x/width; 4665 coordA.y = nPatchInfo.source.y/height; 4666 coordB.x = (nPatchInfo.source.x + leftBorder)/width; 4667 coordB.y = (nPatchInfo.source.y + topBorder)/height; 4668 coordC.x = (nPatchInfo.source.x + nPatchInfo.source.width - rightBorder)/width; 4669 coordC.y = (nPatchInfo.source.y + nPatchInfo.source.height - bottomBorder)/height; 4670 coordD.x = (nPatchInfo.source.x + nPatchInfo.source.width)/width; 4671 coordD.y = (nPatchInfo.source.y + nPatchInfo.source.height)/height; 4672 4673 rlSetTexture(texture.id); 4674 4675 rlPushMatrix(); 4676 rlTranslatef(dest.x, dest.y, 0.0f); 4677 rlRotatef(rotation, 0.0f, 0.0f, 1.0f); 4678 rlTranslatef(-origin.x, -origin.y, 0.0f); 4679 4680 rlBegin(RL_QUADS); 4681 rlColor4ub(tint.r, tint.g, tint.b, tint.a); 4682 rlNormal3f(0.0f, 0.0f, 1.0f); // Normal vector pointing towards viewer 4683 4684 if (nPatchInfo.layout == NPATCH_NINE_PATCH) 4685 { 4686 // ------------------------------------------------------------ 4687 // TOP-LEFT QUAD 4688 rlTexCoord2f(coordA.x, coordB.y); rlVertex2f(vertA.x, vertB.y); // Bottom-left corner for texture and quad 4689 rlTexCoord2f(coordB.x, coordB.y); rlVertex2f(vertB.x, vertB.y); // Bottom-right corner for texture and quad 4690 rlTexCoord2f(coordB.x, coordA.y); rlVertex2f(vertB.x, vertA.y); // Top-right corner for texture and quad 4691 rlTexCoord2f(coordA.x, coordA.y); rlVertex2f(vertA.x, vertA.y); // Top-left corner for texture and quad 4692 if (drawCenter) 4693 { 4694 // TOP-CENTER QUAD 4695 rlTexCoord2f(coordB.x, coordB.y); rlVertex2f(vertB.x, vertB.y); // Bottom-left corner for texture and quad 4696 rlTexCoord2f(coordC.x, coordB.y); rlVertex2f(vertC.x, vertB.y); // Bottom-right corner for texture and quad 4697 rlTexCoord2f(coordC.x, coordA.y); rlVertex2f(vertC.x, vertA.y); // Top-right corner for texture and quad 4698 rlTexCoord2f(coordB.x, coordA.y); rlVertex2f(vertB.x, vertA.y); // Top-left corner for texture and quad 4699 } 4700 // TOP-RIGHT QUAD 4701 rlTexCoord2f(coordC.x, coordB.y); rlVertex2f(vertC.x, vertB.y); // Bottom-left corner for texture and quad 4702 rlTexCoord2f(coordD.x, coordB.y); rlVertex2f(vertD.x, vertB.y); // Bottom-right corner for texture and quad 4703 rlTexCoord2f(coordD.x, coordA.y); rlVertex2f(vertD.x, vertA.y); // Top-right corner for texture and quad 4704 rlTexCoord2f(coordC.x, coordA.y); rlVertex2f(vertC.x, vertA.y); // Top-left corner for texture and quad 4705 if (drawMiddle) 4706 { 4707 // ------------------------------------------------------------ 4708 // MIDDLE-LEFT QUAD 4709 rlTexCoord2f(coordA.x, coordC.y); rlVertex2f(vertA.x, vertC.y); // Bottom-left corner for texture and quad 4710 rlTexCoord2f(coordB.x, coordC.y); rlVertex2f(vertB.x, vertC.y); // Bottom-right corner for texture and quad 4711 rlTexCoord2f(coordB.x, coordB.y); rlVertex2f(vertB.x, vertB.y); // Top-right corner for texture and quad 4712 rlTexCoord2f(coordA.x, coordB.y); rlVertex2f(vertA.x, vertB.y); // Top-left corner for texture and quad 4713 if (drawCenter) 4714 { 4715 // MIDDLE-CENTER QUAD 4716 rlTexCoord2f(coordB.x, coordC.y); rlVertex2f(vertB.x, vertC.y); // Bottom-left corner for texture and quad 4717 rlTexCoord2f(coordC.x, coordC.y); rlVertex2f(vertC.x, vertC.y); // Bottom-right corner for texture and quad 4718 rlTexCoord2f(coordC.x, coordB.y); rlVertex2f(vertC.x, vertB.y); // Top-right corner for texture and quad 4719 rlTexCoord2f(coordB.x, coordB.y); rlVertex2f(vertB.x, vertB.y); // Top-left corner for texture and quad 4720 } 4721 4722 // MIDDLE-RIGHT QUAD 4723 rlTexCoord2f(coordC.x, coordC.y); rlVertex2f(vertC.x, vertC.y); // Bottom-left corner for texture and quad 4724 rlTexCoord2f(coordD.x, coordC.y); rlVertex2f(vertD.x, vertC.y); // Bottom-right corner for texture and quad 4725 rlTexCoord2f(coordD.x, coordB.y); rlVertex2f(vertD.x, vertB.y); // Top-right corner for texture and quad 4726 rlTexCoord2f(coordC.x, coordB.y); rlVertex2f(vertC.x, vertB.y); // Top-left corner for texture and quad 4727 } 4728 4729 // ------------------------------------------------------------ 4730 // BOTTOM-LEFT QUAD 4731 rlTexCoord2f(coordA.x, coordD.y); rlVertex2f(vertA.x, vertD.y); // Bottom-left corner for texture and quad 4732 rlTexCoord2f(coordB.x, coordD.y); rlVertex2f(vertB.x, vertD.y); // Bottom-right corner for texture and quad 4733 rlTexCoord2f(coordB.x, coordC.y); rlVertex2f(vertB.x, vertC.y); // Top-right corner for texture and quad 4734 rlTexCoord2f(coordA.x, coordC.y); rlVertex2f(vertA.x, vertC.y); // Top-left corner for texture and quad 4735 if (drawCenter) 4736 { 4737 // BOTTOM-CENTER QUAD 4738 rlTexCoord2f(coordB.x, coordD.y); rlVertex2f(vertB.x, vertD.y); // Bottom-left corner for texture and quad 4739 rlTexCoord2f(coordC.x, coordD.y); rlVertex2f(vertC.x, vertD.y); // Bottom-right corner for texture and quad 4740 rlTexCoord2f(coordC.x, coordC.y); rlVertex2f(vertC.x, vertC.y); // Top-right corner for texture and quad 4741 rlTexCoord2f(coordB.x, coordC.y); rlVertex2f(vertB.x, vertC.y); // Top-left corner for texture and quad 4742 } 4743 4744 // BOTTOM-RIGHT QUAD 4745 rlTexCoord2f(coordC.x, coordD.y); rlVertex2f(vertC.x, vertD.y); // Bottom-left corner for texture and quad 4746 rlTexCoord2f(coordD.x, coordD.y); rlVertex2f(vertD.x, vertD.y); // Bottom-right corner for texture and quad 4747 rlTexCoord2f(coordD.x, coordC.y); rlVertex2f(vertD.x, vertC.y); // Top-right corner for texture and quad 4748 rlTexCoord2f(coordC.x, coordC.y); rlVertex2f(vertC.x, vertC.y); // Top-left corner for texture and quad 4749 } 4750 else if (nPatchInfo.layout == NPATCH_THREE_PATCH_VERTICAL) 4751 { 4752 // TOP QUAD 4753 // ----------------------------------------------------------- 4754 // Texture coords Vertices 4755 rlTexCoord2f(coordA.x, coordB.y); rlVertex2f(vertA.x, vertB.y); // Bottom-left corner for texture and quad 4756 rlTexCoord2f(coordD.x, coordB.y); rlVertex2f(vertD.x, vertB.y); // Bottom-right corner for texture and quad 4757 rlTexCoord2f(coordD.x, coordA.y); rlVertex2f(vertD.x, vertA.y); // Top-right corner for texture and quad 4758 rlTexCoord2f(coordA.x, coordA.y); rlVertex2f(vertA.x, vertA.y); // Top-left corner for texture and quad 4759 if (drawCenter) 4760 { 4761 // MIDDLE QUAD 4762 // ----------------------------------------------------------- 4763 // Texture coords Vertices 4764 rlTexCoord2f(coordA.x, coordC.y); rlVertex2f(vertA.x, vertC.y); // Bottom-left corner for texture and quad 4765 rlTexCoord2f(coordD.x, coordC.y); rlVertex2f(vertD.x, vertC.y); // Bottom-right corner for texture and quad 4766 rlTexCoord2f(coordD.x, coordB.y); rlVertex2f(vertD.x, vertB.y); // Top-right corner for texture and quad 4767 rlTexCoord2f(coordA.x, coordB.y); rlVertex2f(vertA.x, vertB.y); // Top-left corner for texture and quad 4768 } 4769 // BOTTOM QUAD 4770 // ----------------------------------------------------------- 4771 // Texture coords Vertices 4772 rlTexCoord2f(coordA.x, coordD.y); rlVertex2f(vertA.x, vertD.y); // Bottom-left corner for texture and quad 4773 rlTexCoord2f(coordD.x, coordD.y); rlVertex2f(vertD.x, vertD.y); // Bottom-right corner for texture and quad 4774 rlTexCoord2f(coordD.x, coordC.y); rlVertex2f(vertD.x, vertC.y); // Top-right corner for texture and quad 4775 rlTexCoord2f(coordA.x, coordC.y); rlVertex2f(vertA.x, vertC.y); // Top-left corner for texture and quad 4776 } 4777 else if (nPatchInfo.layout == NPATCH_THREE_PATCH_HORIZONTAL) 4778 { 4779 // LEFT QUAD 4780 // ----------------------------------------------------------- 4781 // Texture coords Vertices 4782 rlTexCoord2f(coordA.x, coordD.y); rlVertex2f(vertA.x, vertD.y); // Bottom-left corner for texture and quad 4783 rlTexCoord2f(coordB.x, coordD.y); rlVertex2f(vertB.x, vertD.y); // Bottom-right corner for texture and quad 4784 rlTexCoord2f(coordB.x, coordA.y); rlVertex2f(vertB.x, vertA.y); // Top-right corner for texture and quad 4785 rlTexCoord2f(coordA.x, coordA.y); rlVertex2f(vertA.x, vertA.y); // Top-left corner for texture and quad 4786 if (drawCenter) 4787 { 4788 // CENTER QUAD 4789 // ----------------------------------------------------------- 4790 // Texture coords Vertices 4791 rlTexCoord2f(coordB.x, coordD.y); rlVertex2f(vertB.x, vertD.y); // Bottom-left corner for texture and quad 4792 rlTexCoord2f(coordC.x, coordD.y); rlVertex2f(vertC.x, vertD.y); // Bottom-right corner for texture and quad 4793 rlTexCoord2f(coordC.x, coordA.y); rlVertex2f(vertC.x, vertA.y); // Top-right corner for texture and quad 4794 rlTexCoord2f(coordB.x, coordA.y); rlVertex2f(vertB.x, vertA.y); // Top-left corner for texture and quad 4795 } 4796 // RIGHT QUAD 4797 // ----------------------------------------------------------- 4798 // Texture coords Vertices 4799 rlTexCoord2f(coordC.x, coordD.y); rlVertex2f(vertC.x, vertD.y); // Bottom-left corner for texture and quad 4800 rlTexCoord2f(coordD.x, coordD.y); rlVertex2f(vertD.x, vertD.y); // Bottom-right corner for texture and quad 4801 rlTexCoord2f(coordD.x, coordA.y); rlVertex2f(vertD.x, vertA.y); // Top-right corner for texture and quad 4802 rlTexCoord2f(coordC.x, coordA.y); rlVertex2f(vertC.x, vertA.y); // Top-left corner for texture and quad 4803 } 4804 rlEnd(); 4805 rlPopMatrix(); 4806 4807 rlSetTexture(0); 4808 } 4809 } 4810 4811 // Check if two colors are equal 4812 bool ColorIsEqual(Color col1, Color col2) 4813 { 4814 bool result = false; 4815 4816 if ((col1.r == col2.r) && (col1.g == col2.g) && (col1.b == col2.b) && (col1.a == col2.a)) result = true; 4817 4818 return result; 4819 } 4820 4821 // Get color with alpha applied, alpha goes from 0.0f to 1.0f 4822 Color Fade(Color color, float alpha) 4823 { 4824 Color result = color; 4825 4826 if (alpha < 0.0f) alpha = 0.0f; 4827 else if (alpha > 1.0f) alpha = 1.0f; 4828 4829 result.a = (unsigned char)(255.0f*alpha); 4830 4831 return result; 4832 } 4833 4834 // Get hexadecimal value for a Color 4835 int ColorToInt(Color color) 4836 { 4837 int result = 0; 4838 4839 result = (int)(((unsigned int)color.r << 24) | 4840 ((unsigned int)color.g << 16) | 4841 ((unsigned int)color.b << 8) | 4842 (unsigned int)color.a); 4843 4844 return result; 4845 } 4846 4847 // Get color normalized as float [0..1] 4848 Vector4 ColorNormalize(Color color) 4849 { 4850 Vector4 result; 4851 4852 result.x = (float)color.r/255.0f; 4853 result.y = (float)color.g/255.0f; 4854 result.z = (float)color.b/255.0f; 4855 result.w = (float)color.a/255.0f; 4856 4857 return result; 4858 } 4859 4860 // Get color from normalized values [0..1] 4861 Color ColorFromNormalized(Vector4 normalized) 4862 { 4863 Color result; 4864 4865 result.r = (unsigned char)(normalized.x*255.0f); 4866 result.g = (unsigned char)(normalized.y*255.0f); 4867 result.b = (unsigned char)(normalized.z*255.0f); 4868 result.a = (unsigned char)(normalized.w*255.0f); 4869 4870 return result; 4871 } 4872 4873 // Get HSV values for a Color 4874 // NOTE: Hue is returned as degrees [0..360] 4875 Vector3 ColorToHSV(Color color) 4876 { 4877 Vector3 hsv = { 0 }; 4878 Vector3 rgb = { (float)color.r/255.0f, (float)color.g/255.0f, (float)color.b/255.0f }; 4879 float min, max, delta; 4880 4881 min = rgb.x < rgb.y? rgb.x : rgb.y; 4882 min = min < rgb.z? min : rgb.z; 4883 4884 max = rgb.x > rgb.y? rgb.x : rgb.y; 4885 max = max > rgb.z? max : rgb.z; 4886 4887 hsv.z = max; // Value 4888 delta = max - min; 4889 4890 if (delta < 0.00001f) 4891 { 4892 hsv.y = 0.0f; 4893 hsv.x = 0.0f; // Undefined, maybe NAN? 4894 return hsv; 4895 } 4896 4897 if (max > 0.0f) 4898 { 4899 // NOTE: If max is 0, this divide would cause a crash 4900 hsv.y = (delta/max); // Saturation 4901 } 4902 else 4903 { 4904 // NOTE: If max is 0, then r = g = b = 0, s = 0, h is undefined 4905 hsv.y = 0.0f; 4906 hsv.x = NAN; // Undefined 4907 return hsv; 4908 } 4909 4910 // NOTE: Comparing float values could not work properly 4911 if (rgb.x >= max) hsv.x = (rgb.y - rgb.z)/delta; // Between yellow & magenta 4912 else 4913 { 4914 if (rgb.y >= max) hsv.x = 2.0f + (rgb.z - rgb.x)/delta; // Between cyan & yellow 4915 else hsv.x = 4.0f + (rgb.x - rgb.y)/delta; // Between magenta & cyan 4916 } 4917 4918 hsv.x *= 60.0f; // Convert to degrees 4919 4920 if (hsv.x < 0.0f) hsv.x += 360.0f; 4921 4922 return hsv; 4923 } 4924 4925 // Get a Color from HSV values 4926 // Implementation reference: https://en.wikipedia.org/wiki/HSL_and_HSV#Alternative_HSV_conversion 4927 // NOTE: Color->HSV->Color conversion will not yield exactly the same color due to rounding errors 4928 // Hue is provided in degrees: [0..360] 4929 // Saturation/Value are provided normalized: [0.0f..1.0f] 4930 Color ColorFromHSV(float hue, float saturation, float value) 4931 { 4932 Color color = { 0, 0, 0, 255 }; 4933 4934 // Red channel 4935 float k = fmodf((5.0f + hue/60.0f), 6); 4936 float t = 4.0f - k; 4937 k = (t < k)? t : k; 4938 k = (k < 1)? k : 1; 4939 k = (k > 0)? k : 0; 4940 color.r = (unsigned char)((value - value*saturation*k)*255.0f); 4941 4942 // Green channel 4943 k = fmodf((3.0f + hue/60.0f), 6); 4944 t = 4.0f - k; 4945 k = (t < k)? t : k; 4946 k = (k < 1)? k : 1; 4947 k = (k > 0)? k : 0; 4948 color.g = (unsigned char)((value - value*saturation*k)*255.0f); 4949 4950 // Blue channel 4951 k = fmodf((1.0f + hue/60.0f), 6); 4952 t = 4.0f - k; 4953 k = (t < k)? t : k; 4954 k = (k < 1)? k : 1; 4955 k = (k > 0)? k : 0; 4956 color.b = (unsigned char)((value - value*saturation*k)*255.0f); 4957 4958 return color; 4959 } 4960 4961 // Get color multiplied with another color 4962 Color ColorTint(Color color, Color tint) 4963 { 4964 Color result = color; 4965 4966 unsigned char r = (unsigned char)(((int)color.r*(int)tint.r)/255); 4967 unsigned char g = (unsigned char)(((int)color.g*(int)tint.g)/255); 4968 unsigned char b = (unsigned char)(((int)color.b*(int)tint.b)/255); 4969 unsigned char a = (unsigned char)(((int)color.a*(int)tint.a)/255); 4970 4971 result.r = r; 4972 result.g = g; 4973 result.b = b; 4974 result.a = a; 4975 4976 return result; 4977 } 4978 4979 // Get color with brightness correction, brightness factor goes from -1.0f to 1.0f 4980 Color ColorBrightness(Color color, float factor) 4981 { 4982 Color result = color; 4983 4984 if (factor > 1.0f) factor = 1.0f; 4985 else if (factor < -1.0f) factor = -1.0f; 4986 4987 float red = (float)color.r; 4988 float green = (float)color.g; 4989 float blue = (float)color.b; 4990 4991 if (factor < 0.0f) 4992 { 4993 factor = 1.0f + factor; 4994 red *= factor; 4995 green *= factor; 4996 blue *= factor; 4997 } 4998 else 4999 { 5000 red = (255 - red)*factor + red; 5001 green = (255 - green)*factor + green; 5002 blue = (255 - blue)*factor + blue; 5003 } 5004 5005 result.r = (unsigned char)red; 5006 result.g = (unsigned char)green; 5007 result.b = (unsigned char)blue; 5008 5009 return result; 5010 } 5011 5012 // Get color with contrast correction 5013 // NOTE: Contrast values between -1.0f and 1.0f 5014 Color ColorContrast(Color color, float contrast) 5015 { 5016 Color result = color; 5017 5018 if (contrast < -1.0f) contrast = -1.0f; 5019 else if (contrast > 1.0f) contrast = 1.0f; 5020 5021 contrast = (1.0f + contrast); 5022 contrast *= contrast; 5023 5024 float pR = (float)color.r/255.0f; 5025 pR -= 0.5f; 5026 pR *= contrast; 5027 pR += 0.5f; 5028 pR *= 255; 5029 if (pR < 0) pR = 0; 5030 else if (pR > 255) pR = 255; 5031 5032 float pG = (float)color.g/255.0f; 5033 pG -= 0.5f; 5034 pG *= contrast; 5035 pG += 0.5f; 5036 pG *= 255; 5037 if (pG < 0) pG = 0; 5038 else if (pG > 255) pG = 255; 5039 5040 float pB = (float)color.b/255.0f; 5041 pB -= 0.5f; 5042 pB *= contrast; 5043 pB += 0.5f; 5044 pB *= 255; 5045 if (pB < 0) pB = 0; 5046 else if (pB > 255) pB = 255; 5047 5048 result.r = (unsigned char)pR; 5049 result.g = (unsigned char)pG; 5050 result.b = (unsigned char)pB; 5051 5052 return result; 5053 } 5054 5055 // Get color with alpha applied, alpha goes from 0.0f to 1.0f 5056 Color ColorAlpha(Color color, float alpha) 5057 { 5058 Color result = color; 5059 5060 if (alpha < 0.0f) alpha = 0.0f; 5061 else if (alpha > 1.0f) alpha = 1.0f; 5062 5063 result.a = (unsigned char)(255.0f*alpha); 5064 5065 return result; 5066 } 5067 5068 // Get src alpha-blended into dst color with tint 5069 Color ColorAlphaBlend(Color dst, Color src, Color tint) 5070 { 5071 Color out = WHITE; 5072 5073 // Apply color tint to source color 5074 src.r = (unsigned char)(((unsigned int)src.r*((unsigned int)tint.r+1)) >> 8); 5075 src.g = (unsigned char)(((unsigned int)src.g*((unsigned int)tint.g+1)) >> 8); 5076 src.b = (unsigned char)(((unsigned int)src.b*((unsigned int)tint.b+1)) >> 8); 5077 src.a = (unsigned char)(((unsigned int)src.a*((unsigned int)tint.a+1)) >> 8); 5078 5079 //#define COLORALPHABLEND_FLOAT 5080 #define COLORALPHABLEND_INTEGERS 5081 #if defined(COLORALPHABLEND_INTEGERS) 5082 if (src.a == 0) out = dst; 5083 else if (src.a == 255) out = src; 5084 else 5085 { 5086 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 5087 out.a = (unsigned char)(((unsigned int)alpha*256 + (unsigned int)dst.a*(256 - alpha)) >> 8); 5088 5089 if (out.a > 0) 5090 { 5091 out.r = (unsigned char)((((unsigned int)src.r*alpha*256 + (unsigned int)dst.r*(unsigned int)dst.a*(256 - alpha))/out.a) >> 8); 5092 out.g = (unsigned char)((((unsigned int)src.g*alpha*256 + (unsigned int)dst.g*(unsigned int)dst.a*(256 - alpha))/out.a) >> 8); 5093 out.b = (unsigned char)((((unsigned int)src.b*alpha*256 + (unsigned int)dst.b*(unsigned int)dst.a*(256 - alpha))/out.a) >> 8); 5094 } 5095 } 5096 #endif 5097 #if defined(COLORALPHABLEND_FLOAT) 5098 if (src.a == 0) out = dst; 5099 else if (src.a == 255) out = src; 5100 else 5101 { 5102 Vector4 fdst = ColorNormalize(dst); 5103 Vector4 fsrc = ColorNormalize(src); 5104 Vector4 ftint = ColorNormalize(tint); 5105 Vector4 fout = { 0 }; 5106 5107 fout.w = fsrc.w + fdst.w*(1.0f - fsrc.w); 5108 5109 if (fout.w > 0.0f) 5110 { 5111 fout.x = (fsrc.x*fsrc.w + fdst.x*fdst.w*(1 - fsrc.w))/fout.w; 5112 fout.y = (fsrc.y*fsrc.w + fdst.y*fdst.w*(1 - fsrc.w))/fout.w; 5113 fout.z = (fsrc.z*fsrc.w + fdst.z*fdst.w*(1 - fsrc.w))/fout.w; 5114 } 5115 5116 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) }; 5117 } 5118 #endif 5119 5120 return out; 5121 } 5122 5123 // Get color lerp interpolation between two colors, factor [0.0f..1.0f] 5124 Color ColorLerp(Color color1, Color color2, float factor) 5125 { 5126 Color color = { 0 }; 5127 5128 if (factor < 0.0f) factor = 0.0f; 5129 else if (factor > 1.0f) factor = 1.0f; 5130 5131 color.r = (unsigned char)((1.0f - factor)*color1.r + factor*color2.r); 5132 color.g = (unsigned char)((1.0f - factor)*color1.g + factor*color2.g); 5133 color.b = (unsigned char)((1.0f - factor)*color1.b + factor*color2.b); 5134 color.a = (unsigned char)((1.0f - factor)*color1.a + factor*color2.a); 5135 5136 return color; 5137 } 5138 5139 // Get a Color struct from hexadecimal value 5140 Color GetColor(unsigned int hexValue) 5141 { 5142 Color color; 5143 5144 color.r = (unsigned char)(hexValue >> 24) & 0xFF; 5145 color.g = (unsigned char)(hexValue >> 16) & 0xFF; 5146 color.b = (unsigned char)(hexValue >> 8) & 0xFF; 5147 color.a = (unsigned char)hexValue & 0xFF; 5148 5149 return color; 5150 } 5151 5152 // Get color from a pixel from certain format 5153 Color GetPixelColor(void *srcPtr, int format) 5154 { 5155 Color color = { 0 }; 5156 5157 switch (format) 5158 { 5159 case PIXELFORMAT_UNCOMPRESSED_GRAYSCALE: color = (Color){ ((unsigned char *)srcPtr)[0], ((unsigned char *)srcPtr)[0], ((unsigned char *)srcPtr)[0], 255 }; break; 5160 case PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA: color = (Color){ ((unsigned char *)srcPtr)[0], ((unsigned char *)srcPtr)[0], ((unsigned char *)srcPtr)[0], ((unsigned char *)srcPtr)[1] }; break; 5161 case PIXELFORMAT_UNCOMPRESSED_R5G6B5: 5162 { 5163 color.r = (unsigned char)((((unsigned short *)srcPtr)[0] >> 11)*255/31); 5164 color.g = (unsigned char)(((((unsigned short *)srcPtr)[0] >> 5) & 0b0000000000111111)*255/63); 5165 color.b = (unsigned char)((((unsigned short *)srcPtr)[0] & 0b0000000000011111)*255/31); 5166 color.a = 255; 5167 5168 } break; 5169 case PIXELFORMAT_UNCOMPRESSED_R5G5B5A1: 5170 { 5171 color.r = (unsigned char)((((unsigned short *)srcPtr)[0] >> 11)*255/31); 5172 color.g = (unsigned char)(((((unsigned short *)srcPtr)[0] >> 6) & 0b0000000000011111)*255/31); 5173 color.b = (unsigned char)((((unsigned short *)srcPtr)[0] & 0b0000000000011111)*255/31); 5174 color.a = (((unsigned short *)srcPtr)[0] & 0b0000000000000001)? 255 : 0; 5175 5176 } break; 5177 case PIXELFORMAT_UNCOMPRESSED_R4G4B4A4: 5178 { 5179 color.r = (unsigned char)((((unsigned short *)srcPtr)[0] >> 12)*255/15); 5180 color.g = (unsigned char)(((((unsigned short *)srcPtr)[0] >> 8) & 0b0000000000001111)*255/15); 5181 color.b = (unsigned char)(((((unsigned short *)srcPtr)[0] >> 4) & 0b0000000000001111)*255/15); 5182 color.a = (unsigned char)((((unsigned short *)srcPtr)[0] & 0b0000000000001111)*255/15); 5183 5184 } break; 5185 case PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: color = (Color){ ((unsigned char *)srcPtr)[0], ((unsigned char *)srcPtr)[1], ((unsigned char *)srcPtr)[2], ((unsigned char *)srcPtr)[3] }; break; 5186 case PIXELFORMAT_UNCOMPRESSED_R8G8B8: color = (Color){ ((unsigned char *)srcPtr)[0], ((unsigned char *)srcPtr)[1], ((unsigned char *)srcPtr)[2], 255 }; break; 5187 case PIXELFORMAT_UNCOMPRESSED_R32: 5188 { 5189 // NOTE: Pixel normalized float value is converted to [0..255] 5190 color.r = (unsigned char)(((float *)srcPtr)[0]*255.0f); 5191 color.g = (unsigned char)(((float *)srcPtr)[0]*255.0f); 5192 color.b = (unsigned char)(((float *)srcPtr)[0]*255.0f); 5193 color.a = 255; 5194 5195 } break; 5196 case PIXELFORMAT_UNCOMPRESSED_R32G32B32: 5197 { 5198 // NOTE: Pixel normalized float value is converted to [0..255] 5199 color.r = (unsigned char)(((float *)srcPtr)[0]*255.0f); 5200 color.g = (unsigned char)(((float *)srcPtr)[1]*255.0f); 5201 color.b = (unsigned char)(((float *)srcPtr)[2]*255.0f); 5202 color.a = 255; 5203 5204 } break; 5205 case PIXELFORMAT_UNCOMPRESSED_R32G32B32A32: 5206 { 5207 // NOTE: Pixel normalized float value is converted to [0..255] 5208 color.r = (unsigned char)(((float *)srcPtr)[0]*255.0f); 5209 color.g = (unsigned char)(((float *)srcPtr)[1]*255.0f); 5210 color.b = (unsigned char)(((float *)srcPtr)[2]*255.0f); 5211 color.a = (unsigned char)(((float *)srcPtr)[3]*255.0f); 5212 5213 } break; 5214 case PIXELFORMAT_UNCOMPRESSED_R16: 5215 { 5216 // NOTE: Pixel normalized float value is converted to [0..255] 5217 color.r = (unsigned char)(HalfToFloat(((unsigned short *)srcPtr)[0])*255.0f); 5218 color.g = (unsigned char)(HalfToFloat(((unsigned short *)srcPtr)[0])*255.0f); 5219 color.b = (unsigned char)(HalfToFloat(((unsigned short *)srcPtr)[0])*255.0f); 5220 color.a = 255; 5221 5222 } break; 5223 case PIXELFORMAT_UNCOMPRESSED_R16G16B16: 5224 { 5225 // NOTE: Pixel normalized float value is converted to [0..255] 5226 color.r = (unsigned char)(HalfToFloat(((unsigned short *)srcPtr)[0])*255.0f); 5227 color.g = (unsigned char)(HalfToFloat(((unsigned short *)srcPtr)[1])*255.0f); 5228 color.b = (unsigned char)(HalfToFloat(((unsigned short *)srcPtr)[2])*255.0f); 5229 color.a = 255; 5230 5231 } break; 5232 case PIXELFORMAT_UNCOMPRESSED_R16G16B16A16: 5233 { 5234 // NOTE: Pixel normalized float value is converted to [0..255] 5235 color.r = (unsigned char)(HalfToFloat(((unsigned short *)srcPtr)[0])*255.0f); 5236 color.g = (unsigned char)(HalfToFloat(((unsigned short *)srcPtr)[1])*255.0f); 5237 color.b = (unsigned char)(HalfToFloat(((unsigned short *)srcPtr)[2])*255.0f); 5238 color.a = (unsigned char)(HalfToFloat(((unsigned short *)srcPtr)[3])*255.0f); 5239 5240 } break; 5241 default: break; 5242 } 5243 5244 return color; 5245 } 5246 5247 // Set pixel color formatted into destination pointer 5248 void SetPixelColor(void *dstPtr, Color color, int format) 5249 { 5250 switch (format) 5251 { 5252 case PIXELFORMAT_UNCOMPRESSED_GRAYSCALE: 5253 { 5254 // NOTE: Calculate grayscale equivalent color 5255 Vector3 coln = { (float)color.r/255.0f, (float)color.g/255.0f, (float)color.b/255.0f }; 5256 unsigned char gray = (unsigned char)((coln.x*0.299f + coln.y*0.587f + coln.z*0.114f)*255.0f); 5257 5258 ((unsigned char *)dstPtr)[0] = gray; 5259 5260 } break; 5261 case PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA: 5262 { 5263 // NOTE: Calculate grayscale equivalent color 5264 Vector3 coln = { (float)color.r/255.0f, (float)color.g/255.0f, (float)color.b/255.0f }; 5265 unsigned char gray = (unsigned char)((coln.x*0.299f + coln.y*0.587f + coln.z*0.114f)*255.0f); 5266 5267 ((unsigned char *)dstPtr)[0] = gray; 5268 ((unsigned char *)dstPtr)[1] = color.a; 5269 5270 } break; 5271 case PIXELFORMAT_UNCOMPRESSED_R5G6B5: 5272 { 5273 // NOTE: Calculate R5G6B5 equivalent color 5274 Vector3 coln = { (float)color.r/255.0f, (float)color.g/255.0f, (float)color.b/255.0f }; 5275 5276 unsigned char r = (unsigned char)(round(coln.x*31.0f)); 5277 unsigned char g = (unsigned char)(round(coln.y*63.0f)); 5278 unsigned char b = (unsigned char)(round(coln.z*31.0f)); 5279 5280 ((unsigned short *)dstPtr)[0] = (unsigned short)r << 11 | (unsigned short)g << 5 | (unsigned short)b; 5281 5282 } break; 5283 case PIXELFORMAT_UNCOMPRESSED_R5G5B5A1: 5284 { 5285 // NOTE: Calculate R5G5B5A1 equivalent color 5286 Vector4 coln = { (float)color.r/255.0f, (float)color.g/255.0f, (float)color.b/255.0f, (float)color.a/255.0f }; 5287 5288 unsigned char r = (unsigned char)(round(coln.x*31.0f)); 5289 unsigned char g = (unsigned char)(round(coln.y*31.0f)); 5290 unsigned char b = (unsigned char)(round(coln.z*31.0f)); 5291 unsigned char a = (coln.w > ((float)PIXELFORMAT_UNCOMPRESSED_R5G5B5A1_ALPHA_THRESHOLD/255.0f))? 1 : 0; 5292 5293 ((unsigned short *)dstPtr)[0] = (unsigned short)r << 11 | (unsigned short)g << 6 | (unsigned short)b << 1 | (unsigned short)a; 5294 5295 } break; 5296 case PIXELFORMAT_UNCOMPRESSED_R4G4B4A4: 5297 { 5298 // NOTE: Calculate R5G5B5A1 equivalent color 5299 Vector4 coln = { (float)color.r/255.0f, (float)color.g/255.0f, (float)color.b/255.0f, (float)color.a/255.0f }; 5300 5301 unsigned char r = (unsigned char)(round(coln.x*15.0f)); 5302 unsigned char g = (unsigned char)(round(coln.y*15.0f)); 5303 unsigned char b = (unsigned char)(round(coln.z*15.0f)); 5304 unsigned char a = (unsigned char)(round(coln.w*15.0f)); 5305 5306 ((unsigned short *)dstPtr)[0] = (unsigned short)r << 12 | (unsigned short)g << 8 | (unsigned short)b << 4 | (unsigned short)a; 5307 5308 } break; 5309 case PIXELFORMAT_UNCOMPRESSED_R8G8B8: 5310 { 5311 ((unsigned char *)dstPtr)[0] = color.r; 5312 ((unsigned char *)dstPtr)[1] = color.g; 5313 ((unsigned char *)dstPtr)[2] = color.b; 5314 5315 } break; 5316 case PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: 5317 { 5318 ((unsigned char *)dstPtr)[0] = color.r; 5319 ((unsigned char *)dstPtr)[1] = color.g; 5320 ((unsigned char *)dstPtr)[2] = color.b; 5321 ((unsigned char *)dstPtr)[3] = color.a; 5322 5323 } break; 5324 default: break; 5325 } 5326 } 5327 5328 // Get pixel data size in bytes for certain format 5329 // NOTE: Size can be requested for Image or Texture data 5330 int GetPixelDataSize(int width, int height, int format) 5331 { 5332 int dataSize = 0; // Size in bytes 5333 int bpp = 0; // Bits per pixel 5334 5335 switch (format) 5336 { 5337 case PIXELFORMAT_UNCOMPRESSED_GRAYSCALE: bpp = 8; break; 5338 case PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA: 5339 case PIXELFORMAT_UNCOMPRESSED_R5G6B5: 5340 case PIXELFORMAT_UNCOMPRESSED_R5G5B5A1: 5341 case PIXELFORMAT_UNCOMPRESSED_R4G4B4A4: bpp = 16; break; 5342 case PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: bpp = 32; break; 5343 case PIXELFORMAT_UNCOMPRESSED_R8G8B8: bpp = 24; break; 5344 case PIXELFORMAT_UNCOMPRESSED_R32: bpp = 32; break; 5345 case PIXELFORMAT_UNCOMPRESSED_R32G32B32: bpp = 32*3; break; 5346 case PIXELFORMAT_UNCOMPRESSED_R32G32B32A32: bpp = 32*4; break; 5347 case PIXELFORMAT_UNCOMPRESSED_R16: bpp = 16; break; 5348 case PIXELFORMAT_UNCOMPRESSED_R16G16B16: bpp = 16*3; break; 5349 case PIXELFORMAT_UNCOMPRESSED_R16G16B16A16: bpp = 16*4; break; 5350 case PIXELFORMAT_COMPRESSED_DXT1_RGB: 5351 case PIXELFORMAT_COMPRESSED_DXT1_RGBA: 5352 case PIXELFORMAT_COMPRESSED_ETC1_RGB: 5353 case PIXELFORMAT_COMPRESSED_ETC2_RGB: 5354 case PIXELFORMAT_COMPRESSED_PVRT_RGB: 5355 case PIXELFORMAT_COMPRESSED_PVRT_RGBA: bpp = 4; break; 5356 case PIXELFORMAT_COMPRESSED_DXT3_RGBA: 5357 case PIXELFORMAT_COMPRESSED_DXT5_RGBA: 5358 case PIXELFORMAT_COMPRESSED_ETC2_EAC_RGBA: 5359 case PIXELFORMAT_COMPRESSED_ASTC_4x4_RGBA: bpp = 8; break; 5360 case PIXELFORMAT_COMPRESSED_ASTC_8x8_RGBA: bpp = 2; break; 5361 default: break; 5362 } 5363 5364 double bytesPerPixel = (double)bpp/8.0; 5365 dataSize = (int)(bytesPerPixel*width*height); // Total data size in bytes 5366 5367 // Most compressed formats works on 4x4 blocks, 5368 // if texture is smaller, minimum dataSize is 8 or 16 5369 if ((width < 4) && (height < 4)) 5370 { 5371 if ((format >= PIXELFORMAT_COMPRESSED_DXT1_RGB) && (format < PIXELFORMAT_COMPRESSED_DXT3_RGBA)) dataSize = 8; 5372 else if ((format >= PIXELFORMAT_COMPRESSED_DXT3_RGBA) && (format < PIXELFORMAT_COMPRESSED_ASTC_8x8_RGBA)) dataSize = 16; 5373 } 5374 5375 return dataSize; 5376 } 5377 5378 //---------------------------------------------------------------------------------- 5379 // Module specific Functions Definition 5380 //---------------------------------------------------------------------------------- 5381 // Convert half-float (stored as unsigned short) to float 5382 // REF: https://stackoverflow.com/questions/1659440/32-bit-to-16-bit-floating-point-conversion/60047308#60047308 5383 static float HalfToFloat(unsigned short x) 5384 { 5385 float result = 0.0f; 5386 5387 const unsigned int e = (x & 0x7C00) >> 10; // Exponent 5388 const unsigned int m = (x & 0x03FF) << 13; // Mantissa 5389 const float fm = (float)m; 5390 const unsigned int v = (*(unsigned int*)&fm) >> 23; // Evil log2 bit hack to count leading zeros in denormalized format 5391 const unsigned int r = (x & 0x8000) << 16 | (e != 0)*((e + 112) << 23 | m) | ((e == 0)&(m != 0))*((v - 37) << 23 | ((m << (150 - v)) & 0x007FE000)); // sign : normalized : denormalized 5392 5393 result = *(float *)&r; 5394 5395 return result; 5396 } 5397 5398 // Convert float to half-float (stored as unsigned short) 5399 static unsigned short FloatToHalf(float x) 5400 { 5401 unsigned short result = 0; 5402 5403 const unsigned int b = (*(unsigned int*) & x) + 0x00001000; // Round-to-nearest-even: add last bit after truncated mantissa 5404 const unsigned int e = (b & 0x7F800000) >> 23; // Exponent 5405 const unsigned int m = b & 0x007FFFFF; // Mantissa; in line below: 0x007FF000 = 0x00800000-0x00001000 = decimal indicator flag - initial rounding 5406 5407 result = (b & 0x80000000) >> 16 | (e > 112)*((((e - 112) << 10) & 0x7C00) | m >> 13) | ((e < 113) & (e > 101))*((((0x007FF000 + m) >> (125 - e)) + 1) >> 1) | (e > 143)*0x7FFF; // sign : normalized : denormalized : saturate 5408 5409 return result; 5410 } 5411 5412 // Get pixel data from image as Vector4 array (float normalized) 5413 static Vector4 *LoadImageDataNormalized(Image image) 5414 { 5415 Vector4 *pixels = (Vector4 *)RL_MALLOC(image.width*image.height*sizeof(Vector4)); 5416 5417 if (image.format >= PIXELFORMAT_COMPRESSED_DXT1_RGB) TRACELOG(LOG_WARNING, "IMAGE: Pixel data retrieval not supported for compressed image formats"); 5418 else 5419 { 5420 for (int i = 0, k = 0; i < image.width*image.height; i++) 5421 { 5422 switch (image.format) 5423 { 5424 case PIXELFORMAT_UNCOMPRESSED_GRAYSCALE: 5425 { 5426 pixels[i].x = (float)((unsigned char *)image.data)[i]/255.0f; 5427 pixels[i].y = (float)((unsigned char *)image.data)[i]/255.0f; 5428 pixels[i].z = (float)((unsigned char *)image.data)[i]/255.0f; 5429 pixels[i].w = 1.0f; 5430 5431 } break; 5432 case PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA: 5433 { 5434 pixels[i].x = (float)((unsigned char *)image.data)[k]/255.0f; 5435 pixels[i].y = (float)((unsigned char *)image.data)[k]/255.0f; 5436 pixels[i].z = (float)((unsigned char *)image.data)[k]/255.0f; 5437 pixels[i].w = (float)((unsigned char *)image.data)[k + 1]/255.0f; 5438 5439 k += 2; 5440 } break; 5441 case PIXELFORMAT_UNCOMPRESSED_R5G5B5A1: 5442 { 5443 unsigned short pixel = ((unsigned short *)image.data)[i]; 5444 5445 pixels[i].x = (float)((pixel & 0b1111100000000000) >> 11)*(1.0f/31); 5446 pixels[i].y = (float)((pixel & 0b0000011111000000) >> 6)*(1.0f/31); 5447 pixels[i].z = (float)((pixel & 0b0000000000111110) >> 1)*(1.0f/31); 5448 pixels[i].w = ((pixel & 0b0000000000000001) == 0)? 0.0f : 1.0f; 5449 5450 } break; 5451 case PIXELFORMAT_UNCOMPRESSED_R5G6B5: 5452 { 5453 unsigned short pixel = ((unsigned short *)image.data)[i]; 5454 5455 pixels[i].x = (float)((pixel & 0b1111100000000000) >> 11)*(1.0f/31); 5456 pixels[i].y = (float)((pixel & 0b0000011111100000) >> 5)*(1.0f/63); 5457 pixels[i].z = (float)(pixel & 0b0000000000011111)*(1.0f/31); 5458 pixels[i].w = 1.0f; 5459 5460 } break; 5461 case PIXELFORMAT_UNCOMPRESSED_R4G4B4A4: 5462 { 5463 unsigned short pixel = ((unsigned short *)image.data)[i]; 5464 5465 pixels[i].x = (float)((pixel & 0b1111000000000000) >> 12)*(1.0f/15); 5466 pixels[i].y = (float)((pixel & 0b0000111100000000) >> 8)*(1.0f/15); 5467 pixels[i].z = (float)((pixel & 0b0000000011110000) >> 4)*(1.0f/15); 5468 pixels[i].w = (float)(pixel & 0b0000000000001111)*(1.0f/15); 5469 5470 } break; 5471 case PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: 5472 { 5473 pixels[i].x = (float)((unsigned char *)image.data)[k]/255.0f; 5474 pixels[i].y = (float)((unsigned char *)image.data)[k + 1]/255.0f; 5475 pixels[i].z = (float)((unsigned char *)image.data)[k + 2]/255.0f; 5476 pixels[i].w = (float)((unsigned char *)image.data)[k + 3]/255.0f; 5477 5478 k += 4; 5479 } break; 5480 case PIXELFORMAT_UNCOMPRESSED_R8G8B8: 5481 { 5482 pixels[i].x = (float)((unsigned char *)image.data)[k]/255.0f; 5483 pixels[i].y = (float)((unsigned char *)image.data)[k + 1]/255.0f; 5484 pixels[i].z = (float)((unsigned char *)image.data)[k + 2]/255.0f; 5485 pixels[i].w = 1.0f; 5486 5487 k += 3; 5488 } break; 5489 case PIXELFORMAT_UNCOMPRESSED_R32: 5490 { 5491 pixels[i].x = ((float *)image.data)[k]; 5492 pixels[i].y = 0.0f; 5493 pixels[i].z = 0.0f; 5494 pixels[i].w = 1.0f; 5495 5496 } break; 5497 case PIXELFORMAT_UNCOMPRESSED_R32G32B32: 5498 { 5499 pixels[i].x = ((float *)image.data)[k]; 5500 pixels[i].y = ((float *)image.data)[k + 1]; 5501 pixels[i].z = ((float *)image.data)[k + 2]; 5502 pixels[i].w = 1.0f; 5503 5504 k += 3; 5505 } break; 5506 case PIXELFORMAT_UNCOMPRESSED_R32G32B32A32: 5507 { 5508 pixels[i].x = ((float *)image.data)[k]; 5509 pixels[i].y = ((float *)image.data)[k + 1]; 5510 pixels[i].z = ((float *)image.data)[k + 2]; 5511 pixels[i].w = ((float *)image.data)[k + 3]; 5512 5513 k += 4; 5514 } break; 5515 case PIXELFORMAT_UNCOMPRESSED_R16: 5516 { 5517 pixels[i].x = HalfToFloat(((unsigned short *)image.data)[k]); 5518 pixels[i].y = 0.0f; 5519 pixels[i].z = 0.0f; 5520 pixels[i].w = 1.0f; 5521 } break; 5522 case PIXELFORMAT_UNCOMPRESSED_R16G16B16: 5523 { 5524 pixels[i].x = HalfToFloat(((unsigned short *)image.data)[k]); 5525 pixels[i].y = HalfToFloat(((unsigned short *)image.data)[k + 1]); 5526 pixels[i].z = HalfToFloat(((unsigned short *)image.data)[k + 2]); 5527 pixels[i].w = 1.0f; 5528 5529 k += 3; 5530 } break; 5531 case PIXELFORMAT_UNCOMPRESSED_R16G16B16A16: 5532 { 5533 pixels[i].x = HalfToFloat(((unsigned short *)image.data)[k]); 5534 pixels[i].y = HalfToFloat(((unsigned short *)image.data)[k + 1]); 5535 pixels[i].z = HalfToFloat(((unsigned short *)image.data)[k + 2]); 5536 pixels[i].w = HalfToFloat(((unsigned short *)image.data)[k + 3]); 5537 5538 k += 4; 5539 } break; 5540 default: break; 5541 } 5542 } 5543 } 5544 5545 return pixels; 5546 } 5547 5548 #endif // SUPPORT_MODULE_RTEXTURES