Logo

index : raylib-jai

---

  • summary
  • about
  • tree
  • log
  • branches
<< path: root/public/raylib-jai.git/html/Raylib/raylib/src/rtextures.c blob: 4208b40bd4ea99e5651aff5e1d2e5892f073f17a [raw] [clear marker]

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