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

Generated with cgit - Back to sebastiano.tronto.net