GCC Code Coverage Report


Directory: ./
File: submodules/raylib/src/rtextures.c
Date: 2023-09-29 04:53:15
Exec Total Coverage
Lines: 0 2024 0.0%
Branches: 0 1172 0.0%

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