1/**********************************************************************************************
2*
3* raylib.textures - Basic functions to load and draw Textures (2d)
4*
5* CONFIGURATION:
6*
7* #define SUPPORT_FILEFORMAT_BMP
8* #define SUPPORT_FILEFORMAT_PNG
9* #define SUPPORT_FILEFORMAT_TGA
10* #define SUPPORT_FILEFORMAT_JPG
11* #define SUPPORT_FILEFORMAT_GIF
12* #define SUPPORT_FILEFORMAT_PSD
13* #define SUPPORT_FILEFORMAT_PIC
14* #define SUPPORT_FILEFORMAT_HDR
15* #define SUPPORT_FILEFORMAT_DDS
16* #define SUPPORT_FILEFORMAT_PKM
17* #define SUPPORT_FILEFORMAT_KTX
18* #define SUPPORT_FILEFORMAT_PVR
19* #define SUPPORT_FILEFORMAT_ASTC
20* Select desired fileformats to be supported for image data loading. Some of those formats are
21* supported by default, to remove support, just comment unrequired #define in this module
22*
23* #define SUPPORT_IMAGE_EXPORT
24* Support image export in multiple file formats
25*
26* #define SUPPORT_IMAGE_MANIPULATION
27* Support multiple image editing functions to scale, adjust colors, flip, draw on images, crop...
28* If not defined only three image editing functions supported: ImageFormat(), ImageAlphaMask(), ImageToPOT()
29*
30* #define SUPPORT_IMAGE_GENERATION
31* Support procedural image generation functionality (gradient, spot, perlin-noise, cellular)
32*
33* DEPENDENCIES:
34* stb_image - Multiple image formats loading (JPEG, PNG, BMP, TGA, PSD, GIF, PIC)
35* NOTE: stb_image has been slightly modified to support Android platform.
36* stb_image_resize - Multiple image resize algorythms
37*
38*
39* LICENSE: zlib/libpng
40*
41* Copyright (c) 2013-2020 Ramon Santamaria (@raysan5)
42*
43* This software is provided "as-is", without any express or implied warranty. In no event
44* will the authors be held liable for any damages arising from the use of this software.
45*
46* Permission is granted to anyone to use this software for any purpose, including commercial
47* applications, and to alter it and redistribute it freely, subject to the following restrictions:
48*
49* 1. The origin of this software must not be misrepresented; you must not claim that you
50* wrote the original software. If you use this software in a product, an acknowledgment
51* in the product documentation would be appreciated but is not required.
52*
53* 2. Altered source versions must be plainly marked as such, and must not be misrepresented
54* as being the original software.
55*
56* 3. This notice may not be removed or altered from any source distribution.
57*
58**********************************************************************************************/
59
60#include "raylib.h" // Declares module functions
61
62// Check if config flags have been externally provided on compilation line
63#if !defined(EXTERNAL_CONFIG_FLAGS)
64 #include "config.h" // Defines module configuration flags
65#endif
66
67#include <stdlib.h> // Required for: malloc(), free()
68#include <stdio.h> // Required for: FILE, fopen(), fclose(), fread()
69#include <string.h> // Required for: strlen() [Used in ImageTextEx()]
70#include <math.h> // Required for: fabsf()
71
72#include "utils.h" // Required for: fopen() Android mapping
73
74#include "rlgl.h" // raylib OpenGL abstraction layer to OpenGL 1.1, 3.3 or ES2
75 // Required for: rlLoadTexture() rlDeleteTextures(),
76 // rlGenerateMipmaps(), some funcs for DrawTexturePro()
77
78// Support only desired texture formats on stb_image
79#if !defined(SUPPORT_FILEFORMAT_BMP)
80 #define STBI_NO_BMP
81#endif
82#if !defined(SUPPORT_FILEFORMAT_PNG)
83 #define STBI_NO_PNG
84#endif
85#if !defined(SUPPORT_FILEFORMAT_TGA)
86 #define STBI_NO_TGA
87#endif
88#if !defined(SUPPORT_FILEFORMAT_JPG)
89 #define STBI_NO_JPEG // Image format .jpg and .jpeg
90#endif
91#if !defined(SUPPORT_FILEFORMAT_PSD)
92 #define STBI_NO_PSD
93#endif
94#if !defined(SUPPORT_FILEFORMAT_GIF)
95 #define STBI_NO_GIF
96#endif
97#if !defined(SUPPORT_FILEFORMAT_PIC)
98 #define STBI_NO_PIC
99#endif
100#if !defined(SUPPORT_FILEFORMAT_HDR)
101 #define STBI_NO_HDR
102#endif
103
104// Image fileformats not supported by default
105#define STBI_NO_PIC
106#define STBI_NO_PNM // Image format .ppm and .pgm
107
108#if (defined(SUPPORT_FILEFORMAT_BMP) || \
109 defined(SUPPORT_FILEFORMAT_PNG) || \
110 defined(SUPPORT_FILEFORMAT_TGA) || \
111 defined(SUPPORT_FILEFORMAT_JPG) || \
112 defined(SUPPORT_FILEFORMAT_PSD) || \
113 defined(SUPPORT_FILEFORMAT_GIF) || \
114 defined(SUPPORT_FILEFORMAT_PIC) || \
115 defined(SUPPORT_FILEFORMAT_HDR))
116
117 #define STBI_MALLOC RL_MALLOC
118 #define STBI_FREE RL_FREE
119 #define STBI_REALLOC RL_REALLOC
120
121 #define STB_IMAGE_IMPLEMENTATION
122 #include "external/stb_image.h" // Required for: stbi_load_from_file()
123 // NOTE: Used to read image data (multiple formats support)
124#endif
125
126#if defined(SUPPORT_IMAGE_EXPORT)
127 #define STBIW_MALLOC RL_MALLOC
128 #define STBIW_FREE RL_FREE
129 #define STBIW_REALLOC RL_REALLOC
130
131 #define STB_IMAGE_WRITE_IMPLEMENTATION
132 #include "external/stb_image_write.h" // Required for: stbi_write_*()
133#endif
134
135#if defined(SUPPORT_IMAGE_MANIPULATION)
136 #define STBIR_MALLOC(size,c) ((void)(c), RL_MALLOC(size))
137 #define STBIR_FREE(ptr,c) ((void)(c), RL_FREE(ptr))
138
139 #define STB_IMAGE_RESIZE_IMPLEMENTATION
140 #include "external/stb_image_resize.h" // Required for: stbir_resize_uint8() [ImageResize()]
141#endif
142
143#if defined(SUPPORT_IMAGE_GENERATION)
144 #define STB_PERLIN_IMPLEMENTATION
145 #include "external/stb_perlin.h" // Required for: stb_perlin_fbm_noise3
146#endif
147
148//----------------------------------------------------------------------------------
149// Defines and Macros
150//----------------------------------------------------------------------------------
151// Nop...
152
153//----------------------------------------------------------------------------------
154// Types and Structures Definition
155//----------------------------------------------------------------------------------
156// ...
157
158//----------------------------------------------------------------------------------
159// Global Variables Definition
160//----------------------------------------------------------------------------------
161// It's lonely here...
162
163//----------------------------------------------------------------------------------
164// Other Modules Functions Declaration (required by text)
165//----------------------------------------------------------------------------------
166// ...
167
168//----------------------------------------------------------------------------------
169// Module specific Functions Declaration
170//----------------------------------------------------------------------------------
171#if defined(SUPPORT_FILEFORMAT_GIF)
172static Image LoadAnimatedGIF(const char *fileName, int *frames, int **delays); // Load animated GIF file
173#endif
174#if defined(SUPPORT_FILEFORMAT_DDS)
175static Image LoadDDS(const char *fileName); // Load DDS file
176#endif
177#if defined(SUPPORT_FILEFORMAT_PKM)
178static Image LoadPKM(const char *fileName); // Load PKM file
179#endif
180#if defined(SUPPORT_FILEFORMAT_KTX)
181static Image LoadKTX(const char *fileName); // Load KTX file
182static int SaveKTX(Image image, const char *fileName); // Save image data as KTX file
183#endif
184#if defined(SUPPORT_FILEFORMAT_PVR)
185static Image LoadPVR(const char *fileName); // Load PVR file
186#endif
187#if defined(SUPPORT_FILEFORMAT_ASTC)
188static Image LoadASTC(const char *fileName); // Load ASTC file
189#endif
190
191//----------------------------------------------------------------------------------
192// Module Functions Definition
193//----------------------------------------------------------------------------------
194
195// Load image from file into CPU memory (RAM)
196Image LoadImage(const char *fileName)
197{
198 Image image = { 0 };
199
200#if defined(SUPPORT_FILEFORMAT_PNG) || \
201 defined(SUPPORT_FILEFORMAT_BMP) || \
202 defined(SUPPORT_FILEFORMAT_TGA) || \
203 defined(SUPPORT_FILEFORMAT_GIF) || \
204 defined(SUPPORT_FILEFORMAT_PIC) || \
205 defined(SUPPORT_FILEFORMAT_HDR) || \
206 defined(SUPPORT_FILEFORMAT_PSD)
207#define STBI_REQUIRED
208#endif
209
210#if defined(SUPPORT_FILEFORMAT_PNG)
211 if ((IsFileExtension(fileName, ".png"))
212#else
213 if ((false)
214#endif
215#if defined(SUPPORT_FILEFORMAT_BMP)
216 || (IsFileExtension(fileName, ".bmp"))
217#endif
218#if defined(SUPPORT_FILEFORMAT_TGA)
219 || (IsFileExtension(fileName, ".tga"))
220#endif
221#if defined(SUPPORT_FILEFORMAT_JPG)
222 || (IsFileExtension(fileName, ".jpg"))
223#endif
224#if defined(SUPPORT_FILEFORMAT_GIF)
225 || (IsFileExtension(fileName, ".gif"))
226#endif
227#if defined(SUPPORT_FILEFORMAT_PIC)
228 || (IsFileExtension(fileName, ".pic"))
229#endif
230#if defined(SUPPORT_FILEFORMAT_PSD)
231 || (IsFileExtension(fileName, ".psd"))
232#endif
233 )
234 {
235#if defined(STBI_REQUIRED)
236 // NOTE: Using stb_image to load images (Supports multiple image formats)
237
238 unsigned int dataSize = 0;
239 unsigned char *fileData = LoadFileData(fileName, &dataSize);
240
241 if (fileData != NULL)
242 {
243 int comp = 0;
244 image.data = stbi_load_from_memory(fileData, dataSize, &image.width, &image.height, &comp, 0);
245
246 image.mipmaps = 1;
247
248 if (comp == 1) image.format = UNCOMPRESSED_GRAYSCALE;
249 else if (comp == 2) image.format = UNCOMPRESSED_GRAY_ALPHA;
250 else if (comp == 3) image.format = UNCOMPRESSED_R8G8B8;
251 else if (comp == 4) image.format = UNCOMPRESSED_R8G8B8A8;
252
253 RL_FREE(fileData);
254 }
255#endif
256 }
257#if defined(SUPPORT_FILEFORMAT_HDR)
258 else if (IsFileExtension(fileName, ".hdr"))
259 {
260#if defined(STBI_REQUIRED)
261 unsigned int dataSize = 0;
262 unsigned char *fileData = LoadFileData(fileName, &dataSize);
263
264 if (fileData != NULL)
265 {
266 int comp = 0;
267 image.data = stbi_loadf_from_memory(fileData, dataSize, &image.width, &image.height, &comp, 0);
268
269 image.mipmaps = 1;
270
271 if (comp == 1) image.format = UNCOMPRESSED_R32;
272 else if (comp == 3) image.format = UNCOMPRESSED_R32G32B32;
273 else if (comp == 4) image.format = UNCOMPRESSED_R32G32B32A32;
274 else
275 {
276 TRACELOG(LOG_WARNING, "IMAGE: [%s] HDR fileformat not supported", fileName);
277 UnloadImage(image);
278 }
279
280 RL_FREE(fileData);
281 }
282#endif
283 }
284#endif
285#if defined(SUPPORT_FILEFORMAT_DDS)
286 else if (IsFileExtension(fileName, ".dds")) image = LoadDDS(fileName);
287#endif
288#if defined(SUPPORT_FILEFORMAT_PKM)
289 else if (IsFileExtension(fileName, ".pkm")) image = LoadPKM(fileName);
290#endif
291#if defined(SUPPORT_FILEFORMAT_KTX)
292 else if (IsFileExtension(fileName, ".ktx")) image = LoadKTX(fileName);
293#endif
294#if defined(SUPPORT_FILEFORMAT_PVR)
295 else if (IsFileExtension(fileName, ".pvr")) image = LoadPVR(fileName);
296#endif
297#if defined(SUPPORT_FILEFORMAT_ASTC)
298 else if (IsFileExtension(fileName, ".astc")) image = LoadASTC(fileName);
299#endif
300 else TRACELOG(LOG_WARNING, "IMAGE: [%s] Fileformat not supported", fileName);
301
302 if (image.data != NULL) TRACELOG(LOG_INFO, "IMAGE: [%s] Data loaded successfully (%ix%i)", fileName, image.width, image.height);
303 else TRACELOG(LOG_WARNING, "IMAGE: [%s] Failed to load data", fileName);
304
305 return image;
306}
307
308// Load image from Color array data (RGBA - 32bit)
309// NOTE: Creates a copy of pixels data array
310Image LoadImageEx(Color *pixels, int width, int height)
311{
312 Image image = { 0 };
313 image.data = NULL;
314 image.width = width;
315 image.height = height;
316 image.mipmaps = 1;
317 image.format = UNCOMPRESSED_R8G8B8A8;
318
319 int k = 0;
320
321 image.data = (unsigned char *)RL_MALLOC(image.width*image.height*4*sizeof(unsigned char));
322
323 for (int i = 0; i < image.width*image.height*4; i += 4)
324 {
325 ((unsigned char *)image.data)[i] = pixels[k].r;
326 ((unsigned char *)image.data)[i + 1] = pixels[k].g;
327 ((unsigned char *)image.data)[i + 2] = pixels[k].b;
328 ((unsigned char *)image.data)[i + 3] = pixels[k].a;
329 k++;
330 }
331
332 return image;
333}
334
335// Load an image from RAW file data
336Image LoadImageRaw(const char *fileName, int width, int height, int format, int headerSize)
337{
338 Image image = { 0 };
339
340 unsigned int dataSize = 0;
341 unsigned char *fileData = LoadFileData(fileName, &dataSize);
342
343 if (fileData != NULL)
344 {
345 unsigned char *dataPtr = fileData;
346 unsigned int size = GetPixelDataSize(width, height, format);
347
348 if (headerSize > 0) dataPtr += headerSize;
349
350 image.data = RL_MALLOC(size); // Allocate required memory in bytes
351 memcpy(image.data, dataPtr, size); // Copy required data to image
352 image.width = width;
353 image.height = height;
354 image.mipmaps = 1;
355 image.format = format;
356
357 RL_FREE(fileData);
358 }
359
360 return image;
361}
362
363// Load texture from file into GPU memory (VRAM)
364Texture2D LoadTexture(const char *fileName)
365{
366 Texture2D texture = { 0 };
367
368 Image image = LoadImage(fileName);
369
370 if (image.data != NULL)
371 {
372 texture = LoadTextureFromImage(image);
373 UnloadImage(image);
374 }
375
376 return texture;
377}
378
379// Load a texture from image data
380// NOTE: image is not unloaded, it must be done manually
381Texture2D LoadTextureFromImage(Image image)
382{
383 Texture2D texture = { 0 };
384
385 if ((image.data != NULL) && (image.width != 0) && (image.height != 0))
386 {
387 texture.id = rlLoadTexture(image.data, image.width, image.height, image.format, image.mipmaps);
388 }
389 else TRACELOG(LOG_WARNING, "IMAGE: Data is not valid to load texture");
390
391 texture.width = image.width;
392 texture.height = image.height;
393 texture.mipmaps = image.mipmaps;
394 texture.format = image.format;
395
396 return texture;
397}
398
399// Load texture for rendering (framebuffer)
400// NOTE: Render texture is loaded by default with RGBA color attachment and depth RenderBuffer
401RenderTexture2D LoadRenderTexture(int width, int height)
402{
403 RenderTexture2D target = rlLoadRenderTexture(width, height, UNCOMPRESSED_R8G8B8A8, 24, false);
404
405 return target;
406}
407
408// Unload image from CPU memory (RAM)
409void UnloadImage(Image image)
410{
411 RL_FREE(image.data);
412}
413
414// Unload texture from GPU memory (VRAM)
415void UnloadTexture(Texture2D texture)
416{
417 if (texture.id > 0)
418 {
419 rlDeleteTextures(texture.id);
420
421 TRACELOG(LOG_INFO, "TEXTURE: [ID %i] Unloaded texture data from VRAM (GPU)", texture.id);
422 }
423}
424
425// Unload render texture from GPU memory (VRAM)
426void UnloadRenderTexture(RenderTexture2D target)
427{
428 if (target.id > 0) rlDeleteRenderTextures(target);
429}
430
431// Get pixel data from image in the form of Color struct array
432Color *GetImageData(Image image)
433{
434 if ((image.width == 0) || (image.height == 0)) return NULL;
435
436 Color *pixels = (Color *)RL_MALLOC(image.width*image.height*sizeof(Color));
437
438 if (image.format >= COMPRESSED_DXT1_RGB) TRACELOG(LOG_WARNING, "IMAGE: Pixel data retrieval not supported for compressed image formats");
439 else
440 {
441 if ((image.format == UNCOMPRESSED_R32) ||
442 (image.format == UNCOMPRESSED_R32G32B32) ||
443 (image.format == UNCOMPRESSED_R32G32B32A32)) TRACELOG(LOG_WARNING, "IMAGE: Pixel format converted from 32bit to 8bit per channel");
444
445 for (int i = 0, k = 0; i < image.width*image.height; i++)
446 {
447 switch (image.format)
448 {
449 case UNCOMPRESSED_GRAYSCALE:
450 {
451 pixels[i].r = ((unsigned char *)image.data)[i];
452 pixels[i].g = ((unsigned char *)image.data)[i];
453 pixels[i].b = ((unsigned char *)image.data)[i];
454 pixels[i].a = 255;
455
456 } break;
457 case UNCOMPRESSED_GRAY_ALPHA:
458 {
459 pixels[i].r = ((unsigned char *)image.data)[k];
460 pixels[i].g = ((unsigned char *)image.data)[k];
461 pixels[i].b = ((unsigned char *)image.data)[k];
462 pixels[i].a = ((unsigned char *)image.data)[k + 1];
463
464 k += 2;
465 } break;
466 case UNCOMPRESSED_R5G5B5A1:
467 {
468 unsigned short pixel = ((unsigned short *)image.data)[i];
469
470 pixels[i].r = (unsigned char)((float)((pixel & 0b1111100000000000) >> 11)*(255/31));
471 pixels[i].g = (unsigned char)((float)((pixel & 0b0000011111000000) >> 6)*(255/31));
472 pixels[i].b = (unsigned char)((float)((pixel & 0b0000000000111110) >> 1)*(255/31));
473 pixels[i].a = (unsigned char)((pixel & 0b0000000000000001)*255);
474
475 } break;
476 case UNCOMPRESSED_R5G6B5:
477 {
478 unsigned short pixel = ((unsigned short *)image.data)[i];
479
480 pixels[i].r = (unsigned char)((float)((pixel & 0b1111100000000000) >> 11)*(255/31));
481 pixels[i].g = (unsigned char)((float)((pixel & 0b0000011111100000) >> 5)*(255/63));
482 pixels[i].b = (unsigned char)((float)(pixel & 0b0000000000011111)*(255/31));
483 pixels[i].a = 255;
484
485 } break;
486 case UNCOMPRESSED_R4G4B4A4:
487 {
488 unsigned short pixel = ((unsigned short *)image.data)[i];
489
490 pixels[i].r = (unsigned char)((float)((pixel & 0b1111000000000000) >> 12)*(255/15));
491 pixels[i].g = (unsigned char)((float)((pixel & 0b0000111100000000) >> 8)*(255/15));
492 pixels[i].b = (unsigned char)((float)((pixel & 0b0000000011110000) >> 4)*(255/15));
493 pixels[i].a = (unsigned char)((float)(pixel & 0b0000000000001111)*(255/15));
494
495 } break;
496 case UNCOMPRESSED_R8G8B8A8:
497 {
498 pixels[i].r = ((unsigned char *)image.data)[k];
499 pixels[i].g = ((unsigned char *)image.data)[k + 1];
500 pixels[i].b = ((unsigned char *)image.data)[k + 2];
501 pixels[i].a = ((unsigned char *)image.data)[k + 3];
502
503 k += 4;
504 } break;
505 case UNCOMPRESSED_R8G8B8:
506 {
507 pixels[i].r = (unsigned char)((unsigned char *)image.data)[k];
508 pixels[i].g = (unsigned char)((unsigned char *)image.data)[k + 1];
509 pixels[i].b = (unsigned char)((unsigned char *)image.data)[k + 2];
510 pixels[i].a = 255;
511
512 k += 3;
513 } break;
514 case UNCOMPRESSED_R32:
515 {
516 pixels[i].r = (unsigned char)(((float *)image.data)[k]*255.0f);
517 pixels[i].g = 0;
518 pixels[i].b = 0;
519 pixels[i].a = 255;
520
521 } break;
522 case UNCOMPRESSED_R32G32B32:
523 {
524 pixels[i].r = (unsigned char)(((float *)image.data)[k]*255.0f);
525 pixels[i].g = (unsigned char)(((float *)image.data)[k + 1]*255.0f);
526 pixels[i].b = (unsigned char)(((float *)image.data)[k + 2]*255.0f);
527 pixels[i].a = 255;
528
529 k += 3;
530 } break;
531 case UNCOMPRESSED_R32G32B32A32:
532 {
533 pixels[i].r = (unsigned char)(((float *)image.data)[k]*255.0f);
534 pixels[i].g = (unsigned char)(((float *)image.data)[k]*255.0f);
535 pixels[i].b = (unsigned char)(((float *)image.data)[k]*255.0f);
536 pixels[i].a = (unsigned char)(((float *)image.data)[k]*255.0f);
537
538 k += 4;
539 } break;
540 default: break;
541 }
542 }
543 }
544
545 return pixels;
546}
547
548// Get pixel data from image as Vector4 array (float normalized)
549Vector4 *GetImageDataNormalized(Image image)
550{
551 Vector4 *pixels = (Vector4 *)RL_MALLOC(image.width*image.height*sizeof(Vector4));
552
553 if (image.format >= COMPRESSED_DXT1_RGB) TRACELOG(LOG_WARNING, "IMAGE: Pixel data retrieval not supported for compressed image formats");
554 else
555 {
556 for (int i = 0, k = 0; i < image.width*image.height; i++)
557 {
558 switch (image.format)
559 {
560 case UNCOMPRESSED_GRAYSCALE:
561 {
562 pixels[i].x = (float)((unsigned char *)image.data)[i]/255.0f;
563 pixels[i].y = (float)((unsigned char *)image.data)[i]/255.0f;
564 pixels[i].z = (float)((unsigned char *)image.data)[i]/255.0f;
565 pixels[i].w = 1.0f;
566
567 } break;
568 case UNCOMPRESSED_GRAY_ALPHA:
569 {
570 pixels[i].x = (float)((unsigned char *)image.data)[k]/255.0f;
571 pixels[i].y = (float)((unsigned char *)image.data)[k]/255.0f;
572 pixels[i].z = (float)((unsigned char *)image.data)[k]/255.0f;
573 pixels[i].w = (float)((unsigned char *)image.data)[k + 1]/255.0f;
574
575 k += 2;
576 } break;
577 case UNCOMPRESSED_R5G5B5A1:
578 {
579 unsigned short pixel = ((unsigned short *)image.data)[i];
580
581 pixels[i].x = (float)((pixel & 0b1111100000000000) >> 11)*(1.0f/31);
582 pixels[i].y = (float)((pixel & 0b0000011111000000) >> 6)*(1.0f/31);
583 pixels[i].z = (float)((pixel & 0b0000000000111110) >> 1)*(1.0f/31);
584 pixels[i].w = ((pixel & 0b0000000000000001) == 0)? 0.0f : 1.0f;
585
586 } break;
587 case UNCOMPRESSED_R5G6B5:
588 {
589 unsigned short pixel = ((unsigned short *)image.data)[i];
590
591 pixels[i].x = (float)((pixel & 0b1111100000000000) >> 11)*(1.0f/31);
592 pixels[i].y = (float)((pixel & 0b0000011111100000) >> 5)*(1.0f/63);
593 pixels[i].z = (float)(pixel & 0b0000000000011111)*(1.0f/31);
594 pixels[i].w = 1.0f;
595
596 } break;
597 case UNCOMPRESSED_R4G4B4A4:
598 {
599 unsigned short pixel = ((unsigned short *)image.data)[i];
600
601 pixels[i].x = (float)((pixel & 0b1111000000000000) >> 12)*(1.0f/15);
602 pixels[i].y = (float)((pixel & 0b0000111100000000) >> 8)*(1.0f/15);
603 pixels[i].z = (float)((pixel & 0b0000000011110000) >> 4)*(1.0f/15);
604 pixels[i].w = (float)(pixel & 0b0000000000001111)*(1.0f/15);
605
606 } break;
607 case UNCOMPRESSED_R8G8B8A8:
608 {
609 pixels[i].x = (float)((unsigned char *)image.data)[k]/255.0f;
610 pixels[i].y = (float)((unsigned char *)image.data)[k + 1]/255.0f;
611 pixels[i].z = (float)((unsigned char *)image.data)[k + 2]/255.0f;
612 pixels[i].w = (float)((unsigned char *)image.data)[k + 3]/255.0f;
613
614 k += 4;
615 } break;
616 case UNCOMPRESSED_R8G8B8:
617 {
618 pixels[i].x = (float)((unsigned char *)image.data)[k]/255.0f;
619 pixels[i].y = (float)((unsigned char *)image.data)[k + 1]/255.0f;
620 pixels[i].z = (float)((unsigned char *)image.data)[k + 2]/255.0f;
621 pixels[i].w = 1.0f;
622
623 k += 3;
624 } break;
625 case UNCOMPRESSED_R32:
626 {
627 pixels[i].x = ((float *)image.data)[k];
628 pixels[i].y = 0.0f;
629 pixels[i].z = 0.0f;
630 pixels[i].w = 1.0f;
631
632 } break;
633 case UNCOMPRESSED_R32G32B32:
634 {
635 pixels[i].x = ((float *)image.data)[k];
636 pixels[i].y = ((float *)image.data)[k + 1];
637 pixels[i].z = ((float *)image.data)[k + 2];
638 pixels[i].w = 1.0f;
639
640 k += 3;
641 } break;
642 case UNCOMPRESSED_R32G32B32A32:
643 {
644 pixels[i].x = ((float *)image.data)[k];
645 pixels[i].y = ((float *)image.data)[k + 1];
646 pixels[i].z = ((float *)image.data)[k + 2];
647 pixels[i].w = ((float *)image.data)[k + 3];
648
649 k += 4;
650 }
651 default: break;
652 }
653 }
654 }
655
656 return pixels;
657}
658
659// Get image alpha border rectangle
660Rectangle GetImageAlphaBorder(Image image, float threshold)
661{
662 Rectangle crop = { 0 };
663
664 Color *pixels = GetImageData(image);
665
666 if (pixels != NULL)
667 {
668 int xMin = 65536; // Define a big enough number
669 int xMax = 0;
670 int yMin = 65536;
671 int yMax = 0;
672
673 for (int y = 0; y < image.height; y++)
674 {
675 for (int x = 0; x < image.width; x++)
676 {
677 if (pixels[y*image.width + x].a > (unsigned char)(threshold*255.0f))
678 {
679 if (x < xMin) xMin = x;
680 if (x > xMax) xMax = x;
681 if (y < yMin) yMin = y;
682 if (y > yMax) yMax = y;
683 }
684 }
685 }
686
687 // Check for empty blank image
688 if ((xMin != 65536) && (xMax != 65536))
689 {
690 crop = (Rectangle){ xMin, yMin, (xMax + 1) - xMin, (yMax + 1) - yMin };
691 }
692
693 RL_FREE(pixels);
694 }
695
696 return crop;
697}
698
699// Get pixel data size in bytes (image or texture)
700// NOTE: Size depends on pixel format
701int GetPixelDataSize(int width, int height, int format)
702{
703 int dataSize = 0; // Size in bytes
704 int bpp = 0; // Bits per pixel
705
706 switch (format)
707 {
708 case UNCOMPRESSED_GRAYSCALE: bpp = 8; break;
709 case UNCOMPRESSED_GRAY_ALPHA:
710 case UNCOMPRESSED_R5G6B5:
711 case UNCOMPRESSED_R5G5B5A1:
712 case UNCOMPRESSED_R4G4B4A4: bpp = 16; break;
713 case UNCOMPRESSED_R8G8B8A8: bpp = 32; break;
714 case UNCOMPRESSED_R8G8B8: bpp = 24; break;
715 case UNCOMPRESSED_R32: bpp = 32; break;
716 case UNCOMPRESSED_R32G32B32: bpp = 32*3; break;
717 case UNCOMPRESSED_R32G32B32A32: bpp = 32*4; break;
718 case COMPRESSED_DXT1_RGB:
719 case COMPRESSED_DXT1_RGBA:
720 case COMPRESSED_ETC1_RGB:
721 case COMPRESSED_ETC2_RGB:
722 case COMPRESSED_PVRT_RGB:
723 case COMPRESSED_PVRT_RGBA: bpp = 4; break;
724 case COMPRESSED_DXT3_RGBA:
725 case COMPRESSED_DXT5_RGBA:
726 case COMPRESSED_ETC2_EAC_RGBA:
727 case COMPRESSED_ASTC_4x4_RGBA: bpp = 8; break;
728 case COMPRESSED_ASTC_8x8_RGBA: bpp = 2; break;
729 default: break;
730 }
731
732 dataSize = width*height*bpp/8; // Total data size in bytes
733
734 return dataSize;
735}
736
737// Get pixel data from GPU texture and return an Image
738// NOTE: Compressed texture formats not supported
739Image GetTextureData(Texture2D texture)
740{
741 Image image = { 0 };
742
743 if (texture.format < 8)
744 {
745 image.data = rlReadTexturePixels(texture);
746
747 if (image.data != NULL)
748 {
749 image.width = texture.width;
750 image.height = texture.height;
751 image.format = texture.format;
752 image.mipmaps = 1;
753
754#if defined(GRAPHICS_API_OPENGL_ES2)
755 // NOTE: Data retrieved on OpenGL ES 2.0 should be RGBA,
756 // coming from FBO color buffer attachment, but it seems
757 // original texture format is retrieved on RPI...
758 image.format = UNCOMPRESSED_R8G8B8A8;
759#endif
760 TRACELOG(LOG_INFO, "TEXTURE: [ID %i] Pixel data retrieved successfully", texture.id);
761 }
762 else TRACELOG(LOG_WARNING, "TEXTURE: [ID %i] Failed to retrieve pixel data", texture.id);
763 }
764 else TRACELOG(LOG_WARNING, "TEXTURE: [ID %i] Failed to retrieve compressed pixel data", texture.id);
765
766 return image;
767}
768
769// Get pixel data from GPU frontbuffer and return an Image (screenshot)
770Image GetScreenData(void)
771{
772 Image image = { 0 };
773
774 image.width = GetScreenWidth();
775 image.height = GetScreenHeight();
776 image.mipmaps = 1;
777 image.format = UNCOMPRESSED_R8G8B8A8;
778 image.data = rlReadScreenPixels(image.width, image.height);
779
780 return image;
781}
782
783// Update GPU texture with new data
784// NOTE: pixels data must match texture.format
785void UpdateTexture(Texture2D texture, const void *pixels)
786{
787 rlUpdateTexture(texture.id, texture.width, texture.height, texture.format, pixels);
788}
789
790// Export image data to file
791// NOTE: File format depends on fileName extension
792void ExportImage(Image image, const char *fileName)
793{
794 int success = 0;
795
796#if defined(SUPPORT_IMAGE_EXPORT)
797 // NOTE: Getting Color array as RGBA unsigned char values
798 unsigned char *imgData = (unsigned char *)GetImageData(image);
799
800#if defined(SUPPORT_FILEFORMAT_PNG)
801 if (IsFileExtension(fileName, ".png")) success = stbi_write_png(fileName, image.width, image.height, 4, imgData, image.width*4);
802#else
803 if (false) {}
804#endif
805#if defined(SUPPORT_FILEFORMAT_BMP)
806 else if (IsFileExtension(fileName, ".bmp")) success = stbi_write_bmp(fileName, image.width, image.height, 4, imgData);
807#endif
808#if defined(SUPPORT_FILEFORMAT_TGA)
809 else if (IsFileExtension(fileName, ".tga")) success = stbi_write_tga(fileName, image.width, image.height, 4, imgData);
810#endif
811#if defined(SUPPORT_FILEFORMAT_JPG)
812 else if (IsFileExtension(fileName, ".jpg")) success = stbi_write_jpg(fileName, image.width, image.height, 4, imgData, 80); // JPG quality: between 1 and 100
813#endif
814#if defined(SUPPORT_FILEFORMAT_KTX)
815 else if (IsFileExtension(fileName, ".ktx")) success = SaveKTX(image, fileName);
816#endif
817 else if (IsFileExtension(fileName, ".raw"))
818 {
819 // Export raw pixel data (without header)
820 // NOTE: It's up to the user to track image parameters
821 SaveFileData(fileName, image.data, GetPixelDataSize(image.width, image.height, image.format));
822 success = true;
823 }
824
825 RL_FREE(imgData);
826#endif
827
828 if (success != 0) TRACELOG(LOG_INFO, "FILEIO: [%s] Image exported successfully", fileName);
829 else TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to export image", fileName);
830}
831
832// Export image as code file (.h) defining an array of bytes
833void ExportImageAsCode(Image image, const char *fileName)
834{
835 #define BYTES_TEXT_PER_LINE 20
836
837 FILE *txtFile = fopen(fileName, "wt");
838
839 if (txtFile != NULL)
840 {
841 char varFileName[256] = { 0 };
842 int dataSize = GetPixelDataSize(image.width, image.height, image.format);
843
844 fprintf(txtFile, "////////////////////////////////////////////////////////////////////////////////////////\n");
845 fprintf(txtFile, "// //\n");
846 fprintf(txtFile, "// ImageAsCode exporter v1.0 - Image pixel data exported as an array of bytes //\n");
847 fprintf(txtFile, "// //\n");
848 fprintf(txtFile, "// more info and bugs-report: github.com/raysan5/raylib //\n");
849 fprintf(txtFile, "// feedback and support: ray[at]raylib.com //\n");
850 fprintf(txtFile, "// //\n");
851 fprintf(txtFile, "// Copyright (c) 2020 Ramon Santamaria (@raysan5) //\n");
852 fprintf(txtFile, "// //\n");
853 fprintf(txtFile, "////////////////////////////////////////////////////////////////////////////////////////\n\n");
854
855 // Get file name from path and convert variable name to uppercase
856 strcpy(varFileName, GetFileNameWithoutExt(fileName));
857 for (int i = 0; varFileName[i] != '\0'; i++) if ((varFileName[i] >= 'a') && (varFileName[i] <= 'z')) { varFileName[i] = varFileName[i] - 32; }
858
859 // Add image information
860 fprintf(txtFile, "// Image data information\n");
861 fprintf(txtFile, "#define %s_WIDTH %i\n", varFileName, image.width);
862 fprintf(txtFile, "#define %s_HEIGHT %i\n", varFileName, image.height);
863 fprintf(txtFile, "#define %s_FORMAT %i // raylib internal pixel format\n\n", varFileName, image.format);
864
865 fprintf(txtFile, "static unsigned char %s_DATA[%i] = { ", varFileName, dataSize);
866 for (int i = 0; i < dataSize - 1; i++) fprintf(txtFile, ((i%BYTES_TEXT_PER_LINE == 0)? "0x%x,\n" : "0x%x, "), ((unsigned char *)image.data)[i]);
867 fprintf(txtFile, "0x%x };\n", ((unsigned char *)image.data)[dataSize - 1]);
868
869 fclose(txtFile);
870 }
871}
872
873// Copy an image to a new image
874Image ImageCopy(Image image)
875{
876 Image newImage = { 0 };
877
878 int width = image.width;
879 int height = image.height;
880 int size = 0;
881
882 for (int i = 0; i < image.mipmaps; i++)
883 {
884 size += GetPixelDataSize(width, height, image.format);
885
886 width /= 2;
887 height /= 2;
888
889 // Security check for NPOT textures
890 if (width < 1) width = 1;
891 if (height < 1) height = 1;
892 }
893
894 newImage.data = RL_MALLOC(size);
895
896 if (newImage.data != NULL)
897 {
898 // NOTE: Size must be provided in bytes
899 memcpy(newImage.data, image.data, size);
900
901 newImage.width = image.width;
902 newImage.height = image.height;
903 newImage.mipmaps = image.mipmaps;
904 newImage.format = image.format;
905 }
906
907 return newImage;
908}
909
910// Create an image from another image piece
911Image ImageFromImage(Image image, Rectangle rec)
912{
913 Image result = ImageCopy(image);
914
915#if defined(SUPPORT_IMAGE_MANIPULATION)
916 ImageCrop(&result, rec);
917#endif
918
919 return result;
920}
921
922// Convert image to POT (power-of-two)
923// NOTE: It could be useful on OpenGL ES 2.0 (RPI, HTML5)
924void ImageToPOT(Image *image, Color fillColor)
925{
926 // Security check to avoid program crash
927 if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return;
928
929 // Calculate next power-of-two values
930 // NOTE: Just add the required amount of pixels at the right and bottom sides of image...
931 int potWidth = (int)powf(2, ceilf(logf((float)image->width)/logf(2)));
932 int potHeight = (int)powf(2, ceilf(logf((float)image->height)/logf(2)));
933
934 // Check if POT texture generation is required (if texture is not already POT)
935 if ((potWidth != image->width) || (potHeight != image->height))
936 {
937 Color *pixels = GetImageData(*image); // Get pixels data
938 Color *pixelsPOT = NULL;
939
940 // Generate POT array from NPOT data
941 pixelsPOT = (Color *)RL_MALLOC(potWidth*potHeight*sizeof(Color));
942
943 for (int j = 0; j < potHeight; j++)
944 {
945 for (int i = 0; i < potWidth; i++)
946 {
947 if ((j < image->height) && (i < image->width)) pixelsPOT[j*potWidth + i] = pixels[j*image->width + i];
948 else pixelsPOT[j*potWidth + i] = fillColor;
949 }
950 }
951
952 RL_FREE(pixels); // Free pixels data
953 RL_FREE(image->data); // Free old image data
954
955 int format = image->format; // Store image data format to reconvert later
956
957 // NOTE: Image size changes, new width and height
958 *image = LoadImageEx(pixelsPOT, potWidth, potHeight);
959
960 RL_FREE(pixelsPOT); // Free POT pixels data
961
962 ImageFormat(image, format); // Reconvert image to previous format
963
964 // TODO: Verification required for log
965 TRACELOG(LOG_WARNING, "IMAGE: Converted to POT: (%ix%i) -> (%ix%i)", image->width, image->height, potWidth, potHeight);
966 }
967}
968
969// Convert image data to desired format
970void ImageFormat(Image *image, int newFormat)
971{
972 // Security check to avoid program crash
973 if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return;
974
975 if ((newFormat != 0) && (image->format != newFormat))
976 {
977 if ((image->format < COMPRESSED_DXT1_RGB) && (newFormat < COMPRESSED_DXT1_RGB))
978 {
979 Vector4 *pixels = GetImageDataNormalized(*image); // Supports 8 to 32 bit per channel
980
981 RL_FREE(image->data); // WARNING! We loose mipmaps data --> Regenerated at the end...
982 image->data = NULL;
983 image->format = newFormat;
984
985 int k = 0;
986
987 switch (image->format)
988 {
989 case UNCOMPRESSED_GRAYSCALE:
990 {
991 image->data = (unsigned char *)RL_MALLOC(image->width*image->height*sizeof(unsigned char));
992
993 for (int i = 0; i < image->width*image->height; i++)
994 {
995 ((unsigned char *)image->data)[i] = (unsigned char)((pixels[i].x*0.299f + pixels[i].y*0.587f + pixels[i].z*0.114f)*255.0f);
996 }
997
998 } break;
999 case UNCOMPRESSED_GRAY_ALPHA:
1000 {
1001 image->data = (unsigned char *)RL_MALLOC(image->width*image->height*2*sizeof(unsigned char));
1002
1003 for (int i = 0; i < image->width*image->height*2; i += 2, k++)
1004 {
1005 ((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);
1006 ((unsigned char *)image->data)[i + 1] = (unsigned char)(pixels[k].w*255.0f);
1007 }
1008
1009 } break;
1010 case UNCOMPRESSED_R5G6B5:
1011 {
1012 image->data = (unsigned short *)RL_MALLOC(image->width*image->height*sizeof(unsigned short));
1013
1014 unsigned char r = 0;
1015 unsigned char g = 0;
1016 unsigned char b = 0;
1017
1018 for (int i = 0; i < image->width*image->height; i++)
1019 {
1020 r = (unsigned char)(round(pixels[i].x*31.0f));
1021 g = (unsigned char)(round(pixels[i].y*63.0f));
1022 b = (unsigned char)(round(pixels[i].z*31.0f));
1023
1024 ((unsigned short *)image->data)[i] = (unsigned short)r << 11 | (unsigned short)g << 5 | (unsigned short)b;
1025 }
1026
1027 } break;
1028 case UNCOMPRESSED_R8G8B8:
1029 {
1030 image->data = (unsigned char *)RL_MALLOC(image->width*image->height*3*sizeof(unsigned char));
1031
1032 for (int i = 0, k = 0; i < image->width*image->height*3; i += 3, k++)
1033 {
1034 ((unsigned char *)image->data)[i] = (unsigned char)(pixels[k].x*255.0f);
1035 ((unsigned char *)image->data)[i + 1] = (unsigned char)(pixels[k].y*255.0f);
1036 ((unsigned char *)image->data)[i + 2] = (unsigned char)(pixels[k].z*255.0f);
1037 }
1038 } break;
1039 case UNCOMPRESSED_R5G5B5A1:
1040 {
1041 #define ALPHA_THRESHOLD 50
1042
1043 image->data = (unsigned short *)RL_MALLOC(image->width*image->height*sizeof(unsigned short));
1044
1045 unsigned char r = 0;
1046 unsigned char g = 0;
1047 unsigned char b = 0;
1048 unsigned char a = 0;
1049
1050 for (int i = 0; i < image->width*image->height; i++)
1051 {
1052 r = (unsigned char)(round(pixels[i].x*31.0f));
1053 g = (unsigned char)(round(pixels[i].y*31.0f));
1054 b = (unsigned char)(round(pixels[i].z*31.0f));
1055 a = (pixels[i].w > ((float)ALPHA_THRESHOLD/255.0f))? 1 : 0;
1056
1057 ((unsigned short *)image->data)[i] = (unsigned short)r << 11 | (unsigned short)g << 6 | (unsigned short)b << 1 | (unsigned short)a;
1058 }
1059
1060 } break;
1061 case UNCOMPRESSED_R4G4B4A4:
1062 {
1063 image->data = (unsigned short *)RL_MALLOC(image->width*image->height*sizeof(unsigned short));
1064
1065 unsigned char r = 0;
1066 unsigned char g = 0;
1067 unsigned char b = 0;
1068 unsigned char a = 0;
1069
1070 for (int i = 0; i < image->width*image->height; i++)
1071 {
1072 r = (unsigned char)(round(pixels[i].x*15.0f));
1073 g = (unsigned char)(round(pixels[i].y*15.0f));
1074 b = (unsigned char)(round(pixels[i].z*15.0f));
1075 a = (unsigned char)(round(pixels[i].w*15.0f));
1076
1077 ((unsigned short *)image->data)[i] = (unsigned short)r << 12 | (unsigned short)g << 8 | (unsigned short)b << 4 | (unsigned short)a;
1078 }
1079
1080 } break;
1081 case UNCOMPRESSED_R8G8B8A8:
1082 {
1083 image->data = (unsigned char *)RL_MALLOC(image->width*image->height*4*sizeof(unsigned char));
1084
1085 for (int i = 0, k = 0; i < image->width*image->height*4; i += 4, k++)
1086 {
1087 ((unsigned char *)image->data)[i] = (unsigned char)(pixels[k].x*255.0f);
1088 ((unsigned char *)image->data)[i + 1] = (unsigned char)(pixels[k].y*255.0f);
1089 ((unsigned char *)image->data)[i + 2] = (unsigned char)(pixels[k].z*255.0f);
1090 ((unsigned char *)image->data)[i + 3] = (unsigned char)(pixels[k].w*255.0f);
1091 }
1092 } break;
1093 case UNCOMPRESSED_R32:
1094 {
1095 // WARNING: Image is converted to GRAYSCALE eqeuivalent 32bit
1096
1097 image->data = (float *)RL_MALLOC(image->width*image->height*sizeof(float));
1098
1099 for (int i = 0; i < image->width*image->height; i++)
1100 {
1101 ((float *)image->data)[i] = (float)(pixels[i].x*0.299f + pixels[i].y*0.587f + pixels[i].z*0.114f);
1102 }
1103 } break;
1104 case UNCOMPRESSED_R32G32B32:
1105 {
1106 image->data = (float *)RL_MALLOC(image->width*image->height*3*sizeof(float));
1107
1108 for (int i = 0, k = 0; i < image->width*image->height*3; i += 3, k++)
1109 {
1110 ((float *)image->data)[i] = pixels[k].x;
1111 ((float *)image->data)[i + 1] = pixels[k].y;
1112 ((float *)image->data)[i + 2] = pixels[k].z;
1113 }
1114 } break;
1115 case UNCOMPRESSED_R32G32B32A32:
1116 {
1117 image->data = (float *)RL_MALLOC(image->width*image->height*4*sizeof(float));
1118
1119 for (int i = 0, k = 0; i < image->width*image->height*4; i += 4, k++)
1120 {
1121 ((float *)image->data)[i] = pixels[k].x;
1122 ((float *)image->data)[i + 1] = pixels[k].y;
1123 ((float *)image->data)[i + 2] = pixels[k].z;
1124 ((float *)image->data)[i + 3] = pixels[k].w;
1125 }
1126 } break;
1127 default: break;
1128 }
1129
1130 RL_FREE(pixels);
1131 pixels = NULL;
1132
1133 // In case original image had mipmaps, generate mipmaps for formated image
1134 // NOTE: Original mipmaps are replaced by new ones, if custom mipmaps were used, they are lost
1135 if (image->mipmaps > 1)
1136 {
1137 image->mipmaps = 1;
1138 #if defined(SUPPORT_IMAGE_MANIPULATION)
1139 if (image->data != NULL) ImageMipmaps(image);
1140 #endif
1141 }
1142 }
1143 else TRACELOG(LOG_WARNING, "IMAGE: Data format is compressed, can not be converted");
1144 }
1145}
1146
1147// Apply alpha mask to image
1148// NOTE 1: Returned image is GRAY_ALPHA (16bit) or RGBA (32bit)
1149// NOTE 2: alphaMask should be same size as image
1150void ImageAlphaMask(Image *image, Image alphaMask)
1151{
1152 if ((image->width != alphaMask.width) || (image->height != alphaMask.height))
1153 {
1154 TRACELOG(LOG_WARNING, "IMAGE: Alpha mask must be same size as image");
1155 }
1156 else if (image->format >= COMPRESSED_DXT1_RGB)
1157 {
1158 TRACELOG(LOG_WARNING, "IMAGE: Alpha mask can not be applied to compressed data formats");
1159 }
1160 else
1161 {
1162 // Force mask to be Grayscale
1163 Image mask = ImageCopy(alphaMask);
1164 if (mask.format != UNCOMPRESSED_GRAYSCALE) ImageFormat(&mask, UNCOMPRESSED_GRAYSCALE);
1165
1166 // In case image is only grayscale, we just add alpha channel
1167 if (image->format == UNCOMPRESSED_GRAYSCALE)
1168 {
1169 unsigned char *data = (unsigned char *)RL_MALLOC(image->width*image->height*2);
1170
1171 // Apply alpha mask to alpha channel
1172 for (int i = 0, k = 0; (i < mask.width*mask.height) || (i < image->width*image->height); i++, k += 2)
1173 {
1174 data[k] = ((unsigned char *)image->data)[i];
1175 data[k + 1] = ((unsigned char *)mask.data)[i];
1176 }
1177
1178 RL_FREE(image->data);
1179 image->data = data;
1180 image->format = UNCOMPRESSED_GRAY_ALPHA;
1181 }
1182 else
1183 {
1184 // Convert image to RGBA
1185 if (image->format != UNCOMPRESSED_R8G8B8A8) ImageFormat(image, UNCOMPRESSED_R8G8B8A8);
1186
1187 // Apply alpha mask to alpha channel
1188 for (int i = 0, k = 3; (i < mask.width*mask.height) || (i < image->width*image->height); i++, k += 4)
1189 {
1190 ((unsigned char *)image->data)[k] = ((unsigned char *)mask.data)[i];
1191 }
1192 }
1193
1194 UnloadImage(mask);
1195 }
1196}
1197
1198// Clear alpha channel to desired color
1199// NOTE: Threshold defines the alpha limit, 0.0f to 1.0f
1200void ImageAlphaClear(Image *image, Color color, float threshold)
1201{
1202 // Security check to avoid program crash
1203 if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return;
1204
1205 Color *pixels = GetImageData(*image);
1206
1207 for (int i = 0; i < image->width*image->height; i++) if (pixels[i].a <= (unsigned char)(threshold*255.0f)) pixels[i] = color;
1208
1209 UnloadImage(*image);
1210
1211 int prevFormat = image->format;
1212 *image = LoadImageEx(pixels, image->width, image->height);
1213
1214 ImageFormat(image, prevFormat);
1215 RL_FREE(pixels);
1216}
1217
1218// Premultiply alpha channel
1219void ImageAlphaPremultiply(Image *image)
1220{
1221 // Security check to avoid program crash
1222 if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return;
1223
1224 float alpha = 0.0f;
1225 Color *pixels = GetImageData(*image);
1226
1227 for (int i = 0; i < image->width*image->height; i++)
1228 {
1229 alpha = (float)pixels[i].a/255.0f;
1230 pixels[i].r = (unsigned char)((float)pixels[i].r*alpha);
1231 pixels[i].g = (unsigned char)((float)pixels[i].g*alpha);
1232 pixels[i].b = (unsigned char)((float)pixels[i].b*alpha);
1233 }
1234
1235 UnloadImage(*image);
1236
1237 int prevFormat = image->format;
1238 *image = LoadImageEx(pixels, image->width, image->height);
1239
1240 ImageFormat(image, prevFormat);
1241 RL_FREE(pixels);
1242}
1243
1244#if defined(SUPPORT_IMAGE_MANIPULATION)
1245// Load cubemap from image, multiple image cubemap layouts supported
1246TextureCubemap LoadTextureCubemap(Image image, int layoutType)
1247{
1248 TextureCubemap cubemap = { 0 };
1249
1250 if (layoutType == CUBEMAP_AUTO_DETECT) // Try to automatically guess layout type
1251 {
1252 // Check image width/height to determine the type of cubemap provided
1253 if (image.width > image.height)
1254 {
1255 if ((image.width/6) == image.height) { layoutType = CUBEMAP_LINE_HORIZONTAL; cubemap.width = image.width/6; }
1256 else if ((image.width/4) == (image.height/3)) { layoutType = CUBEMAP_CROSS_FOUR_BY_THREE; cubemap.width = image.width/4; }
1257 else if (image.width >= (int)((float)image.height*1.85f)) { layoutType = CUBEMAP_PANORAMA; cubemap.width = image.width/4; }
1258 }
1259 else if (image.height > image.width)
1260 {
1261 if ((image.height/6) == image.width) { layoutType = CUBEMAP_LINE_VERTICAL; cubemap.width = image.height/6; }
1262 else if ((image.width/3) == (image.height/4)) { layoutType = CUBEMAP_CROSS_THREE_BY_FOUR; cubemap.width = image.width/3; }
1263 }
1264
1265 cubemap.height = cubemap.width;
1266 }
1267
1268 if (layoutType != CUBEMAP_AUTO_DETECT)
1269 {
1270 int size = cubemap.width;
1271
1272 Image faces = { 0 }; // Vertical column image
1273 Rectangle faceRecs[6] = { 0 }; // Face source rectangles
1274 for (int i = 0; i < 6; i++) faceRecs[i] = (Rectangle){ 0, 0, size, size };
1275
1276 if (layoutType == CUBEMAP_LINE_VERTICAL)
1277 {
1278 faces = image;
1279 for (int i = 0; i < 6; i++) faceRecs[i].y = size*i;
1280 }
1281 else if (layoutType == CUBEMAP_PANORAMA)
1282 {
1283 // TODO: Convert panorama image to square faces...
1284 // Ref: https://github.com/denivip/panorama/blob/master/panorama.cpp
1285 }
1286 else
1287 {
1288 if (layoutType == CUBEMAP_LINE_HORIZONTAL) for (int i = 0; i < 6; i++) faceRecs[i].x = size*i;
1289 else if (layoutType == CUBEMAP_CROSS_THREE_BY_FOUR)
1290 {
1291 faceRecs[0].x = size; faceRecs[0].y = size;
1292 faceRecs[1].x = size; faceRecs[1].y = 3*size;
1293 faceRecs[2].x = size; faceRecs[2].y = 0;
1294 faceRecs[3].x = size; faceRecs[3].y = 2*size;
1295 faceRecs[4].x = 0; faceRecs[4].y = size;
1296 faceRecs[5].x = 2*size; faceRecs[5].y = size;
1297 }
1298 else if (layoutType == CUBEMAP_CROSS_FOUR_BY_THREE)
1299 {
1300 faceRecs[0].x = 2*size; faceRecs[0].y = size;
1301 faceRecs[1].x = 0; faceRecs[1].y = size;
1302 faceRecs[2].x = size; faceRecs[2].y = 0;
1303 faceRecs[3].x = size; faceRecs[3].y = 2*size;
1304 faceRecs[4].x = size; faceRecs[4].y = size;
1305 faceRecs[5].x = 3*size; faceRecs[5].y = size;
1306 }
1307
1308 // Convert image data to 6 faces in a vertical column, that's the optimum layout for loading
1309 faces = GenImageColor(size, size*6, MAGENTA);
1310 ImageFormat(&faces, image.format);
1311
1312 // TODO: Image formating does not work with compressed textures!
1313 }
1314
1315 for (int i = 0; i < 6; i++) ImageDraw(&faces, image, faceRecs[i], (Rectangle){ 0, size*i, size, size }, WHITE);
1316
1317 cubemap.id = rlLoadTextureCubemap(faces.data, size, faces.format);
1318 if (cubemap.id == 0) TRACELOG(LOG_WARNING, "IMAGE: Failed to load cubemap image");
1319
1320 UnloadImage(faces);
1321 }
1322 else TRACELOG(LOG_WARNING, "IMAGE: Failed to detect cubemap image layout");
1323
1324 return cubemap;
1325}
1326
1327// Crop an image to area defined by a rectangle
1328// NOTE: Security checks are performed in case rectangle goes out of bounds
1329void ImageCrop(Image *image, Rectangle crop)
1330{
1331 // Security check to avoid program crash
1332 if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return;
1333
1334 // Security checks to validate crop rectangle
1335 if (crop.x < 0) { crop.width += crop.x; crop.x = 0; }
1336 if (crop.y < 0) { crop.height += crop.y; crop.y = 0; }
1337 if ((crop.x + crop.width) > image->width) crop.width = image->width - crop.x;
1338 if ((crop.y + crop.height) > image->height) crop.height = image->height - crop.y;
1339
1340 if ((crop.x < image->width) && (crop.y < image->height))
1341 {
1342 // Start the cropping process
1343 Color *pixels = GetImageData(*image); // Get data as Color pixels array
1344 Color *cropPixels = (Color *)RL_MALLOC((int)crop.width*(int)crop.height*sizeof(Color));
1345
1346 for (int j = (int)crop.y; j < (int)(crop.y + crop.height); j++)
1347 {
1348 for (int i = (int)crop.x; i < (int)(crop.x + crop.width); i++)
1349 {
1350 cropPixels[(j - (int)crop.y)*(int)crop.width + (i - (int)crop.x)] = pixels[j*image->width + i];
1351 }
1352 }
1353
1354 RL_FREE(pixels);
1355
1356 int format = image->format;
1357
1358 UnloadImage(*image);
1359
1360 *image = LoadImageEx(cropPixels, (int)crop.width, (int)crop.height);
1361
1362 RL_FREE(cropPixels);
1363
1364 // Reformat 32bit RGBA image to original format
1365 ImageFormat(image, format);
1366 }
1367 else TRACELOG(LOG_WARNING, "IMAGE: Failed to crop, rectangle out of bounds");
1368}
1369
1370// Crop image depending on alpha value
1371void ImageAlphaCrop(Image *image, float threshold)
1372{
1373 // Security check to avoid program crash
1374 if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return;
1375
1376 Color *pixels = GetImageData(*image);
1377
1378 int xMin = 65536; // Define a big enough number
1379 int xMax = 0;
1380 int yMin = 65536;
1381 int yMax = 0;
1382
1383 for (int y = 0; y < image->height; y++)
1384 {
1385 for (int x = 0; x < image->width; x++)
1386 {
1387 if (pixels[y*image->width + x].a > (unsigned char)(threshold*255.0f))
1388 {
1389 if (x < xMin) xMin = x;
1390 if (x > xMax) xMax = x;
1391 if (y < yMin) yMin = y;
1392 if (y > yMax) yMax = y;
1393 }
1394 }
1395 }
1396
1397 Rectangle crop = { xMin, yMin, (xMax + 1) - xMin, (yMax + 1) - yMin };
1398
1399 RL_FREE(pixels);
1400
1401 // Check for not empty image brefore cropping
1402 if (!((xMax < xMin) || (yMax < yMin))) ImageCrop(image, crop);
1403}
1404
1405// Resize and image to new size
1406// NOTE: Uses stb default scaling filters (both bicubic):
1407// STBIR_DEFAULT_FILTER_UPSAMPLE STBIR_FILTER_CATMULLROM
1408// STBIR_DEFAULT_FILTER_DOWNSAMPLE STBIR_FILTER_MITCHELL (high-quality Catmull-Rom)
1409void ImageResize(Image *image, int newWidth, int newHeight)
1410{
1411 // Security check to avoid program crash
1412 if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return;
1413
1414 // Get data as Color pixels array to work with it
1415 Color *pixels = GetImageData(*image);
1416 Color *output = (Color *)RL_MALLOC(newWidth*newHeight*sizeof(Color));
1417
1418 // NOTE: Color data is casted to (unsigned char *), there shouldn't been any problem...
1419 stbir_resize_uint8((unsigned char *)pixels, image->width, image->height, 0, (unsigned char *)output, newWidth, newHeight, 0, 4);
1420
1421 int format = image->format;
1422
1423 UnloadImage(*image);
1424
1425 *image = LoadImageEx(output, newWidth, newHeight);
1426 ImageFormat(image, format); // Reformat 32bit RGBA image to original format
1427
1428 RL_FREE(output);
1429 RL_FREE(pixels);
1430}
1431
1432// Resize and image to new size using Nearest-Neighbor scaling algorithm
1433void ImageResizeNN(Image *image,int newWidth,int newHeight)
1434{
1435 // Security check to avoid program crash
1436 if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return;
1437
1438 Color *pixels = GetImageData(*image);
1439 Color *output = (Color *)RL_MALLOC(newWidth*newHeight*sizeof(Color));
1440
1441 // EDIT: added +1 to account for an early rounding problem
1442 int xRatio = (int)((image->width << 16)/newWidth) + 1;
1443 int yRatio = (int)((image->height << 16)/newHeight) + 1;
1444
1445 int x2, y2;
1446 for (int y = 0; y < newHeight; y++)
1447 {
1448 for (int x = 0; x < newWidth; x++)
1449 {
1450 x2 = ((x*xRatio) >> 16);
1451 y2 = ((y*yRatio) >> 16);
1452
1453 output[(y*newWidth) + x] = pixels[(y2*image->width) + x2] ;
1454 }
1455 }
1456
1457 int format = image->format;
1458
1459 UnloadImage(*image);
1460
1461 *image = LoadImageEx(output, newWidth, newHeight);
1462 ImageFormat(image, format); // Reformat 32bit RGBA image to original format
1463
1464 RL_FREE(output);
1465 RL_FREE(pixels);
1466}
1467
1468// Resize canvas and fill with color
1469// NOTE: Resize offset is relative to the top-left corner of the original image
1470void ImageResizeCanvas(Image *image, int newWidth, int newHeight, int offsetX, int offsetY, Color color)
1471{
1472 // Security check to avoid program crash
1473 if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return;
1474
1475 if ((newWidth != image->width) || (newHeight != image->height))
1476 {
1477 // Support offsets out of canvas new size -> original image is cropped
1478 if (offsetX < 0)
1479 {
1480 ImageCrop(image, (Rectangle) { -offsetX, 0, image->width + offsetX, image->height });
1481 offsetX = 0;
1482 }
1483 else if (offsetX > (newWidth - image->width))
1484 {
1485 ImageCrop(image, (Rectangle) { 0, 0, image->width - (offsetX - (newWidth - image->width)), image->height });
1486 offsetX = newWidth - image->width;
1487 }
1488
1489 if (offsetY < 0)
1490 {
1491 ImageCrop(image, (Rectangle) { 0, -offsetY, image->width, image->height + offsetY });
1492 offsetY = 0;
1493 }
1494 else if (offsetY > (newHeight - image->height))
1495 {
1496 ImageCrop(image, (Rectangle) { 0, 0, image->width, image->height - (offsetY - (newHeight - image->height)) });
1497 offsetY = newHeight - image->height;
1498 }
1499
1500 if ((newWidth > image->width) && (newHeight > image->height))
1501 {
1502 Image imTemp = GenImageColor(newWidth, newHeight, color);
1503
1504 Rectangle srcRec = { 0.0f, 0.0f, (float)image->width, (float)image->height };
1505 Rectangle dstRec = { (float)offsetX, (float)offsetY, srcRec.width, srcRec.height };
1506
1507 ImageDraw(&imTemp, *image, srcRec, dstRec, WHITE);
1508 ImageFormat(&imTemp, image->format);
1509 UnloadImage(*image);
1510 *image = imTemp;
1511 }
1512 else if ((newWidth < image->width) && (newHeight < image->height))
1513 {
1514 Rectangle crop = { (float)offsetX, (float)offsetY, (float)newWidth, (float)newHeight };
1515 ImageCrop(image, crop);
1516 }
1517 else // One side is bigger and the other is smaller
1518 {
1519 Image imTemp = GenImageColor(newWidth, newHeight, color);
1520
1521 Rectangle srcRec = { 0.0f, 0.0f, (float)image->width, (float)image->height };
1522 Rectangle dstRec = { (float)offsetX, (float)offsetY, (float)image->width, (float)image->height };
1523
1524 if (newWidth < image->width)
1525 {
1526 srcRec.x = offsetX;
1527 srcRec.width = newWidth;
1528 dstRec.x = 0.0f;
1529 }
1530
1531 if (newHeight < image->height)
1532 {
1533 srcRec.y = offsetY;
1534 srcRec.height = newHeight;
1535 dstRec.y = 0.0f;
1536 }
1537
1538 ImageDraw(&imTemp, *image, srcRec, dstRec, WHITE);
1539 ImageFormat(&imTemp, image->format);
1540 UnloadImage(*image);
1541 *image = imTemp;
1542 }
1543 }
1544}
1545
1546// Generate all mipmap levels for a provided image
1547// NOTE 1: Supports POT and NPOT images
1548// NOTE 2: image.data is scaled to include mipmap levels
1549// NOTE 3: Mipmaps format is the same as base image
1550void ImageMipmaps(Image *image)
1551{
1552 // Security check to avoid program crash
1553 if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return;
1554
1555 int mipCount = 1; // Required mipmap levels count (including base level)
1556 int mipWidth = image->width; // Base image width
1557 int mipHeight = image->height; // Base image height
1558 int mipSize = GetPixelDataSize(mipWidth, mipHeight, image->format); // Image data size (in bytes)
1559
1560 // Count mipmap levels required
1561 while ((mipWidth != 1) || (mipHeight != 1))
1562 {
1563 if (mipWidth != 1) mipWidth /= 2;
1564 if (mipHeight != 1) mipHeight /= 2;
1565
1566 // Security check for NPOT textures
1567 if (mipWidth < 1) mipWidth = 1;
1568 if (mipHeight < 1) mipHeight = 1;
1569
1570 TRACELOGD("IMAGE: Next mipmap level: %i x %i - current size %i", mipWidth, mipHeight, mipSize);
1571
1572 mipCount++;
1573 mipSize += GetPixelDataSize(mipWidth, mipHeight, image->format); // Add mipmap size (in bytes)
1574 }
1575
1576 if (image->mipmaps < mipCount)
1577 {
1578 void *temp = RL_REALLOC(image->data, mipSize);
1579
1580 if (temp != NULL) image->data = temp; // Assign new pointer (new size) to store mipmaps data
1581 else TRACELOG(LOG_WARNING, "IMAGE: Mipmaps required memory could not be allocated");
1582
1583 // Pointer to allocated memory point where store next mipmap level data
1584 unsigned char *nextmip = (unsigned char *)image->data + GetPixelDataSize(image->width, image->height, image->format);
1585
1586 mipWidth = image->width/2;
1587 mipHeight = image->height/2;
1588 mipSize = GetPixelDataSize(mipWidth, mipHeight, image->format);
1589 Image imCopy = ImageCopy(*image);
1590
1591 for (int i = 1; i < mipCount; i++)
1592 {
1593 TRACELOGD("IMAGE: Generating mipmap level: %i (%i x %i) - size: %i - offset: 0x%x", i, mipWidth, mipHeight, mipSize, nextmip);
1594
1595 ImageResize(&imCopy, mipWidth, mipHeight); // Uses internally Mitchell cubic downscale filter
1596
1597 memcpy(nextmip, imCopy.data, mipSize);
1598 nextmip += mipSize;
1599 image->mipmaps++;
1600
1601 mipWidth /= 2;
1602 mipHeight /= 2;
1603
1604 // Security check for NPOT textures
1605 if (mipWidth < 1) mipWidth = 1;
1606 if (mipHeight < 1) mipHeight = 1;
1607
1608 mipSize = GetPixelDataSize(mipWidth, mipHeight, image->format);
1609 }
1610
1611 UnloadImage(imCopy);
1612 }
1613 else TRACELOG(LOG_WARNING, "IMAGE: Mipmaps already available");
1614}
1615
1616// Dither image data to 16bpp or lower (Floyd-Steinberg dithering)
1617// NOTE: In case selected bpp do not represent an known 16bit format,
1618// dithered data is stored in the LSB part of the unsigned short
1619void ImageDither(Image *image, int rBpp, int gBpp, int bBpp, int aBpp)
1620{
1621 // Security check to avoid program crash
1622 if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return;
1623
1624 if (image->format >= COMPRESSED_DXT1_RGB)
1625 {
1626 TRACELOG(LOG_WARNING, "IMAGE: Compressed data formats can not be dithered");
1627 return;
1628 }
1629
1630 if ((rBpp + gBpp + bBpp + aBpp) > 16)
1631 {
1632 TRACELOG(LOG_WARNING, "IMAGE: Unsupported dithering bpps (%ibpp), only 16bpp or lower modes supported", (rBpp+gBpp+bBpp+aBpp));
1633 }
1634 else
1635 {
1636 Color *pixels = GetImageData(*image);
1637
1638 RL_FREE(image->data); // free old image data
1639
1640 if ((image->format != UNCOMPRESSED_R8G8B8) && (image->format != UNCOMPRESSED_R8G8B8A8))
1641 {
1642 TRACELOG(LOG_WARNING, "IMAGE: Format is already 16bpp or lower, dithering could have no effect");
1643 }
1644
1645 // Define new image format, check if desired bpp match internal known format
1646 if ((rBpp == 5) && (gBpp == 6) && (bBpp == 5) && (aBpp == 0)) image->format = UNCOMPRESSED_R5G6B5;
1647 else if ((rBpp == 5) && (gBpp == 5) && (bBpp == 5) && (aBpp == 1)) image->format = UNCOMPRESSED_R5G5B5A1;
1648 else if ((rBpp == 4) && (gBpp == 4) && (bBpp == 4) && (aBpp == 4)) image->format = UNCOMPRESSED_R4G4B4A4;
1649 else
1650 {
1651 image->format = 0;
1652 TRACELOG(LOG_WARNING, "IMAGE: Unsupported dithered OpenGL internal format: %ibpp (R%iG%iB%iA%i)", (rBpp+gBpp+bBpp+aBpp), rBpp, gBpp, bBpp, aBpp);
1653 }
1654
1655 // NOTE: We will store the dithered data as unsigned short (16bpp)
1656 image->data = (unsigned short *)RL_MALLOC(image->width*image->height*sizeof(unsigned short));
1657
1658 Color oldPixel = WHITE;
1659 Color newPixel = WHITE;
1660
1661 int rError, gError, bError;
1662 unsigned short rPixel, gPixel, bPixel, aPixel; // Used for 16bit pixel composition
1663
1664 #define MIN(a,b) (((a)<(b))?(a):(b))
1665
1666 for (int y = 0; y < image->height; y++)
1667 {
1668 for (int x = 0; x < image->width; x++)
1669 {
1670 oldPixel = pixels[y*image->width + x];
1671
1672 // NOTE: New pixel obtained by bits truncate, it would be better to round values (check ImageFormat())
1673 newPixel.r = oldPixel.r >> (8 - rBpp); // R bits
1674 newPixel.g = oldPixel.g >> (8 - gBpp); // G bits
1675 newPixel.b = oldPixel.b >> (8 - bBpp); // B bits
1676 newPixel.a = oldPixel.a >> (8 - aBpp); // A bits (not used on dithering)
1677
1678 // NOTE: Error must be computed between new and old pixel but using same number of bits!
1679 // We want to know how much color precision we have lost...
1680 rError = (int)oldPixel.r - (int)(newPixel.r << (8 - rBpp));
1681 gError = (int)oldPixel.g - (int)(newPixel.g << (8 - gBpp));
1682 bError = (int)oldPixel.b - (int)(newPixel.b << (8 - bBpp));
1683
1684 pixels[y*image->width + x] = newPixel;
1685
1686 // NOTE: Some cases are out of the array and should be ignored
1687 if (x < (image->width - 1))
1688 {
1689 pixels[y*image->width + x+1].r = MIN((int)pixels[y*image->width + x+1].r + (int)((float)rError*7.0f/16), 0xff);
1690 pixels[y*image->width + x+1].g = MIN((int)pixels[y*image->width + x+1].g + (int)((float)gError*7.0f/16), 0xff);
1691 pixels[y*image->width + x+1].b = MIN((int)pixels[y*image->width + x+1].b + (int)((float)bError*7.0f/16), 0xff);
1692 }
1693
1694 if ((x > 0) && (y < (image->height - 1)))
1695 {
1696 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);
1697 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);
1698 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);
1699 }
1700
1701 if (y < (image->height - 1))
1702 {
1703 pixels[(y+1)*image->width + x].r = MIN((int)pixels[(y+1)*image->width + x].r + (int)((float)rError*5.0f/16), 0xff);
1704 pixels[(y+1)*image->width + x].g = MIN((int)pixels[(y+1)*image->width + x].g + (int)((float)gError*5.0f/16), 0xff);
1705 pixels[(y+1)*image->width + x].b = MIN((int)pixels[(y+1)*image->width + x].b + (int)((float)bError*5.0f/16), 0xff);
1706 }
1707
1708 if ((x < (image->width - 1)) && (y < (image->height - 1)))
1709 {
1710 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);
1711 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);
1712 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);
1713 }
1714
1715 rPixel = (unsigned short)newPixel.r;
1716 gPixel = (unsigned short)newPixel.g;
1717 bPixel = (unsigned short)newPixel.b;
1718 aPixel = (unsigned short)newPixel.a;
1719
1720 ((unsigned short *)image->data)[y*image->width + x] = (rPixel << (gBpp + bBpp + aBpp)) | (gPixel << (bBpp + aBpp)) | (bPixel << aBpp) | aPixel;
1721 }
1722 }
1723
1724 RL_FREE(pixels);
1725 }
1726}
1727
1728// Extract color palette from image to maximum size
1729// NOTE: Memory allocated should be freed manually!
1730Color *ImageExtractPalette(Image image, int maxPaletteSize, int *extractCount)
1731{
1732 #define COLOR_EQUAL(col1, col2) ((col1.r == col2.r)&&(col1.g == col2.g)&&(col1.b == col2.b)&&(col1.a == col2.a))
1733
1734 Color *pixels = GetImageData(image);
1735 Color *palette = (Color *)RL_MALLOC(maxPaletteSize*sizeof(Color));
1736
1737 int palCount = 0;
1738 for (int i = 0; i < maxPaletteSize; i++) palette[i] = BLANK; // Set all colors to BLANK
1739
1740 for (int i = 0; i < image.width*image.height; i++)
1741 {
1742 if (pixels[i].a > 0)
1743 {
1744 bool colorInPalette = false;
1745
1746 // Check if the color is already on palette
1747 for (int j = 0; j < maxPaletteSize; j++)
1748 {
1749 if (COLOR_EQUAL(pixels[i], palette[j]))
1750 {
1751 colorInPalette = true;
1752 break;
1753 }
1754 }
1755
1756 // Store color if not on the palette
1757 if (!colorInPalette)
1758 {
1759 palette[palCount] = pixels[i]; // Add pixels[i] to palette
1760 palCount++;
1761
1762 // We reached the limit of colors supported by palette
1763 if (palCount >= maxPaletteSize)
1764 {
1765 i = image.width*image.height; // Finish palette get
1766 TRACELOG(LOG_WARNING, "IMAGE: Palette is greater than %i colors", maxPaletteSize);
1767 }
1768 }
1769 }
1770 }
1771
1772 RL_FREE(pixels);
1773
1774 *extractCount = palCount;
1775
1776 return palette;
1777}
1778
1779// Draw an image (source) within an image (destination)
1780// NOTE: Color tint is applied to source image
1781void ImageDraw(Image *dst, Image src, Rectangle srcRec, Rectangle dstRec, Color tint)
1782{
1783 // Security check to avoid program crash
1784 if ((dst->data == NULL) || (dst->width == 0) || (dst->height == 0) ||
1785 (src.data == NULL) || (src.width == 0) || (src.height == 0)) return;
1786
1787 // Security checks to avoid size and rectangle issues (out of bounds)
1788 // Check that srcRec is inside src image
1789 if (srcRec.x < 0) srcRec.x = 0;
1790 if (srcRec.y < 0) srcRec.y = 0;
1791
1792 if ((srcRec.x + srcRec.width) > src.width)
1793 {
1794 srcRec.width = src.width - srcRec.x;
1795 TRACELOG(LOG_WARNING, "IMAGE: Source rectangle width out of bounds, rescaled width: %i", srcRec.width);
1796 }
1797
1798 if ((srcRec.y + srcRec.height) > src.height)
1799 {
1800 srcRec.height = src.height - srcRec.y;
1801 TRACELOG(LOG_WARNING, "IMAGE: Source rectangle height out of bounds, rescaled height: %i", srcRec.height);
1802 }
1803
1804 Image srcCopy = ImageCopy(src); // Make a copy of source image to work with it
1805
1806 // Crop source image to desired source rectangle (if required)
1807 if ((src.width != (int)srcRec.width) && (src.height != (int)srcRec.height)) ImageCrop(&srcCopy, srcRec);
1808
1809 // Scale source image in case destination rec size is different than source rec size
1810 if (((int)dstRec.width != (int)srcRec.width) || ((int)dstRec.height != (int)srcRec.height))
1811 {
1812 ImageResize(&srcCopy, (int)dstRec.width, (int)dstRec.height);
1813 }
1814
1815 // Check that dstRec is inside dst image
1816 // Allow negative position within destination with cropping
1817 if (dstRec.x < 0)
1818 {
1819 ImageCrop(&srcCopy, (Rectangle) { -dstRec.x, 0, dstRec.width + dstRec.x, dstRec.height });
1820 dstRec.width = dstRec.width + dstRec.x;
1821 dstRec.x = 0;
1822 }
1823
1824 if ((dstRec.x + dstRec.width) > dst->width)
1825 {
1826 ImageCrop(&srcCopy, (Rectangle) { 0, 0, dst->width - dstRec.x, dstRec.height });
1827 dstRec.width = dst->width - dstRec.x;
1828 }
1829
1830 if (dstRec.y < 0)
1831 {
1832 ImageCrop(&srcCopy, (Rectangle) { 0, -dstRec.y, dstRec.width, dstRec.height + dstRec.y });
1833 dstRec.height = dstRec.height + dstRec.y;
1834 dstRec.y = 0;
1835 }
1836
1837 if ((dstRec.y + dstRec.height) > dst->height)
1838 {
1839 ImageCrop(&srcCopy, (Rectangle) { 0, 0, dstRec.width, dst->height - dstRec.y });
1840 dstRec.height = dst->height - dstRec.y;
1841 }
1842
1843 // Get image data as Color pixels array to work with it
1844 Color *dstPixels = GetImageData(*dst);
1845 Color *srcPixels = GetImageData(srcCopy);
1846
1847 UnloadImage(srcCopy); // Source copy not required any more
1848
1849 Vector4 fsrc, fdst, fout; // Normalized pixel data (ready for operation)
1850 Vector4 ftint = ColorNormalize(tint); // Normalized color tint
1851
1852 // Blit pixels, copy source image into destination
1853 // TODO: Maybe out-of-bounds blitting could be considered here instead of so much cropping
1854 for (int j = (int)dstRec.y; j < (int)(dstRec.y + dstRec.height); j++)
1855 {
1856 for (int i = (int)dstRec.x; i < (int)(dstRec.x + dstRec.width); i++)
1857 {
1858 // Alpha blending (https://en.wikipedia.org/wiki/Alpha_compositing)
1859
1860 fdst = ColorNormalize(dstPixels[j*(int)dst->width + i]);
1861 fsrc = ColorNormalize(srcPixels[(j - (int)dstRec.y)*(int)dstRec.width + (i - (int)dstRec.x)]);
1862
1863 // Apply color tint to source image
1864 fsrc.x *= ftint.x; fsrc.y *= ftint.y; fsrc.z *= ftint.z; fsrc.w *= ftint.w;
1865
1866 fout.w = fsrc.w + fdst.w*(1.0f - fsrc.w);
1867
1868 if (fout.w <= 0.0f)
1869 {
1870 fout.x = 0.0f;
1871 fout.y = 0.0f;
1872 fout.z = 0.0f;
1873 }
1874 else
1875 {
1876 fout.x = (fsrc.x*fsrc.w + fdst.x*fdst.w*(1 - fsrc.w))/fout.w;
1877 fout.y = (fsrc.y*fsrc.w + fdst.y*fdst.w*(1 - fsrc.w))/fout.w;
1878 fout.z = (fsrc.z*fsrc.w + fdst.z*fdst.w*(1 - fsrc.w))/fout.w;
1879 }
1880
1881 dstPixels[j*(int)dst->width + i] = (Color){ (unsigned char)(fout.x*255.0f),
1882 (unsigned char)(fout.y*255.0f),
1883 (unsigned char)(fout.z*255.0f),
1884 (unsigned char)(fout.w*255.0f) };
1885
1886 // TODO: Support other blending options
1887 }
1888 }
1889
1890 UnloadImage(*dst);
1891
1892 *dst = LoadImageEx(dstPixels, (int)dst->width, (int)dst->height);
1893 ImageFormat(dst, dst->format);
1894
1895 RL_FREE(srcPixels);
1896 RL_FREE(dstPixels);
1897}
1898
1899// Create an image from text (default font)
1900Image ImageText(const char *text, int fontSize, Color color)
1901{
1902 int defaultFontSize = 10; // Default Font chars height in pixel
1903 if (fontSize < defaultFontSize) fontSize = defaultFontSize;
1904 int spacing = fontSize / defaultFontSize;
1905
1906 Image imText = ImageTextEx(GetFontDefault(), text, (float)fontSize, (float)spacing, color);
1907
1908 return imText;
1909}
1910
1911// Create an image from text (custom sprite font)
1912Image ImageTextEx(Font font, const char *text, float fontSize, float spacing, Color tint)
1913{
1914 int length = strlen(text);
1915
1916 int textOffsetX = 0; // Image drawing position X
1917 int textOffsetY = 0; // Offset between lines (on line break '\n')
1918
1919 // NOTE: Text image is generated at font base size, later scaled to desired font size
1920 Vector2 imSize = MeasureTextEx(font, text, (float)font.baseSize, spacing);
1921
1922 // Create image to store text
1923 Image imText = GenImageColor((int)imSize.x, (int)imSize.y, BLANK);
1924
1925 for (int i = 0; i < length; i++)
1926 {
1927 // Get next codepoint from byte string and glyph index in font
1928 int codepointByteCount = 0;
1929 int codepoint = GetNextCodepoint(&text[i], &codepointByteCount);
1930 int index = GetGlyphIndex(font, codepoint);
1931
1932 // NOTE: Normally we exit the decoding sequence as soon as a bad byte is found (and return 0x3f)
1933 // but we need to draw all of the bad bytes using the '?' symbol moving one byte
1934 if (codepoint == 0x3f) codepointByteCount = 1;
1935
1936 if (codepoint == '\n')
1937 {
1938 // NOTE: Fixed line spacing of 1.5 line-height
1939 // TODO: Support custom line spacing defined by user
1940 textOffsetY += (font.baseSize + font.baseSize/2);
1941 textOffsetX = 0.0f;
1942 }
1943 else
1944 {
1945 if ((codepoint != ' ') && (codepoint != '\t'))
1946 {
1947 Rectangle rec = { textOffsetX + font.chars[index].offsetX, textOffsetY + font.chars[index].offsetY, font.recs[index].width, font.recs[index].height };
1948 ImageDraw(&imText, font.chars[index].image, (Rectangle){ 0, 0, font.chars[index].image.width, font.chars[index].image.height }, rec, tint);
1949 }
1950
1951 if (font.chars[index].advanceX == 0) textOffsetX += (int)(font.recs[index].width + spacing);
1952 else textOffsetX += font.chars[index].advanceX + (int)spacing;
1953 }
1954
1955 i += (codepointByteCount - 1); // Move text bytes counter to next codepoint
1956 }
1957
1958 // Scale image depending on text size
1959 if (fontSize > imSize.y)
1960 {
1961 float scaleFactor = fontSize/imSize.y;
1962 TRACELOG(LOG_INFO, "IMAGE: Text scaled by factor: %f", scaleFactor);
1963
1964 // Using nearest-neighbor scaling algorithm for default font
1965 if (font.texture.id == GetFontDefault().texture.id) ImageResizeNN(&imText, (int)(imSize.x*scaleFactor), (int)(imSize.y*scaleFactor));
1966 else ImageResize(&imText, (int)(imSize.x*scaleFactor), (int)(imSize.y*scaleFactor));
1967 }
1968
1969 return imText;
1970}
1971
1972// Draw rectangle within an image
1973void ImageDrawRectangle(Image *dst, int posX, int posY, int width, int height, Color color)
1974{
1975 ImageDrawRectangleRec(dst, (Rectangle){ posX, posY, width, height }, color);
1976}
1977
1978// Draw rectangle within an image (Vector version)
1979void ImageDrawRectangleV(Image *dst, Vector2 position, Vector2 size, Color color)
1980{
1981 ImageDrawRectangle(dst, position.x, position.y, size.x, size.y, color);
1982}
1983
1984// Draw rectangle within an image
1985void ImageDrawRectangleRec(Image *dst, Rectangle rec, Color color)
1986{
1987 // Security check to avoid program crash
1988 if ((dst->data == NULL) || (dst->width == 0) || (dst->height == 0)) return;
1989
1990 Image imRec = GenImageColor((int)rec.width, (int)rec.height, color);
1991 ImageDraw(dst, imRec, (Rectangle){ 0, 0, rec.width, rec.height }, rec, WHITE);
1992 UnloadImage(imRec);
1993}
1994
1995// Draw rectangle lines within an image
1996void ImageDrawRectangleLines(Image *dst, Rectangle rec, int thick, Color color)
1997{
1998 ImageDrawRectangle(dst, rec.x, rec.y, rec.width, thick, color);
1999 ImageDrawRectangle(dst, rec.x, rec.y + thick, thick, rec.height - thick*2, color);
2000 ImageDrawRectangle(dst, rec.x + rec.width - thick, rec.y + thick, thick, rec.height - thick*2, color);
2001 ImageDrawRectangle(dst, rec.x, rec.y + rec.height - thick, rec.width, thick, color);
2002}
2003
2004// Clear image background with given color
2005void ImageClearBackground(Image *dst, Color color)
2006{
2007 ImageDrawRectangle(dst, 0, 0, dst->width, dst->height, color);
2008}
2009
2010// Draw pixel within an image
2011// NOTE: Compressed image formats not supported
2012void ImageDrawPixel(Image *dst, int x, int y, Color color)
2013{
2014 // Security check to avoid program crash
2015 if ((dst->data == NULL) || (x < 0) || (x >= dst->width) || (y < 0) || (y >= dst->height)) return;
2016
2017 switch (dst->format)
2018 {
2019 case UNCOMPRESSED_GRAYSCALE:
2020 {
2021 // NOTE: Calculate grayscale equivalent color
2022 Vector3 coln = { (float)color.r/255.0f, (float)color.g/255.0f, (float)color.b/255.0f };
2023 unsigned char gray = (unsigned char)((coln.x*0.299f + coln.y*0.587f + coln.z*0.114f)*255.0f);
2024
2025 ((unsigned char *)dst->data)[y*dst->width + x] = gray;
2026
2027 } break;
2028 case UNCOMPRESSED_GRAY_ALPHA:
2029 {
2030 // NOTE: Calculate grayscale equivalent color
2031 Vector3 coln = { (float)color.r/255.0f, (float)color.g/255.0f, (float)color.b/255.0f };
2032 unsigned char gray = (unsigned char)((coln.x*0.299f + coln.y*0.587f + coln.z*0.114f)*255.0f);
2033
2034 ((unsigned char *)dst->data)[(y*dst->width + x)*2] = gray;
2035 ((unsigned char *)dst->data)[(y*dst->width + x)*2 + 1] = color.a;
2036
2037 } break;
2038 case UNCOMPRESSED_R5G6B5:
2039 {
2040 // NOTE: Calculate R5G6B5 equivalent color
2041 Vector3 coln = { (float)color.r/255.0f, (float)color.g/255.0f, (float)color.b/255.0f };
2042
2043 unsigned char r = (unsigned char)(round(coln.x*31.0f));
2044 unsigned char g = (unsigned char)(round(coln.y*63.0f));
2045 unsigned char b = (unsigned char)(round(coln.z*31.0f));
2046
2047 ((unsigned short *)dst->data)[y*dst->width + x] = (unsigned short)r << 11 | (unsigned short)g << 5 | (unsigned short)b;
2048
2049 } break;
2050 case UNCOMPRESSED_R5G5B5A1:
2051 {
2052 #define PIXEL_ALPHA_THRESHOLD 50
2053
2054 // NOTE: Calculate R5G5B5A1 equivalent color
2055 Vector4 coln = { (float)color.r/255.0f, (float)color.g/255.0f, (float)color.b/255.0f, (float)color.a/255.0f };
2056
2057 unsigned char r = (unsigned char)(round(coln.x*31.0f));
2058 unsigned char g = (unsigned char)(round(coln.y*31.0f));
2059 unsigned char b = (unsigned char)(round(coln.z*31.0f));
2060 unsigned char a = (coln.w > ((float)PIXEL_ALPHA_THRESHOLD/255.0f))? 1 : 0;;
2061
2062 ((unsigned short *)dst->data)[y*dst->width + x] = (unsigned short)r << 11 | (unsigned short)g << 6 | (unsigned short)b << 1 | (unsigned short)a;
2063
2064 } break;
2065 case UNCOMPRESSED_R4G4B4A4:
2066 {
2067 // NOTE: Calculate R5G5B5A1 equivalent color
2068 Vector4 coln = { (float)color.r/255.0f, (float)color.g/255.0f, (float)color.b/255.0f, (float)color.a/255.0f };
2069
2070 unsigned char r = (unsigned char)(round(coln.x*15.0f));
2071 unsigned char g = (unsigned char)(round(coln.y*15.0f));
2072 unsigned char b = (unsigned char)(round(coln.z*15.0f));
2073 unsigned char a = (unsigned char)(round(coln.w*15.0f));
2074
2075 ((unsigned short *)dst->data)[y*dst->width + x] = (unsigned short)r << 12 | (unsigned short)g << 8 | (unsigned short)b << 4 | (unsigned short)a;
2076
2077 } break;
2078 case UNCOMPRESSED_R8G8B8:
2079 {
2080 ((unsigned char *)dst->data)[(y*dst->width + x)*3] = color.r;
2081 ((unsigned char *)dst->data)[(y*dst->width + x)*3 + 1] = color.g;
2082 ((unsigned char *)dst->data)[(y*dst->width + x)*3 + 2] = color.b;
2083
2084 } break;
2085 case UNCOMPRESSED_R8G8B8A8:
2086 {
2087 ((unsigned char *)dst->data)[(y*dst->width + x)*4] = color.r;
2088 ((unsigned char *)dst->data)[(y*dst->width + x)*4 + 1] = color.g;
2089 ((unsigned char *)dst->data)[(y*dst->width + x)*4 + 2] = color.b;
2090 ((unsigned char *)dst->data)[(y*dst->width + x)*4 + 3] = color.a;
2091
2092 } break;
2093 case UNCOMPRESSED_R32:
2094 {
2095 // NOTE: Calculate grayscale equivalent color (normalized to 32bit)
2096 Vector3 coln = { (float)color.r/255.0f, (float)color.g/255.0f, (float)color.b/255.0f };
2097
2098 ((float *)dst->data)[y*dst->width + x] = coln.x*0.299f + coln.y*0.587f + coln.z*0.114f;
2099
2100 } break;
2101 case UNCOMPRESSED_R32G32B32:
2102 {
2103 // NOTE: Calculate R32G32B32 equivalent color (normalized to 32bit)
2104 Vector3 coln = { (float)color.r/255.0f, (float)color.g/255.0f, (float)color.b/255.0f };
2105
2106 ((float *)dst->data)[(y*dst->width + x)*3] = coln.x;
2107 ((float *)dst->data)[(y*dst->width + x)*3 + 1] = coln.y;
2108 ((float *)dst->data)[(y*dst->width + x)*3 + 2] = coln.z;
2109 } break;
2110 case UNCOMPRESSED_R32G32B32A32:
2111 {
2112 // NOTE: Calculate R32G32B32A32 equivalent color (normalized to 32bit)
2113 Vector4 coln = { (float)color.r/255.0f, (float)color.g/255.0f, (float)color.b/255.0f, (float)color.a/255.0f };
2114
2115 ((float *)dst->data)[(y*dst->width + x)*4] = coln.x;
2116 ((float *)dst->data)[(y*dst->width + x)*4 + 1] = coln.y;
2117 ((float *)dst->data)[(y*dst->width + x)*4 + 2] = coln.z;
2118 ((float *)dst->data)[(y*dst->width + x)*4 + 3] = coln.w;
2119
2120 } break;
2121 default: break;
2122 }
2123}
2124
2125// Draw pixel within an image (Vector version)
2126void ImageDrawPixelV(Image *dst, Vector2 position, Color color)
2127{
2128 ImageDrawPixel(dst, (int)position.x, (int)position.y, color);
2129}
2130
2131// Draw circle within an image
2132void ImageDrawCircle(Image *dst, int centerX, int centerY, int radius, Color color)
2133{
2134 int x = 0, y = radius;
2135 int decesionParameter = 3 - 2*radius;
2136
2137 while (y >= x)
2138 {
2139 ImageDrawPixel(dst, centerX + x, centerY + y, color);
2140 ImageDrawPixel(dst, centerX - x, centerY + y, color);
2141 ImageDrawPixel(dst, centerX + x, centerY - y, color);
2142 ImageDrawPixel(dst, centerX - x, centerY - y, color);
2143 ImageDrawPixel(dst, centerX + y, centerY + x, color);
2144 ImageDrawPixel(dst, centerX - y, centerY + x, color);
2145 ImageDrawPixel(dst, centerX + y, centerY - x, color);
2146 ImageDrawPixel(dst, centerX - y, centerY - x, color);
2147 x++;
2148
2149 if (decesionParameter > 0)
2150 {
2151 y--;
2152 decesionParameter = decesionParameter + 4*(x - y) + 10;
2153 }
2154 else decesionParameter = decesionParameter + 4*x + 6;
2155 }
2156}
2157
2158// Draw circle within an image (Vector version)
2159void ImageDrawCircleV(Image *dst, Vector2 center, int radius, Color color)
2160{
2161 ImageDrawCircle(dst, (int)center.x, (int)center.y, radius, color);
2162}
2163
2164// Draw line within an image
2165void ImageDrawLine(Image *dst, int startPosX, int startPosY, int endPosX, int endPosY, Color color)
2166{
2167 int m = 2*(endPosY - startPosY);
2168 int slopeError = m - (endPosX - startPosX);
2169
2170 for (int x = startPosX, y = startPosY; x <= endPosX; x++)
2171 {
2172 ImageDrawPixel(dst, x, y, color);
2173 slopeError += m;
2174
2175 if (slopeError >= 0)
2176 {
2177 y++;
2178 slopeError -= 2*(endPosX - startPosX);
2179 }
2180 }
2181}
2182
2183// Draw line within an image (Vector version)
2184void ImageDrawLineV(Image *dst, Vector2 start, Vector2 end, Color color)
2185{
2186 ImageDrawLine(dst, (int)start.x, (int)start.y, (int)end.x, (int)end.y, color);
2187}
2188
2189// Draw text (default font) within an image (destination)
2190void ImageDrawText(Image *dst, const char *text, int posX, int posY, int fontSize, Color color)
2191{
2192 Vector2 position = { (float)posX, (float)posY };
2193
2194 // NOTE: For default font, sapcing is set to desired font size / default font size (10)
2195 ImageDrawTextEx(dst, GetFontDefault(), text, position, (float)fontSize, (float)fontSize/10, color);
2196}
2197
2198// Draw text (custom sprite font) within an image (destination)
2199void ImageDrawTextEx(Image *dst, Font font, const char *text, Vector2 position, float fontSize, float spacing, Color tint)
2200{
2201 Image imText = ImageTextEx(font, text, fontSize, spacing, tint);
2202
2203 Rectangle srcRec = { 0.0f, 0.0f, (float)imText.width, (float)imText.height };
2204 Rectangle dstRec = { position.x, position.y, (float)imText.width, (float)imText.height };
2205
2206 ImageDraw(dst, imText, srcRec, dstRec, WHITE);
2207
2208 UnloadImage(imText);
2209}
2210
2211// Flip image vertically
2212void ImageFlipVertical(Image *image)
2213{
2214 // Security check to avoid program crash
2215 if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return;
2216
2217 Color *srcPixels = GetImageData(*image);
2218 Color *dstPixels = (Color *)RL_MALLOC(image->width*image->height*sizeof(Color));
2219
2220 for (int y = 0; y < image->height; y++)
2221 {
2222 for (int x = 0; x < image->width; x++)
2223 {
2224 dstPixels[y*image->width + x] = srcPixels[(image->height - 1 - y)*image->width + x];
2225 }
2226 }
2227
2228 Image processed = LoadImageEx(dstPixels, image->width, image->height);
2229 ImageFormat(&processed, image->format);
2230 UnloadImage(*image);
2231
2232 RL_FREE(srcPixels);
2233 RL_FREE(dstPixels);
2234
2235 image->data = processed.data;
2236}
2237
2238// Flip image horizontally
2239void ImageFlipHorizontal(Image *image)
2240{
2241 // Security check to avoid program crash
2242 if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return;
2243
2244 Color *srcPixels = GetImageData(*image);
2245 Color *dstPixels = (Color *)RL_MALLOC(image->width*image->height*sizeof(Color));
2246
2247 for (int y = 0; y < image->height; y++)
2248 {
2249 for (int x = 0; x < image->width; x++)
2250 {
2251 dstPixels[y*image->width + x] = srcPixels[y*image->width + (image->width - 1 - x)];
2252 }
2253 }
2254
2255 Image processed = LoadImageEx(dstPixels, image->width, image->height);
2256 ImageFormat(&processed, image->format);
2257 UnloadImage(*image);
2258
2259 RL_FREE(srcPixels);
2260 RL_FREE(dstPixels);
2261
2262 image->data = processed.data;
2263}
2264
2265// Rotate image clockwise 90deg
2266void ImageRotateCW(Image *image)
2267{
2268 // Security check to avoid program crash
2269 if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return;
2270
2271 Color *srcPixels = GetImageData(*image);
2272 Color *rotPixels = (Color *)RL_MALLOC(image->width*image->height*sizeof(Color));
2273
2274 for (int y = 0; y < image->height; y++)
2275 {
2276 for (int x = 0; x < image->width; x++)
2277 {
2278 rotPixels[x*image->height + (image->height - y - 1)] = srcPixels[y*image->width + x];
2279 }
2280 }
2281
2282 Image processed = LoadImageEx(rotPixels, image->height, image->width);
2283 ImageFormat(&processed, image->format);
2284 UnloadImage(*image);
2285
2286 RL_FREE(srcPixels);
2287 RL_FREE(rotPixels);
2288
2289 image->data = processed.data;
2290 image->width = processed.width;
2291 image->height = processed.height;
2292}
2293
2294// Rotate image counter-clockwise 90deg
2295void ImageRotateCCW(Image *image)
2296{
2297 // Security check to avoid program crash
2298 if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return;
2299
2300 Color *srcPixels = GetImageData(*image);
2301 Color *rotPixels = (Color *)RL_MALLOC(image->width*image->height*sizeof(Color));
2302
2303 for (int y = 0; y < image->height; y++)
2304 {
2305 for (int x = 0; x < image->width; x++)
2306 {
2307 rotPixels[x*image->height + y] = srcPixels[y*image->width + (image->width - x - 1)];
2308 }
2309 }
2310
2311 Image processed = LoadImageEx(rotPixels, image->height, image->width);
2312 ImageFormat(&processed, image->format);
2313 UnloadImage(*image);
2314
2315 RL_FREE(srcPixels);
2316 RL_FREE(rotPixels);
2317
2318 image->data = processed.data;
2319 image->width = processed.width;
2320 image->height = processed.height;
2321}
2322
2323// Modify image color: tint
2324void ImageColorTint(Image *image, Color color)
2325{
2326 // Security check to avoid program crash
2327 if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return;
2328
2329 Color *pixels = GetImageData(*image);
2330
2331 float cR = (float)color.r/255;
2332 float cG = (float)color.g/255;
2333 float cB = (float)color.b/255;
2334 float cA = (float)color.a/255;
2335
2336 for (int y = 0; y < image->height; y++)
2337 {
2338 for (int x = 0; x < image->width; x++)
2339 {
2340 int index = y * image->width + x;
2341 unsigned char r = 255*((float)pixels[index].r/255*cR);
2342 unsigned char g = 255*((float)pixels[index].g/255*cG);
2343 unsigned char b = 255*((float)pixels[index].b/255*cB);
2344 unsigned char a = 255*((float)pixels[index].a/255*cA);
2345
2346 pixels[y*image->width + x].r = r;
2347 pixels[y*image->width + x].g = g;
2348 pixels[y*image->width + x].b = b;
2349 pixels[y*image->width + x].a = a;
2350 }
2351 }
2352
2353 Image processed = LoadImageEx(pixels, image->width, image->height);
2354 ImageFormat(&processed, image->format);
2355 UnloadImage(*image);
2356 RL_FREE(pixels);
2357
2358 image->data = processed.data;
2359}
2360
2361// Modify image color: invert
2362void ImageColorInvert(Image *image)
2363{
2364 // Security check to avoid program crash
2365 if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return;
2366
2367 Color *pixels = GetImageData(*image);
2368
2369 for (int y = 0; y < image->height; y++)
2370 {
2371 for (int x = 0; x < image->width; x++)
2372 {
2373 pixels[y*image->width + x].r = 255 - pixels[y*image->width + x].r;
2374 pixels[y*image->width + x].g = 255 - pixels[y*image->width + x].g;
2375 pixels[y*image->width + x].b = 255 - pixels[y*image->width + x].b;
2376 }
2377 }
2378
2379 Image processed = LoadImageEx(pixels, image->width, image->height);
2380 ImageFormat(&processed, image->format);
2381 UnloadImage(*image);
2382 RL_FREE(pixels);
2383
2384 image->data = processed.data;
2385}
2386
2387// Modify image color: grayscale
2388void ImageColorGrayscale(Image *image)
2389{
2390 ImageFormat(image, UNCOMPRESSED_GRAYSCALE);
2391}
2392
2393// Modify image color: contrast
2394// NOTE: Contrast values between -100 and 100
2395void ImageColorContrast(Image *image, float contrast)
2396{
2397 // Security check to avoid program crash
2398 if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return;
2399
2400 if (contrast < -100) contrast = -100;
2401 if (contrast > 100) contrast = 100;
2402
2403 contrast = (100.0f + contrast)/100.0f;
2404 contrast *= contrast;
2405
2406 Color *pixels = GetImageData(*image);
2407
2408 for (int y = 0; y < image->height; y++)
2409 {
2410 for (int x = 0; x < image->width; x++)
2411 {
2412 float pR = (float)pixels[y*image->width + x].r/255.0f;
2413 pR -= 0.5;
2414 pR *= contrast;
2415 pR += 0.5;
2416 pR *= 255;
2417 if (pR < 0) pR = 0;
2418 if (pR > 255) pR = 255;
2419
2420 float pG = (float)pixels[y*image->width + x].g/255.0f;
2421 pG -= 0.5;
2422 pG *= contrast;
2423 pG += 0.5;
2424 pG *= 255;
2425 if (pG < 0) pG = 0;
2426 if (pG > 255) pG = 255;
2427
2428 float pB = (float)pixels[y*image->width + x].b/255.0f;
2429 pB -= 0.5;
2430 pB *= contrast;
2431 pB += 0.5;
2432 pB *= 255;
2433 if (pB < 0) pB = 0;
2434 if (pB > 255) pB = 255;
2435
2436 pixels[y*image->width + x].r = (unsigned char)pR;
2437 pixels[y*image->width + x].g = (unsigned char)pG;
2438 pixels[y*image->width + x].b = (unsigned char)pB;
2439 }
2440 }
2441
2442 Image processed = LoadImageEx(pixels, image->width, image->height);
2443 ImageFormat(&processed, image->format);
2444 UnloadImage(*image);
2445 RL_FREE(pixels);
2446
2447 image->data = processed.data;
2448}
2449
2450// Modify image color: brightness
2451// NOTE: Brightness values between -255 and 255
2452void ImageColorBrightness(Image *image, int brightness)
2453{
2454 // Security check to avoid program crash
2455 if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return;
2456
2457 if (brightness < -255) brightness = -255;
2458 if (brightness > 255) brightness = 255;
2459
2460 Color *pixels = GetImageData(*image);
2461
2462 for (int y = 0; y < image->height; y++)
2463 {
2464 for (int x = 0; x < image->width; x++)
2465 {
2466 int cR = pixels[y*image->width + x].r + brightness;
2467 int cG = pixels[y*image->width + x].g + brightness;
2468 int cB = pixels[y*image->width + x].b + brightness;
2469
2470 if (cR < 0) cR = 1;
2471 if (cR > 255) cR = 255;
2472
2473 if (cG < 0) cG = 1;
2474 if (cG > 255) cG = 255;
2475
2476 if (cB < 0) cB = 1;
2477 if (cB > 255) cB = 255;
2478
2479 pixels[y*image->width + x].r = (unsigned char)cR;
2480 pixels[y*image->width + x].g = (unsigned char)cG;
2481 pixels[y*image->width + x].b = (unsigned char)cB;
2482 }
2483 }
2484
2485 Image processed = LoadImageEx(pixels, image->width, image->height);
2486 ImageFormat(&processed, image->format);
2487 UnloadImage(*image);
2488 RL_FREE(pixels);
2489
2490 image->data = processed.data;
2491}
2492
2493// Modify image color: replace color
2494void ImageColorReplace(Image *image, Color color, Color replace)
2495{
2496 // Security check to avoid program crash
2497 if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return;
2498
2499 Color *pixels = GetImageData(*image);
2500
2501 for (int y = 0; y < image->height; y++)
2502 {
2503 for (int x = 0; x < image->width; x++)
2504 {
2505 if ((pixels[y*image->width + x].r == color.r) &&
2506 (pixels[y*image->width + x].g == color.g) &&
2507 (pixels[y*image->width + x].b == color.b) &&
2508 (pixels[y*image->width + x].a == color.a))
2509 {
2510 pixels[y*image->width + x].r = replace.r;
2511 pixels[y*image->width + x].g = replace.g;
2512 pixels[y*image->width + x].b = replace.b;
2513 pixels[y*image->width + x].a = replace.a;
2514 }
2515 }
2516 }
2517
2518 Image processed = LoadImageEx(pixels, image->width, image->height);
2519 ImageFormat(&processed, image->format);
2520 UnloadImage(*image);
2521 RL_FREE(pixels);
2522
2523 image->data = processed.data;
2524}
2525#endif // SUPPORT_IMAGE_MANIPULATION
2526
2527// Generate image: plain color
2528Image GenImageColor(int width, int height, Color color)
2529{
2530 Color *pixels = (Color *)RL_CALLOC(width*height, sizeof(Color));
2531
2532 for (int i = 0; i < width*height; i++) pixels[i] = color;
2533
2534 Image image = LoadImageEx(pixels, width, height);
2535
2536 RL_FREE(pixels);
2537
2538 return image;
2539}
2540
2541#if defined(SUPPORT_IMAGE_GENERATION)
2542// Generate image: vertical gradient
2543Image GenImageGradientV(int width, int height, Color top, Color bottom)
2544{
2545 Color *pixels = (Color *)RL_MALLOC(width*height*sizeof(Color));
2546
2547 for (int j = 0; j < height; j++)
2548 {
2549 float factor = (float)j/(float)height;
2550 for (int i = 0; i < width; i++)
2551 {
2552 pixels[j*width + i].r = (int)((float)bottom.r*factor + (float)top.r*(1.f - factor));
2553 pixels[j*width + i].g = (int)((float)bottom.g*factor + (float)top.g*(1.f - factor));
2554 pixels[j*width + i].b = (int)((float)bottom.b*factor + (float)top.b*(1.f - factor));
2555 pixels[j*width + i].a = (int)((float)bottom.a*factor + (float)top.a*(1.f - factor));
2556 }
2557 }
2558
2559 Image image = LoadImageEx(pixels, width, height);
2560 RL_FREE(pixels);
2561
2562 return image;
2563}
2564
2565// Generate image: horizontal gradient
2566Image GenImageGradientH(int width, int height, Color left, Color right)
2567{
2568 Color *pixels = (Color *)RL_MALLOC(width*height*sizeof(Color));
2569
2570 for (int i = 0; i < width; i++)
2571 {
2572 float factor = (float)i/(float)width;
2573 for (int j = 0; j < height; j++)
2574 {
2575 pixels[j*width + i].r = (int)((float)right.r*factor + (float)left.r*(1.f - factor));
2576 pixels[j*width + i].g = (int)((float)right.g*factor + (float)left.g*(1.f - factor));
2577 pixels[j*width + i].b = (int)((float)right.b*factor + (float)left.b*(1.f - factor));
2578 pixels[j*width + i].a = (int)((float)right.a*factor + (float)left.a*(1.f - factor));
2579 }
2580 }
2581
2582 Image image = LoadImageEx(pixels, width, height);
2583 RL_FREE(pixels);
2584
2585 return image;
2586}
2587
2588// Generate image: radial gradient
2589Image GenImageGradientRadial(int width, int height, float density, Color inner, Color outer)
2590{
2591 Color *pixels = (Color *)RL_MALLOC(width*height*sizeof(Color));
2592 float radius = (width < height)? (float)width/2.0f : (float)height/2.0f;
2593
2594 float centerX = (float)width/2.0f;
2595 float centerY = (float)height/2.0f;
2596
2597 for (int y = 0; y < height; y++)
2598 {
2599 for (int x = 0; x < width; x++)
2600 {
2601 float dist = hypotf((float)x - centerX, (float)y - centerY);
2602 float factor = (dist - radius*density)/(radius*(1.0f - density));
2603
2604 factor = (float)fmax(factor, 0.f);
2605 factor = (float)fmin(factor, 1.f); // dist can be bigger than radius so we have to check
2606
2607 pixels[y*width + x].r = (int)((float)outer.r*factor + (float)inner.r*(1.0f - factor));
2608 pixels[y*width + x].g = (int)((float)outer.g*factor + (float)inner.g*(1.0f - factor));
2609 pixels[y*width + x].b = (int)((float)outer.b*factor + (float)inner.b*(1.0f - factor));
2610 pixels[y*width + x].a = (int)((float)outer.a*factor + (float)inner.a*(1.0f - factor));
2611 }
2612 }
2613
2614 Image image = LoadImageEx(pixels, width, height);
2615 RL_FREE(pixels);
2616
2617 return image;
2618}
2619
2620// Generate image: checked
2621Image GenImageChecked(int width, int height, int checksX, int checksY, Color col1, Color col2)
2622{
2623 Color *pixels = (Color *)RL_MALLOC(width*height*sizeof(Color));
2624
2625 for (int y = 0; y < height; y++)
2626 {
2627 for (int x = 0; x < width; x++)
2628 {
2629 if ((x/checksX + y/checksY)%2 == 0) pixels[y*width + x] = col1;
2630 else pixels[y*width + x] = col2;
2631 }
2632 }
2633
2634 Image image = LoadImageEx(pixels, width, height);
2635 RL_FREE(pixels);
2636
2637 return image;
2638}
2639
2640// Generate image: white noise
2641Image GenImageWhiteNoise(int width, int height, float factor)
2642{
2643 Color *pixels = (Color *)RL_MALLOC(width*height*sizeof(Color));
2644
2645 for (int i = 0; i < width*height; i++)
2646 {
2647 if (GetRandomValue(0, 99) < (int)(factor*100.0f)) pixels[i] = WHITE;
2648 else pixels[i] = BLACK;
2649 }
2650
2651 Image image = LoadImageEx(pixels, width, height);
2652 RL_FREE(pixels);
2653
2654 return image;
2655}
2656
2657// Generate image: perlin noise
2658Image GenImagePerlinNoise(int width, int height, int offsetX, int offsetY, float scale)
2659{
2660 Color *pixels = (Color *)RL_MALLOC(width*height*sizeof(Color));
2661
2662 for (int y = 0; y < height; y++)
2663 {
2664 for (int x = 0; x < width; x++)
2665 {
2666 float nx = (float)(x + offsetX)*scale/(float)width;
2667 float ny = (float)(y + offsetY)*scale/(float)height;
2668
2669 // Typical values to start playing with:
2670 // lacunarity = ~2.0 -- spacing between successive octaves (use exactly 2.0 for wrapping output)
2671 // gain = 0.5 -- relative weighting applied to each successive octave
2672 // octaves = 6 -- number of "octaves" of noise3() to sum
2673
2674 // NOTE: We need to translate the data from [-1..1] to [0..1]
2675 float p = (stb_perlin_fbm_noise3(nx, ny, 1.0f, 2.0f, 0.5f, 6) + 1.0f)/2.0f;
2676
2677 int intensity = (int)(p*255.0f);
2678 pixels[y*width + x] = (Color){intensity, intensity, intensity, 255};
2679 }
2680 }
2681
2682 Image image = LoadImageEx(pixels, width, height);
2683 RL_FREE(pixels);
2684
2685 return image;
2686}
2687
2688// Generate image: cellular algorithm. Bigger tileSize means bigger cells
2689Image GenImageCellular(int width, int height, int tileSize)
2690{
2691 Color *pixels = (Color *)RL_MALLOC(width*height*sizeof(Color));
2692
2693 int seedsPerRow = width/tileSize;
2694 int seedsPerCol = height/tileSize;
2695 int seedsCount = seedsPerRow * seedsPerCol;
2696
2697 Vector2 *seeds = (Vector2 *)RL_MALLOC(seedsCount*sizeof(Vector2));
2698
2699 for (int i = 0; i < seedsCount; i++)
2700 {
2701 int y = (i/seedsPerRow)*tileSize + GetRandomValue(0, tileSize - 1);
2702 int x = (i%seedsPerRow)*tileSize + GetRandomValue(0, tileSize - 1);
2703 seeds[i] = (Vector2){ (float)x, (float)y};
2704 }
2705
2706 for (int y = 0; y < height; y++)
2707 {
2708 int tileY = y/tileSize;
2709
2710 for (int x = 0; x < width; x++)
2711 {
2712 int tileX = x/tileSize;
2713
2714 float minDistance = (float)strtod("Inf", NULL);
2715
2716 // Check all adjacent tiles
2717 for (int i = -1; i < 2; i++)
2718 {
2719 if ((tileX + i < 0) || (tileX + i >= seedsPerRow)) continue;
2720
2721 for (int j = -1; j < 2; j++)
2722 {
2723 if ((tileY + j < 0) || (tileY + j >= seedsPerCol)) continue;
2724
2725 Vector2 neighborSeed = seeds[(tileY + j)*seedsPerRow + tileX + i];
2726
2727 float dist = (float)hypot(x - (int)neighborSeed.x, y - (int)neighborSeed.y);
2728 minDistance = (float)fmin(minDistance, dist);
2729 }
2730 }
2731
2732 // I made this up but it seems to give good results at all tile sizes
2733 int intensity = (int)(minDistance*256.0f/tileSize);
2734 if (intensity > 255) intensity = 255;
2735
2736 pixels[y*width + x] = (Color){ intensity, intensity, intensity, 255 };
2737 }
2738 }
2739
2740 RL_FREE(seeds);
2741
2742 Image image = LoadImageEx(pixels, width, height);
2743 RL_FREE(pixels);
2744
2745 return image;
2746}
2747#endif // SUPPORT_IMAGE_GENERATION
2748
2749// Generate GPU mipmaps for a texture
2750void GenTextureMipmaps(Texture2D *texture)
2751{
2752 // NOTE: NPOT textures support check inside function
2753 // On WebGL (OpenGL ES 2.0) NPOT textures support is limited
2754 rlGenerateMipmaps(texture);
2755}
2756
2757// Set texture scaling filter mode
2758void SetTextureFilter(Texture2D texture, int filterMode)
2759{
2760 switch (filterMode)
2761 {
2762 case FILTER_POINT:
2763 {
2764 if (texture.mipmaps > 1)
2765 {
2766 // RL_FILTER_MIP_NEAREST - tex filter: POINT, mipmaps filter: POINT (sharp switching between mipmaps)
2767 rlTextureParameters(texture.id, RL_TEXTURE_MIN_FILTER, RL_FILTER_MIP_NEAREST);
2768
2769 // RL_FILTER_NEAREST - tex filter: POINT (no filter), no mipmaps
2770 rlTextureParameters(texture.id, RL_TEXTURE_MAG_FILTER, RL_FILTER_NEAREST);
2771 }
2772 else
2773 {
2774 // RL_FILTER_NEAREST - tex filter: POINT (no filter), no mipmaps
2775 rlTextureParameters(texture.id, RL_TEXTURE_MIN_FILTER, RL_FILTER_NEAREST);
2776 rlTextureParameters(texture.id, RL_TEXTURE_MAG_FILTER, RL_FILTER_NEAREST);
2777 }
2778 } break;
2779 case FILTER_BILINEAR:
2780 {
2781 if (texture.mipmaps > 1)
2782 {
2783 // RL_FILTER_LINEAR_MIP_NEAREST - tex filter: BILINEAR, mipmaps filter: POINT (sharp switching between mipmaps)
2784 // Alternative: RL_FILTER_NEAREST_MIP_LINEAR - tex filter: POINT, mipmaps filter: BILINEAR (smooth transition between mipmaps)
2785 rlTextureParameters(texture.id, RL_TEXTURE_MIN_FILTER, RL_FILTER_LINEAR_MIP_NEAREST);
2786
2787 // RL_FILTER_LINEAR - tex filter: BILINEAR, no mipmaps
2788 rlTextureParameters(texture.id, RL_TEXTURE_MAG_FILTER, RL_FILTER_LINEAR);
2789 }
2790 else
2791 {
2792 // RL_FILTER_LINEAR - tex filter: BILINEAR, no mipmaps
2793 rlTextureParameters(texture.id, RL_TEXTURE_MIN_FILTER, RL_FILTER_LINEAR);
2794 rlTextureParameters(texture.id, RL_TEXTURE_MAG_FILTER, RL_FILTER_LINEAR);
2795 }
2796 } break;
2797 case FILTER_TRILINEAR:
2798 {
2799 if (texture.mipmaps > 1)
2800 {
2801 // RL_FILTER_MIP_LINEAR - tex filter: BILINEAR, mipmaps filter: BILINEAR (smooth transition between mipmaps)
2802 rlTextureParameters(texture.id, RL_TEXTURE_MIN_FILTER, RL_FILTER_MIP_LINEAR);
2803
2804 // RL_FILTER_LINEAR - tex filter: BILINEAR, no mipmaps
2805 rlTextureParameters(texture.id, RL_TEXTURE_MAG_FILTER, RL_FILTER_LINEAR);
2806 }
2807 else
2808 {
2809 TRACELOG(LOG_WARNING, "TEXTURE: [ID %i] No mipmaps available for TRILINEAR texture filtering", texture.id);
2810
2811 // RL_FILTER_LINEAR - tex filter: BILINEAR, no mipmaps
2812 rlTextureParameters(texture.id, RL_TEXTURE_MIN_FILTER, RL_FILTER_LINEAR);
2813 rlTextureParameters(texture.id, RL_TEXTURE_MAG_FILTER, RL_FILTER_LINEAR);
2814 }
2815 } break;
2816 case FILTER_ANISOTROPIC_4X: rlTextureParameters(texture.id, RL_TEXTURE_ANISOTROPIC_FILTER, 4); break;
2817 case FILTER_ANISOTROPIC_8X: rlTextureParameters(texture.id, RL_TEXTURE_ANISOTROPIC_FILTER, 8); break;
2818 case FILTER_ANISOTROPIC_16X: rlTextureParameters(texture.id, RL_TEXTURE_ANISOTROPIC_FILTER, 16); break;
2819 default: break;
2820 }
2821}
2822
2823// Set texture wrapping mode
2824void SetTextureWrap(Texture2D texture, int wrapMode)
2825{
2826 switch (wrapMode)
2827 {
2828 case WRAP_REPEAT:
2829 {
2830 rlTextureParameters(texture.id, RL_TEXTURE_WRAP_S, RL_WRAP_REPEAT);
2831 rlTextureParameters(texture.id, RL_TEXTURE_WRAP_T, RL_WRAP_REPEAT);
2832 } break;
2833 case WRAP_CLAMP:
2834 {
2835 rlTextureParameters(texture.id, RL_TEXTURE_WRAP_S, RL_WRAP_CLAMP);
2836 rlTextureParameters(texture.id, RL_TEXTURE_WRAP_T, RL_WRAP_CLAMP);
2837 } break;
2838 case WRAP_MIRROR_REPEAT:
2839 {
2840 rlTextureParameters(texture.id, RL_TEXTURE_WRAP_S, RL_WRAP_MIRROR_REPEAT);
2841 rlTextureParameters(texture.id, RL_TEXTURE_WRAP_T, RL_WRAP_MIRROR_REPEAT);
2842 } break;
2843 case WRAP_MIRROR_CLAMP:
2844 {
2845 rlTextureParameters(texture.id, RL_TEXTURE_WRAP_S, RL_WRAP_MIRROR_CLAMP);
2846 rlTextureParameters(texture.id, RL_TEXTURE_WRAP_T, RL_WRAP_MIRROR_CLAMP);
2847 } break;
2848 default: break;
2849 }
2850}
2851
2852// Draw a Texture2D
2853void DrawTexture(Texture2D texture, int posX, int posY, Color tint)
2854{
2855 DrawTextureEx(texture, (Vector2){ (float)posX, (float)posY }, 0.0f, 1.0f, tint);
2856}
2857
2858// Draw a Texture2D with position defined as Vector2
2859void DrawTextureV(Texture2D texture, Vector2 position, Color tint)
2860{
2861 DrawTextureEx(texture, position, 0, 1.0f, tint);
2862}
2863
2864// Draw a Texture2D with extended parameters
2865void DrawTextureEx(Texture2D texture, Vector2 position, float rotation, float scale, Color tint)
2866{
2867 Rectangle sourceRec = { 0.0f, 0.0f, (float)texture.width, (float)texture.height };
2868 Rectangle destRec = { position.x, position.y, (float)texture.width*scale, (float)texture.height*scale };
2869 Vector2 origin = { 0.0f, 0.0f };
2870
2871 DrawTexturePro(texture, sourceRec, destRec, origin, rotation, tint);
2872}
2873
2874// Draw a part of a texture (defined by a rectangle)
2875void DrawTextureRec(Texture2D texture, Rectangle sourceRec, Vector2 position, Color tint)
2876{
2877 Rectangle destRec = { position.x, position.y, fabsf(sourceRec.width), fabsf(sourceRec.height) };
2878 Vector2 origin = { 0.0f, 0.0f };
2879
2880 DrawTexturePro(texture, sourceRec, destRec, origin, 0.0f, tint);
2881}
2882
2883// Draw texture quad with tiling and offset parameters
2884// NOTE: Tiling and offset should be provided considering normalized texture values [0..1]
2885// i.e tiling = { 1.0f, 1.0f } refers to all texture, offset = { 0.5f, 0.5f } moves texture origin to center
2886void DrawTextureQuad(Texture2D texture, Vector2 tiling, Vector2 offset, Rectangle quad, Color tint)
2887{
2888 Rectangle source = { offset.x*texture.width, offset.y*texture.height, tiling.x*texture.width, tiling.y*texture.height };
2889 Vector2 origin = { 0.0f, 0.0f };
2890
2891 DrawTexturePro(texture, source, quad, origin, 0.0f, tint);
2892}
2893
2894// Draw a part of a texture (defined by a rectangle) with 'pro' parameters
2895// NOTE: origin is relative to destination rectangle size
2896void DrawTexturePro(Texture2D texture, Rectangle sourceRec, Rectangle destRec, Vector2 origin, float rotation, Color tint)
2897{
2898 // Check if texture is valid
2899 if (texture.id > 0)
2900 {
2901 float width = (float)texture.width;
2902 float height = (float)texture.height;
2903
2904 bool flipX = false;
2905
2906 if (sourceRec.width < 0) { flipX = true; sourceRec.width *= -1; }
2907 if (sourceRec.height < 0) sourceRec.y -= sourceRec.height;
2908
2909 rlEnableTexture(texture.id);
2910
2911 rlPushMatrix();
2912 rlTranslatef(destRec.x, destRec.y, 0.0f);
2913 rlRotatef(rotation, 0.0f, 0.0f, 1.0f);
2914 rlTranslatef(-origin.x, -origin.y, 0.0f);
2915
2916 rlBegin(RL_QUADS);
2917 rlColor4ub(tint.r, tint.g, tint.b, tint.a);
2918 rlNormal3f(0.0f, 0.0f, 1.0f); // Normal vector pointing towards viewer
2919
2920 // Bottom-left corner for texture and quad
2921 if (flipX) rlTexCoord2f((sourceRec.x + sourceRec.width)/width, sourceRec.y/height);
2922 else rlTexCoord2f(sourceRec.x/width, sourceRec.y/height);
2923 rlVertex2f(0.0f, 0.0f);
2924
2925 // Bottom-right corner for texture and quad
2926 if (flipX) rlTexCoord2f((sourceRec.x + sourceRec.width)/width, (sourceRec.y + sourceRec.height)/height);
2927 else rlTexCoord2f(sourceRec.x/width, (sourceRec.y + sourceRec.height)/height);
2928 rlVertex2f(0.0f, destRec.height);
2929
2930 // Top-right corner for texture and quad
2931 if (flipX) rlTexCoord2f(sourceRec.x/width, (sourceRec.y + sourceRec.height)/height);
2932 else rlTexCoord2f((sourceRec.x + sourceRec.width)/width, (sourceRec.y + sourceRec.height)/height);
2933 rlVertex2f(destRec.width, destRec.height);
2934
2935 // Top-left corner for texture and quad
2936 if (flipX) rlTexCoord2f(sourceRec.x/width, sourceRec.y/height);
2937 else rlTexCoord2f((sourceRec.x + sourceRec.width)/width, sourceRec.y/height);
2938 rlVertex2f(destRec.width, 0.0f);
2939 rlEnd();
2940 rlPopMatrix();
2941
2942 rlDisableTexture();
2943 }
2944}
2945
2946// Draws a texture (or part of it) that stretches or shrinks nicely using n-patch info
2947void DrawTextureNPatch(Texture2D texture, NPatchInfo nPatchInfo, Rectangle destRec, Vector2 origin, float rotation, Color tint)
2948{
2949 if (texture.id > 0)
2950 {
2951 float width = (float)texture.width;
2952 float height = (float)texture.height;
2953
2954 float patchWidth = (destRec.width <= 0.0f)? 0.0f : destRec.width;
2955 float patchHeight = (destRec.height <= 0.0f)? 0.0f : destRec.height;
2956
2957 if (nPatchInfo.sourceRec.width < 0) nPatchInfo.sourceRec.x -= nPatchInfo.sourceRec.width;
2958 if (nPatchInfo.sourceRec.height < 0) nPatchInfo.sourceRec.y -= nPatchInfo.sourceRec.height;
2959 if (nPatchInfo.type == NPT_3PATCH_HORIZONTAL) patchHeight = nPatchInfo.sourceRec.height;
2960 if (nPatchInfo.type == NPT_3PATCH_VERTICAL) patchWidth = nPatchInfo.sourceRec.width;
2961
2962 bool drawCenter = true;
2963 bool drawMiddle = true;
2964 float leftBorder = (float)nPatchInfo.left;
2965 float topBorder = (float)nPatchInfo.top;
2966 float rightBorder = (float)nPatchInfo.right;
2967 float bottomBorder = (float)nPatchInfo.bottom;
2968
2969 // adjust the lateral (left and right) border widths in case patchWidth < texture.width
2970 if (patchWidth <= (leftBorder + rightBorder) && nPatchInfo.type != NPT_3PATCH_VERTICAL)
2971 {
2972 drawCenter = false;
2973 leftBorder = (leftBorder / (leftBorder + rightBorder)) * patchWidth;
2974 rightBorder = patchWidth - leftBorder;
2975 }
2976 // adjust the lateral (top and bottom) border heights in case patchHeight < texture.height
2977 if (patchHeight <= (topBorder + bottomBorder) && nPatchInfo.type != NPT_3PATCH_HORIZONTAL)
2978 {
2979 drawMiddle = false;
2980 topBorder = (topBorder / (topBorder + bottomBorder)) * patchHeight;
2981 bottomBorder = patchHeight - topBorder;
2982 }
2983
2984 Vector2 vertA, vertB, vertC, vertD;
2985 vertA.x = 0.0f; // outer left
2986 vertA.y = 0.0f; // outer top
2987 vertB.x = leftBorder; // inner left
2988 vertB.y = topBorder; // inner top
2989 vertC.x = patchWidth - rightBorder; // inner right
2990 vertC.y = patchHeight - bottomBorder; // inner bottom
2991 vertD.x = patchWidth; // outer right
2992 vertD.y = patchHeight; // outer bottom
2993
2994 Vector2 coordA, coordB, coordC, coordD;
2995 coordA.x = nPatchInfo.sourceRec.x / width;
2996 coordA.y = nPatchInfo.sourceRec.y / height;
2997 coordB.x = (nPatchInfo.sourceRec.x + leftBorder) / width;
2998 coordB.y = (nPatchInfo.sourceRec.y + topBorder) / height;
2999 coordC.x = (nPatchInfo.sourceRec.x + nPatchInfo.sourceRec.width - rightBorder) / width;
3000 coordC.y = (nPatchInfo.sourceRec.y + nPatchInfo.sourceRec.height - bottomBorder) / height;
3001 coordD.x = (nPatchInfo.sourceRec.x + nPatchInfo.sourceRec.width) / width;
3002 coordD.y = (nPatchInfo.sourceRec.y + nPatchInfo.sourceRec.height) / height;
3003
3004 rlEnableTexture(texture.id);
3005
3006 rlPushMatrix();
3007 rlTranslatef(destRec.x, destRec.y, 0.0f);
3008 rlRotatef(rotation, 0.0f, 0.0f, 1.0f);
3009 rlTranslatef(-origin.x, -origin.y, 0.0f);
3010
3011 rlBegin(RL_QUADS);
3012 rlColor4ub(tint.r, tint.g, tint.b, tint.a);
3013 rlNormal3f(0.0f, 0.0f, 1.0f); // Normal vector pointing towards viewer
3014
3015 if (nPatchInfo.type == NPT_9PATCH)
3016 {
3017 // ------------------------------------------------------------
3018 // TOP-LEFT QUAD
3019 rlTexCoord2f(coordA.x, coordB.y); rlVertex2f(vertA.x, vertB.y); // Bottom-left corner for texture and quad
3020 rlTexCoord2f(coordB.x, coordB.y); rlVertex2f(vertB.x, vertB.y); // Bottom-right corner for texture and quad
3021 rlTexCoord2f(coordB.x, coordA.y); rlVertex2f(vertB.x, vertA.y); // Top-right corner for texture and quad
3022 rlTexCoord2f(coordA.x, coordA.y); rlVertex2f(vertA.x, vertA.y); // Top-left corner for texture and quad
3023 if (drawCenter)
3024 {
3025 // TOP-CENTER QUAD
3026 rlTexCoord2f(coordB.x, coordB.y); rlVertex2f(vertB.x, vertB.y); // Bottom-left corner for texture and quad
3027 rlTexCoord2f(coordC.x, coordB.y); rlVertex2f(vertC.x, vertB.y); // Bottom-right corner for texture and quad
3028 rlTexCoord2f(coordC.x, coordA.y); rlVertex2f(vertC.x, vertA.y); // Top-right corner for texture and quad
3029 rlTexCoord2f(coordB.x, coordA.y); rlVertex2f(vertB.x, vertA.y); // Top-left corner for texture and quad
3030 }
3031 // TOP-RIGHT QUAD
3032 rlTexCoord2f(coordC.x, coordB.y); rlVertex2f(vertC.x, vertB.y); // Bottom-left corner for texture and quad
3033 rlTexCoord2f(coordD.x, coordB.y); rlVertex2f(vertD.x, vertB.y); // Bottom-right corner for texture and quad
3034 rlTexCoord2f(coordD.x, coordA.y); rlVertex2f(vertD.x, vertA.y); // Top-right corner for texture and quad
3035 rlTexCoord2f(coordC.x, coordA.y); rlVertex2f(vertC.x, vertA.y); // Top-left corner for texture and quad
3036 if (drawMiddle)
3037 {
3038 // ------------------------------------------------------------
3039 // MIDDLE-LEFT QUAD
3040 rlTexCoord2f(coordA.x, coordC.y); rlVertex2f(vertA.x, vertC.y); // Bottom-left corner for texture and quad
3041 rlTexCoord2f(coordB.x, coordC.y); rlVertex2f(vertB.x, vertC.y); // Bottom-right corner for texture and quad
3042 rlTexCoord2f(coordB.x, coordB.y); rlVertex2f(vertB.x, vertB.y); // Top-right corner for texture and quad
3043 rlTexCoord2f(coordA.x, coordB.y); rlVertex2f(vertA.x, vertB.y); // Top-left corner for texture and quad
3044 if (drawCenter)
3045 {
3046 // MIDDLE-CENTER QUAD
3047 rlTexCoord2f(coordB.x, coordC.y); rlVertex2f(vertB.x, vertC.y); // Bottom-left corner for texture and quad
3048 rlTexCoord2f(coordC.x, coordC.y); rlVertex2f(vertC.x, vertC.y); // Bottom-right corner for texture and quad
3049 rlTexCoord2f(coordC.x, coordB.y); rlVertex2f(vertC.x, vertB.y); // Top-right corner for texture and quad
3050 rlTexCoord2f(coordB.x, coordB.y); rlVertex2f(vertB.x, vertB.y); // Top-left corner for texture and quad
3051 }
3052
3053 // MIDDLE-RIGHT QUAD
3054 rlTexCoord2f(coordC.x, coordC.y); rlVertex2f(vertC.x, vertC.y); // Bottom-left corner for texture and quad
3055 rlTexCoord2f(coordD.x, coordC.y); rlVertex2f(vertD.x, vertC.y); // Bottom-right corner for texture and quad
3056 rlTexCoord2f(coordD.x, coordB.y); rlVertex2f(vertD.x, vertB.y); // Top-right corner for texture and quad
3057 rlTexCoord2f(coordC.x, coordB.y); rlVertex2f(vertC.x, vertB.y); // Top-left corner for texture and quad
3058 }
3059
3060 // ------------------------------------------------------------
3061 // BOTTOM-LEFT QUAD
3062 rlTexCoord2f(coordA.x, coordD.y); rlVertex2f(vertA.x, vertD.y); // Bottom-left corner for texture and quad
3063 rlTexCoord2f(coordB.x, coordD.y); rlVertex2f(vertB.x, vertD.y); // Bottom-right corner for texture and quad
3064 rlTexCoord2f(coordB.x, coordC.y); rlVertex2f(vertB.x, vertC.y); // Top-right corner for texture and quad
3065 rlTexCoord2f(coordA.x, coordC.y); rlVertex2f(vertA.x, vertC.y); // Top-left corner for texture and quad
3066 if (drawCenter)
3067 {
3068 // BOTTOM-CENTER QUAD
3069 rlTexCoord2f(coordB.x, coordD.y); rlVertex2f(vertB.x, vertD.y); // Bottom-left corner for texture and quad
3070 rlTexCoord2f(coordC.x, coordD.y); rlVertex2f(vertC.x, vertD.y); // Bottom-right corner for texture and quad
3071 rlTexCoord2f(coordC.x, coordC.y); rlVertex2f(vertC.x, vertC.y); // Top-right corner for texture and quad
3072 rlTexCoord2f(coordB.x, coordC.y); rlVertex2f(vertB.x, vertC.y); // Top-left corner for texture and quad
3073 }
3074
3075 // BOTTOM-RIGHT QUAD
3076 rlTexCoord2f(coordC.x, coordD.y); rlVertex2f(vertC.x, vertD.y); // Bottom-left corner for texture and quad
3077 rlTexCoord2f(coordD.x, coordD.y); rlVertex2f(vertD.x, vertD.y); // Bottom-right corner for texture and quad
3078 rlTexCoord2f(coordD.x, coordC.y); rlVertex2f(vertD.x, vertC.y); // Top-right corner for texture and quad
3079 rlTexCoord2f(coordC.x, coordC.y); rlVertex2f(vertC.x, vertC.y); // Top-left corner for texture and quad
3080 }
3081 else if (nPatchInfo.type == NPT_3PATCH_VERTICAL)
3082 {
3083 // TOP QUAD
3084 // -----------------------------------------------------------
3085 // Texture coords Vertices
3086 rlTexCoord2f(coordA.x, coordB.y); rlVertex2f(vertA.x, vertB.y); // Bottom-left corner for texture and quad
3087 rlTexCoord2f(coordD.x, coordB.y); rlVertex2f(vertD.x, vertB.y); // Bottom-right corner for texture and quad
3088 rlTexCoord2f(coordD.x, coordA.y); rlVertex2f(vertD.x, vertA.y); // Top-right corner for texture and quad
3089 rlTexCoord2f(coordA.x, coordA.y); rlVertex2f(vertA.x, vertA.y); // Top-left corner for texture and quad
3090 if (drawCenter)
3091 {
3092 // MIDDLE QUAD
3093 // -----------------------------------------------------------
3094 // Texture coords Vertices
3095 rlTexCoord2f(coordA.x, coordC.y); rlVertex2f(vertA.x, vertC.y); // Bottom-left corner for texture and quad
3096 rlTexCoord2f(coordD.x, coordC.y); rlVertex2f(vertD.x, vertC.y); // Bottom-right corner for texture and quad
3097 rlTexCoord2f(coordD.x, coordB.y); rlVertex2f(vertD.x, vertB.y); // Top-right corner for texture and quad
3098 rlTexCoord2f(coordA.x, coordB.y); rlVertex2f(vertA.x, vertB.y); // Top-left corner for texture and quad
3099 }
3100 // BOTTOM QUAD
3101 // -----------------------------------------------------------
3102 // Texture coords Vertices
3103 rlTexCoord2f(coordA.x, coordD.y); rlVertex2f(vertA.x, vertD.y); // Bottom-left corner for texture and quad
3104 rlTexCoord2f(coordD.x, coordD.y); rlVertex2f(vertD.x, vertD.y); // Bottom-right corner for texture and quad
3105 rlTexCoord2f(coordD.x, coordC.y); rlVertex2f(vertD.x, vertC.y); // Top-right corner for texture and quad
3106 rlTexCoord2f(coordA.x, coordC.y); rlVertex2f(vertA.x, vertC.y); // Top-left corner for texture and quad
3107 }
3108 else if (nPatchInfo.type == NPT_3PATCH_HORIZONTAL)
3109 {
3110 // LEFT QUAD
3111 // -----------------------------------------------------------
3112 // Texture coords Vertices
3113 rlTexCoord2f(coordA.x, coordD.y); rlVertex2f(vertA.x, vertD.y); // Bottom-left corner for texture and quad
3114 rlTexCoord2f(coordB.x, coordD.y); rlVertex2f(vertB.x, vertD.y); // Bottom-right corner for texture and quad
3115 rlTexCoord2f(coordB.x, coordA.y); rlVertex2f(vertB.x, vertA.y); // Top-right corner for texture and quad
3116 rlTexCoord2f(coordA.x, coordA.y); rlVertex2f(vertA.x, vertA.y); // Top-left corner for texture and quad
3117 if (drawCenter)
3118 {
3119 // CENTER QUAD
3120 // -----------------------------------------------------------
3121 // Texture coords Vertices
3122 rlTexCoord2f(coordB.x, coordD.y); rlVertex2f(vertB.x, vertD.y); // Bottom-left corner for texture and quad
3123 rlTexCoord2f(coordC.x, coordD.y); rlVertex2f(vertC.x, vertD.y); // Bottom-right corner for texture and quad
3124 rlTexCoord2f(coordC.x, coordA.y); rlVertex2f(vertC.x, vertA.y); // Top-right corner for texture and quad
3125 rlTexCoord2f(coordB.x, coordA.y); rlVertex2f(vertB.x, vertA.y); // Top-left corner for texture and quad
3126 }
3127 // RIGHT QUAD
3128 // -----------------------------------------------------------
3129 // Texture coords Vertices
3130 rlTexCoord2f(coordC.x, coordD.y); rlVertex2f(vertC.x, vertD.y); // Bottom-left corner for texture and quad
3131 rlTexCoord2f(coordD.x, coordD.y); rlVertex2f(vertD.x, vertD.y); // Bottom-right corner for texture and quad
3132 rlTexCoord2f(coordD.x, coordA.y); rlVertex2f(vertD.x, vertA.y); // Top-right corner for texture and quad
3133 rlTexCoord2f(coordC.x, coordA.y); rlVertex2f(vertC.x, vertA.y); // Top-left corner for texture and quad
3134 }
3135 rlEnd();
3136 rlPopMatrix();
3137
3138 rlDisableTexture();
3139 }
3140}
3141
3142//----------------------------------------------------------------------------------
3143// Module specific Functions Definition
3144//----------------------------------------------------------------------------------
3145#if defined(SUPPORT_FILEFORMAT_GIF)
3146// Load animated GIF data
3147// - Image.data buffer includes all frames: [image#0][image#1][image#2][...]
3148// - Number of frames is returned through 'frames' parameter
3149// - Frames delay is returned through 'delays' parameter (int array)
3150// - All frames are returned in RGBA format
3151static Image LoadAnimatedGIF(const char *fileName, int *frames, int **delays)
3152{
3153 Image image = { 0 };
3154
3155 unsigned int dataSize = 0;
3156 unsigned char *fileData = LoadFileData(fileName, &dataSize);
3157
3158 if (fileData != NULL)
3159 {
3160 int comp = 0;
3161 image.data = stbi_load_gif_from_memory(fileData, dataSize, delays, &image.width, &image.height, frames, &comp, 4);
3162
3163 image.mipmaps = 1;
3164 image.format = UNCOMPRESSED_R8G8B8A8;
3165
3166 RL_FREE(fileData);
3167 }
3168
3169 return image;
3170}
3171#endif
3172
3173#if defined(SUPPORT_FILEFORMAT_DDS)
3174// Loading DDS image data (compressed or uncompressed)
3175static Image LoadDDS(const char *fileName)
3176{
3177 // Required extension:
3178 // GL_EXT_texture_compression_s3tc
3179
3180 // Supported tokens (defined by extensions)
3181 // GL_COMPRESSED_RGB_S3TC_DXT1_EXT 0x83F0
3182 // GL_COMPRESSED_RGBA_S3TC_DXT1_EXT 0x83F1
3183 // GL_COMPRESSED_RGBA_S3TC_DXT3_EXT 0x83F2
3184 // GL_COMPRESSED_RGBA_S3TC_DXT5_EXT 0x83F3
3185
3186 #define FOURCC_DXT1 0x31545844 // Equivalent to "DXT1" in ASCII
3187 #define FOURCC_DXT3 0x33545844 // Equivalent to "DXT3" in ASCII
3188 #define FOURCC_DXT5 0x35545844 // Equivalent to "DXT5" in ASCII
3189
3190 // DDS Pixel Format
3191 typedef struct {
3192 unsigned int size;
3193 unsigned int flags;
3194 unsigned int fourCC;
3195 unsigned int rgbBitCount;
3196 unsigned int rBitMask;
3197 unsigned int gBitMask;
3198 unsigned int bBitMask;
3199 unsigned int aBitMask;
3200 } DDSPixelFormat;
3201
3202 // DDS Header (124 bytes)
3203 typedef struct {
3204 unsigned int size;
3205 unsigned int flags;
3206 unsigned int height;
3207 unsigned int width;
3208 unsigned int pitchOrLinearSize;
3209 unsigned int depth;
3210 unsigned int mipmapCount;
3211 unsigned int reserved1[11];
3212 DDSPixelFormat ddspf;
3213 unsigned int caps;
3214 unsigned int caps2;
3215 unsigned int caps3;
3216 unsigned int caps4;
3217 unsigned int reserved2;
3218 } DDSHeader;
3219
3220 Image image = { 0 };
3221
3222 FILE *ddsFile = fopen(fileName, "rb");
3223
3224 if (ddsFile == NULL)
3225 {
3226 TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to open DDS file", fileName);
3227 }
3228 else
3229 {
3230 // Verify the type of file
3231 char ddsHeaderId[4] = { 0 };
3232
3233 fread(ddsHeaderId, 4, 1, ddsFile);
3234
3235 if ((ddsHeaderId[0] != 'D') || (ddsHeaderId[1] != 'D') || (ddsHeaderId[2] != 'S') || (ddsHeaderId[3] != ' '))
3236 {
3237 TRACELOG(LOG_WARNING, "IMAGE: [%s] DDS file not a valid image", fileName);
3238 }
3239 else
3240 {
3241 DDSHeader ddsHeader = { 0 };
3242
3243 // Get the image header
3244 fread(&ddsHeader, sizeof(DDSHeader), 1, ddsFile);
3245
3246 TRACELOGD("IMAGE: [%s] DDS file info:", fileName);
3247 TRACELOGD(" > Header size: %i", fileName, sizeof(DDSHeader));
3248 TRACELOGD(" > Pixel format size: %i", fileName, ddsHeader.ddspf.size);
3249 TRACELOGD(" > Pixel format flags: 0x%x", fileName, ddsHeader.ddspf.flags);
3250 TRACELOGD(" > File format: 0x%x", fileName, ddsHeader.ddspf.fourCC);
3251 TRACELOGD(" > File bit count: 0x%x", fileName, ddsHeader.ddspf.rgbBitCount);
3252
3253 image.width = ddsHeader.width;
3254 image.height = ddsHeader.height;
3255
3256 if (ddsHeader.mipmapCount == 0) image.mipmaps = 1; // Parameter not used
3257 else image.mipmaps = ddsHeader.mipmapCount;
3258
3259 if (ddsHeader.ddspf.rgbBitCount == 16) // 16bit mode, no compressed
3260 {
3261 if (ddsHeader.ddspf.flags == 0x40) // no alpha channel
3262 {
3263 image.data = (unsigned short *)RL_MALLOC(image.width*image.height*sizeof(unsigned short));
3264 fread(image.data, image.width*image.height*sizeof(unsigned short), 1, ddsFile);
3265
3266 image.format = UNCOMPRESSED_R5G6B5;
3267 }
3268 else if (ddsHeader.ddspf.flags == 0x41) // with alpha channel
3269 {
3270 if (ddsHeader.ddspf.aBitMask == 0x8000) // 1bit alpha
3271 {
3272 image.data = (unsigned short *)RL_MALLOC(image.width*image.height*sizeof(unsigned short));
3273 fread(image.data, image.width*image.height*sizeof(unsigned short), 1, ddsFile);
3274
3275 unsigned char alpha = 0;
3276
3277 // NOTE: Data comes as A1R5G5B5, it must be reordered to R5G5B5A1
3278 for (int i = 0; i < image.width*image.height; i++)
3279 {
3280 alpha = ((unsigned short *)image.data)[i] >> 15;
3281 ((unsigned short *)image.data)[i] = ((unsigned short *)image.data)[i] << 1;
3282 ((unsigned short *)image.data)[i] += alpha;
3283 }
3284
3285 image.format = UNCOMPRESSED_R5G5B5A1;
3286 }
3287 else if (ddsHeader.ddspf.aBitMask == 0xf000) // 4bit alpha
3288 {
3289 image.data = (unsigned short *)RL_MALLOC(image.width*image.height*sizeof(unsigned short));
3290 fread(image.data, image.width*image.height*sizeof(unsigned short), 1, ddsFile);
3291
3292 unsigned char alpha = 0;
3293
3294 // NOTE: Data comes as A4R4G4B4, it must be reordered R4G4B4A4
3295 for (int i = 0; i < image.width*image.height; i++)
3296 {
3297 alpha = ((unsigned short *)image.data)[i] >> 12;
3298 ((unsigned short *)image.data)[i] = ((unsigned short *)image.data)[i] << 4;
3299 ((unsigned short *)image.data)[i] += alpha;
3300 }
3301
3302 image.format = UNCOMPRESSED_R4G4B4A4;
3303 }
3304 }
3305 }
3306 else if (ddsHeader.ddspf.flags == 0x40 && ddsHeader.ddspf.rgbBitCount == 24) // DDS_RGB, no compressed
3307 {
3308 // NOTE: not sure if this case exists...
3309 image.data = (unsigned char *)RL_MALLOC(image.width*image.height*3*sizeof(unsigned char));
3310 fread(image.data, image.width*image.height*3, 1, ddsFile);
3311
3312 image.format = UNCOMPRESSED_R8G8B8;
3313 }
3314 else if (ddsHeader.ddspf.flags == 0x41 && ddsHeader.ddspf.rgbBitCount == 32) // DDS_RGBA, no compressed
3315 {
3316 image.data = (unsigned char *)RL_MALLOC(image.width*image.height*4*sizeof(unsigned char));
3317 fread(image.data, image.width*image.height*4, 1, ddsFile);
3318
3319 unsigned char blue = 0;
3320
3321 // NOTE: Data comes as A8R8G8B8, it must be reordered R8G8B8A8 (view next comment)
3322 // DirecX understand ARGB as a 32bit DWORD but the actual memory byte alignment is BGRA
3323 // So, we must realign B8G8R8A8 to R8G8B8A8
3324 for (int i = 0; i < image.width*image.height*4; i += 4)
3325 {
3326 blue = ((unsigned char *)image.data)[i];
3327 ((unsigned char *)image.data)[i] = ((unsigned char *)image.data)[i + 2];
3328 ((unsigned char *)image.data)[i + 2] = blue;
3329 }
3330
3331 image.format = UNCOMPRESSED_R8G8B8A8;
3332 }
3333 else if (((ddsHeader.ddspf.flags == 0x04) || (ddsHeader.ddspf.flags == 0x05)) && (ddsHeader.ddspf.fourCC > 0)) // Compressed
3334 {
3335 int size; // DDS image data size
3336
3337 // Calculate data size, including all mipmaps
3338 if (ddsHeader.mipmapCount > 1) size = ddsHeader.pitchOrLinearSize*2;
3339 else size = ddsHeader.pitchOrLinearSize;
3340
3341 image.data = (unsigned char *)RL_MALLOC(size*sizeof(unsigned char));
3342
3343 fread(image.data, size, 1, ddsFile);
3344
3345 switch (ddsHeader.ddspf.fourCC)
3346 {
3347 case FOURCC_DXT1:
3348 {
3349 if (ddsHeader.ddspf.flags == 0x04) image.format = COMPRESSED_DXT1_RGB;
3350 else image.format = COMPRESSED_DXT1_RGBA;
3351 } break;
3352 case FOURCC_DXT3: image.format = COMPRESSED_DXT3_RGBA; break;
3353 case FOURCC_DXT5: image.format = COMPRESSED_DXT5_RGBA; break;
3354 default: break;
3355 }
3356 }
3357 }
3358
3359 fclose(ddsFile); // Close file pointer
3360 }
3361
3362 return image;
3363}
3364#endif
3365
3366#if defined(SUPPORT_FILEFORMAT_PKM)
3367// Loading PKM image data (ETC1/ETC2 compression)
3368// NOTE: KTX is the standard Khronos Group compression format (ETC1/ETC2, mipmaps)
3369// PKM is a much simpler file format used mainly to contain a single ETC1/ETC2 compressed image (no mipmaps)
3370static Image LoadPKM(const char *fileName)
3371{
3372 // Required extensions:
3373 // GL_OES_compressed_ETC1_RGB8_texture (ETC1) (OpenGL ES 2.0)
3374 // GL_ARB_ES3_compatibility (ETC2/EAC) (OpenGL ES 3.0)
3375
3376 // Supported tokens (defined by extensions)
3377 // GL_ETC1_RGB8_OES 0x8D64
3378 // GL_COMPRESSED_RGB8_ETC2 0x9274
3379 // GL_COMPRESSED_RGBA8_ETC2_EAC 0x9278
3380
3381 // PKM file (ETC1) Header (16 bytes)
3382 typedef struct {
3383 char id[4]; // "PKM "
3384 char version[2]; // "10" or "20"
3385 unsigned short format; // Data format (big-endian) (Check list below)
3386 unsigned short width; // Texture width (big-endian) (origWidth rounded to multiple of 4)
3387 unsigned short height; // Texture height (big-endian) (origHeight rounded to multiple of 4)
3388 unsigned short origWidth; // Original width (big-endian)
3389 unsigned short origHeight; // Original height (big-endian)
3390 } PKMHeader;
3391
3392 // Formats list
3393 // version 10: format: 0=ETC1_RGB, [1=ETC1_RGBA, 2=ETC1_RGB_MIP, 3=ETC1_RGBA_MIP] (not used)
3394 // version 20: format: 0=ETC1_RGB, 1=ETC2_RGB, 2=ETC2_RGBA_OLD, 3=ETC2_RGBA, 4=ETC2_RGBA1, 5=ETC2_R, 6=ETC2_RG, 7=ETC2_SIGNED_R, 8=ETC2_SIGNED_R
3395
3396 // NOTE: The extended width and height are the widths rounded up to a multiple of 4.
3397 // NOTE: ETC is always 4bit per pixel (64 bit for each 4x4 block of pixels)
3398
3399 Image image = { 0 };
3400
3401 FILE *pkmFile = fopen(fileName, "rb");
3402
3403 if (pkmFile == NULL)
3404 {
3405 TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to open PKM file", fileName);
3406 }
3407 else
3408 {
3409 PKMHeader pkmHeader = { 0 };
3410
3411 // Get the image header
3412 fread(&pkmHeader, sizeof(PKMHeader), 1, pkmFile);
3413
3414 if ((pkmHeader.id[0] != 'P') || (pkmHeader.id[1] != 'K') || (pkmHeader.id[2] != 'M') || (pkmHeader.id[3] != ' '))
3415 {
3416 TRACELOG(LOG_WARNING, "IMAGE: [%s] PKM file not a valid image", fileName);
3417 }
3418 else
3419 {
3420 // NOTE: format, width and height come as big-endian, data must be swapped to little-endian
3421 pkmHeader.format = ((pkmHeader.format & 0x00FF) << 8) | ((pkmHeader.format & 0xFF00) >> 8);
3422 pkmHeader.width = ((pkmHeader.width & 0x00FF) << 8) | ((pkmHeader.width & 0xFF00) >> 8);
3423 pkmHeader.height = ((pkmHeader.height & 0x00FF) << 8) | ((pkmHeader.height & 0xFF00) >> 8);
3424
3425 TRACELOGD("IMAGE: [%s] PKM file info:", fileName);
3426 TRACELOGD(" > Image width: %i", pkmHeader.width);
3427 TRACELOGD(" > Image height: %i", pkmHeader.height);
3428 TRACELOGD(" > Image format: %i", pkmHeader.format);
3429
3430 image.width = pkmHeader.width;
3431 image.height = pkmHeader.height;
3432 image.mipmaps = 1;
3433
3434 int bpp = 4;
3435 if (pkmHeader.format == 3) bpp = 8;
3436
3437 int size = image.width*image.height*bpp/8; // Total data size in bytes
3438
3439 image.data = (unsigned char *)RL_MALLOC(size*sizeof(unsigned char));
3440
3441 fread(image.data, size, 1, pkmFile);
3442
3443 if (pkmHeader.format == 0) image.format = COMPRESSED_ETC1_RGB;
3444 else if (pkmHeader.format == 1) image.format = COMPRESSED_ETC2_RGB;
3445 else if (pkmHeader.format == 3) image.format = COMPRESSED_ETC2_EAC_RGBA;
3446 }
3447
3448 fclose(pkmFile); // Close file pointer
3449 }
3450
3451 return image;
3452}
3453#endif
3454
3455#if defined(SUPPORT_FILEFORMAT_KTX)
3456// Load KTX compressed image data (ETC1/ETC2 compression)
3457static Image LoadKTX(const char *fileName)
3458{
3459 // Required extensions:
3460 // GL_OES_compressed_ETC1_RGB8_texture (ETC1)
3461 // GL_ARB_ES3_compatibility (ETC2/EAC)
3462
3463 // Supported tokens (defined by extensions)
3464 // GL_ETC1_RGB8_OES 0x8D64
3465 // GL_COMPRESSED_RGB8_ETC2 0x9274
3466 // GL_COMPRESSED_RGBA8_ETC2_EAC 0x9278
3467
3468 // KTX file Header (64 bytes)
3469 // v1.1 - https://www.khronos.org/opengles/sdk/tools/KTX/file_format_spec/
3470 // v2.0 - http://github.khronos.org/KTX-Specification/
3471
3472 // TODO: Support KTX 2.2 specs!
3473
3474 typedef struct {
3475 char id[12]; // Identifier: "«KTX 11»\r\n\x1A\n"
3476 unsigned int endianness; // Little endian: 0x01 0x02 0x03 0x04
3477 unsigned int glType; // For compressed textures, glType must equal 0
3478 unsigned int glTypeSize; // For compressed texture data, usually 1
3479 unsigned int glFormat; // For compressed textures is 0
3480 unsigned int glInternalFormat; // Compressed internal format
3481 unsigned int glBaseInternalFormat; // Same as glFormat (RGB, RGBA, ALPHA...)
3482 unsigned int width; // Texture image width in pixels
3483 unsigned int height; // Texture image height in pixels
3484 unsigned int depth; // For 2D textures is 0
3485 unsigned int elements; // Number of array elements, usually 0
3486 unsigned int faces; // Cubemap faces, for no-cubemap = 1
3487 unsigned int mipmapLevels; // Non-mipmapped textures = 1
3488 unsigned int keyValueDataSize; // Used to encode any arbitrary data...
3489 } KTXHeader;
3490
3491 // NOTE: Before start of every mipmap data block, we have: unsigned int dataSize
3492
3493 Image image = { 0 };
3494
3495 FILE *ktxFile = fopen(fileName, "rb");
3496
3497 if (ktxFile == NULL)
3498 {
3499 TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to load KTX file", fileName);
3500 }
3501 else
3502 {
3503 KTXHeader ktxHeader = { 0 };
3504
3505 // Get the image header
3506 fread(&ktxHeader, sizeof(KTXHeader), 1, ktxFile);
3507
3508 if ((ktxHeader.id[1] != 'K') || (ktxHeader.id[2] != 'T') || (ktxHeader.id[3] != 'X') ||
3509 (ktxHeader.id[4] != ' ') || (ktxHeader.id[5] != '1') || (ktxHeader.id[6] != '1'))
3510 {
3511 TRACELOG(LOG_WARNING, "IMAGE: [%s] KTX file not a valid image", fileName);
3512 }
3513 else
3514 {
3515 image.width = ktxHeader.width;
3516 image.height = ktxHeader.height;
3517 image.mipmaps = ktxHeader.mipmapLevels;
3518
3519 TRACELOGD("IMAGE: [%s] KTX file info:", fileName);
3520 TRACELOGD(" > Image width: %i", ktxHeader.width);
3521 TRACELOGD(" > Image height: %i", ktxHeader.height);
3522 TRACELOGD(" > Image format: 0x%x", ktxHeader.glInternalFormat);
3523
3524 unsigned char unused;
3525
3526 if (ktxHeader.keyValueDataSize > 0)
3527 {
3528 for (unsigned int i = 0; i < ktxHeader.keyValueDataSize; i++) fread(&unused, sizeof(unsigned char), 1U, ktxFile);
3529 }
3530
3531 int dataSize;
3532 fread(&dataSize, sizeof(unsigned int), 1, ktxFile);
3533
3534 image.data = (unsigned char *)RL_MALLOC(dataSize*sizeof(unsigned char));
3535
3536 fread(image.data, dataSize, 1, ktxFile);
3537
3538 if (ktxHeader.glInternalFormat == 0x8D64) image.format = COMPRESSED_ETC1_RGB;
3539 else if (ktxHeader.glInternalFormat == 0x9274) image.format = COMPRESSED_ETC2_RGB;
3540 else if (ktxHeader.glInternalFormat == 0x9278) image.format = COMPRESSED_ETC2_EAC_RGBA;
3541 }
3542
3543 fclose(ktxFile); // Close file pointer
3544 }
3545
3546 return image;
3547}
3548
3549// Save image data as KTX file
3550// NOTE: By default KTX 1.1 spec is used, 2.0 is still on draft (01Oct2018)
3551static int SaveKTX(Image image, const char *fileName)
3552{
3553 int success = 0;
3554
3555 // KTX file Header (64 bytes)
3556 // v1.1 - https://www.khronos.org/opengles/sdk/tools/KTX/file_format_spec/
3557 // v2.0 - http://github.khronos.org/KTX-Specification/ - still on draft, not ready for implementation
3558
3559 typedef struct {
3560 char id[12]; // Identifier: "«KTX 11»\r\n\x1A\n" // KTX 2.0: "«KTX 22»\r\n\x1A\n"
3561 unsigned int endianness; // Little endian: 0x01 0x02 0x03 0x04
3562 unsigned int glType; // For compressed textures, glType must equal 0
3563 unsigned int glTypeSize; // For compressed texture data, usually 1
3564 unsigned int glFormat; // For compressed textures is 0
3565 unsigned int glInternalFormat; // Compressed internal format
3566 unsigned int glBaseInternalFormat; // Same as glFormat (RGB, RGBA, ALPHA...) // KTX 2.0: UInt32 vkFormat
3567 unsigned int width; // Texture image width in pixels
3568 unsigned int height; // Texture image height in pixels
3569 unsigned int depth; // For 2D textures is 0
3570 unsigned int elements; // Number of array elements, usually 0
3571 unsigned int faces; // Cubemap faces, for no-cubemap = 1
3572 unsigned int mipmapLevels; // Non-mipmapped textures = 1
3573 unsigned int keyValueDataSize; // Used to encode any arbitrary data... // KTX 2.0: UInt32 levelOrder - ordering of the mipmap levels, usually 0
3574 // KTX 2.0: UInt32 supercompressionScheme - 0 (None), 1 (Crunch CRN), 2 (Zlib DEFLATE)...
3575 // KTX 2.0 defines additional header elements...
3576 } KTXHeader;
3577
3578 // NOTE: Before start of every mipmap data block, we have: unsigned int dataSize
3579
3580 FILE *ktxFile = fopen(fileName, "wb");
3581
3582 if (ktxFile == NULL) TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to open KTX file", fileName);
3583 else
3584 {
3585 KTXHeader ktxHeader = { 0 };
3586
3587 // KTX identifier (v1.1)
3588 //unsigned char id[12] = { '«', 'K', 'T', 'X', ' ', '1', '1', '»', '\r', '\n', '\x1A', '\n' };
3589 //unsigned char id[12] = { 0xAB, 0x4B, 0x54, 0x58, 0x20, 0x31, 0x31, 0xBB, 0x0D, 0x0A, 0x1A, 0x0A };
3590
3591 const char ktxIdentifier[12] = { 0xAB, 'K', 'T', 'X', ' ', '1', '1', 0xBB, '\r', '\n', 0x1A, '\n' };
3592
3593 // Get the image header
3594 strncpy(ktxHeader.id, ktxIdentifier, 12); // KTX 1.1 signature
3595 ktxHeader.endianness = 0;
3596 ktxHeader.glType = 0; // Obtained from image.format
3597 ktxHeader.glTypeSize = 1;
3598 ktxHeader.glFormat = 0; // Obtained from image.format
3599 ktxHeader.glInternalFormat = 0; // Obtained from image.format
3600 ktxHeader.glBaseInternalFormat = 0;
3601 ktxHeader.width = image.width;
3602 ktxHeader.height = image.height;
3603 ktxHeader.depth = 0;
3604 ktxHeader.elements = 0;
3605 ktxHeader.faces = 1;
3606 ktxHeader.mipmapLevels = image.mipmaps; // If it was 0, it means mipmaps should be generated on loading (not for compressed formats)
3607 ktxHeader.keyValueDataSize = 0; // No extra data after the header
3608
3609 rlGetGlTextureFormats(image.format, &ktxHeader.glInternalFormat, &ktxHeader.glFormat, &ktxHeader.glType); // rlgl module function
3610 ktxHeader.glBaseInternalFormat = ktxHeader.glFormat; // KTX 1.1 only
3611
3612 // NOTE: We can save into a .ktx all PixelFormats supported by raylib, including compressed formats like DXT, ETC or ASTC
3613
3614 if (ktxHeader.glFormat == -1) TRACELOG(LOG_WARNING, "IMAGE: GL format not supported for KTX export (%i)", ktxHeader.glFormat);
3615 else
3616 {
3617 success = fwrite(&ktxHeader, sizeof(KTXHeader), 1, ktxFile);
3618
3619 int width = image.width;
3620 int height = image.height;
3621 int dataOffset = 0;
3622
3623 // Save all mipmaps data
3624 for (int i = 0; i < image.mipmaps; i++)
3625 {
3626 unsigned int dataSize = GetPixelDataSize(width, height, image.format);
3627 success = fwrite(&dataSize, sizeof(unsigned int), 1, ktxFile);
3628 success = fwrite((unsigned char *)image.data + dataOffset, dataSize, 1, ktxFile);
3629
3630 width /= 2;
3631 height /= 2;
3632 dataOffset += dataSize;
3633 }
3634 }
3635
3636 fclose(ktxFile); // Close file pointer
3637 }
3638
3639 // If all data has been written correctly to file, success = 1
3640 return success;
3641}
3642#endif
3643
3644#if defined(SUPPORT_FILEFORMAT_PVR)
3645// Loading PVR image data (uncompressed or PVRT compression)
3646// NOTE: PVR v2 not supported, use PVR v3 instead
3647static Image LoadPVR(const char *fileName)
3648{
3649 // Required extension:
3650 // GL_IMG_texture_compression_pvrtc
3651
3652 // Supported tokens (defined by extensions)
3653 // GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG 0x8C00
3654 // GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG 0x8C02
3655
3656#if 0 // Not used...
3657 // PVR file v2 Header (52 bytes)
3658 typedef struct {
3659 unsigned int headerLength;
3660 unsigned int height;
3661 unsigned int width;
3662 unsigned int numMipmaps;
3663 unsigned int flags;
3664 unsigned int dataLength;
3665 unsigned int bpp;
3666 unsigned int bitmaskRed;
3667 unsigned int bitmaskGreen;
3668 unsigned int bitmaskBlue;
3669 unsigned int bitmaskAlpha;
3670 unsigned int pvrTag;
3671 unsigned int numSurfs;
3672 } PVRHeaderV2;
3673#endif
3674
3675 // PVR file v3 Header (52 bytes)
3676 // NOTE: After it could be metadata (15 bytes?)
3677 typedef struct {
3678 char id[4];
3679 unsigned int flags;
3680 unsigned char channels[4]; // pixelFormat high part
3681 unsigned char channelDepth[4]; // pixelFormat low part
3682 unsigned int colourSpace;
3683 unsigned int channelType;
3684 unsigned int height;
3685 unsigned int width;
3686 unsigned int depth;
3687 unsigned int numSurfaces;
3688 unsigned int numFaces;
3689 unsigned int numMipmaps;
3690 unsigned int metaDataSize;
3691 } PVRHeaderV3;
3692
3693#if 0 // Not used...
3694 // Metadata (usually 15 bytes)
3695 typedef struct {
3696 unsigned int devFOURCC;
3697 unsigned int key;
3698 unsigned int dataSize; // Not used?
3699 unsigned char *data; // Not used?
3700 } PVRMetadata;
3701#endif
3702
3703 Image image = { 0 };
3704
3705 FILE *pvrFile = fopen(fileName, "rb");
3706
3707 if (pvrFile == NULL)
3708 {
3709 TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to load PVR file", fileName);
3710 }
3711 else
3712 {
3713 // Check PVR image version
3714 unsigned char pvrVersion = 0;
3715 fread(&pvrVersion, sizeof(unsigned char), 1, pvrFile);
3716 fseek(pvrFile, 0, SEEK_SET);
3717
3718 // Load different PVR data formats
3719 if (pvrVersion == 0x50)
3720 {
3721 PVRHeaderV3 pvrHeader = { 0 };
3722
3723 // Get PVR image header
3724 fread(&pvrHeader, sizeof(PVRHeaderV3), 1, pvrFile);
3725
3726 if ((pvrHeader.id[0] != 'P') || (pvrHeader.id[1] != 'V') || (pvrHeader.id[2] != 'R') || (pvrHeader.id[3] != 3))
3727 {
3728 TRACELOG(LOG_WARNING, "IMAGE: [%s] PVR file not a valid image", fileName);
3729 }
3730 else
3731 {
3732 image.width = pvrHeader.width;
3733 image.height = pvrHeader.height;
3734 image.mipmaps = pvrHeader.numMipmaps;
3735
3736 // Check data format
3737 if (((pvrHeader.channels[0] == 'l') && (pvrHeader.channels[1] == 0)) && (pvrHeader.channelDepth[0] == 8))
3738 image.format = UNCOMPRESSED_GRAYSCALE;
3739 else if (((pvrHeader.channels[0] == 'l') && (pvrHeader.channels[1] == 'a')) && ((pvrHeader.channelDepth[0] == 8) && (pvrHeader.channelDepth[1] == 8)))
3740 image.format = UNCOMPRESSED_GRAY_ALPHA;
3741 else if ((pvrHeader.channels[0] == 'r') && (pvrHeader.channels[1] == 'g') && (pvrHeader.channels[2] == 'b'))
3742 {
3743 if (pvrHeader.channels[3] == 'a')
3744 {
3745 if ((pvrHeader.channelDepth[0] == 5) && (pvrHeader.channelDepth[1] == 5) && (pvrHeader.channelDepth[2] == 5) && (pvrHeader.channelDepth[3] == 1))
3746 image.format = UNCOMPRESSED_R5G5B5A1;
3747 else if ((pvrHeader.channelDepth[0] == 4) && (pvrHeader.channelDepth[1] == 4) && (pvrHeader.channelDepth[2] == 4) && (pvrHeader.channelDepth[3] == 4))
3748 image.format = UNCOMPRESSED_R4G4B4A4;
3749 else if ((pvrHeader.channelDepth[0] == 8) && (pvrHeader.channelDepth[1] == 8) && (pvrHeader.channelDepth[2] == 8) && (pvrHeader.channelDepth[3] == 8))
3750 image.format = UNCOMPRESSED_R8G8B8A8;
3751 }
3752 else if (pvrHeader.channels[3] == 0)
3753 {
3754 if ((pvrHeader.channelDepth[0] == 5) && (pvrHeader.channelDepth[1] == 6) && (pvrHeader.channelDepth[2] == 5)) image.format = UNCOMPRESSED_R5G6B5;
3755 else if ((pvrHeader.channelDepth[0] == 8) && (pvrHeader.channelDepth[1] == 8) && (pvrHeader.channelDepth[2] == 8)) image.format = UNCOMPRESSED_R8G8B8;
3756 }
3757 }
3758 else if (pvrHeader.channels[0] == 2) image.format = COMPRESSED_PVRT_RGB;
3759 else if (pvrHeader.channels[0] == 3) image.format = COMPRESSED_PVRT_RGBA;
3760
3761 // Skip meta data header
3762 unsigned char unused = 0;
3763 for (int i = 0; i < pvrHeader.metaDataSize; i++) fread(&unused, sizeof(unsigned char), 1, pvrFile);
3764
3765 // Calculate data size (depends on format)
3766 int bpp = 0;
3767
3768 switch (image.format)
3769 {
3770 case UNCOMPRESSED_GRAYSCALE: bpp = 8; break;
3771 case UNCOMPRESSED_GRAY_ALPHA:
3772 case UNCOMPRESSED_R5G5B5A1:
3773 case UNCOMPRESSED_R5G6B5:
3774 case UNCOMPRESSED_R4G4B4A4: bpp = 16; break;
3775 case UNCOMPRESSED_R8G8B8A8: bpp = 32; break;
3776 case UNCOMPRESSED_R8G8B8: bpp = 24; break;
3777 case COMPRESSED_PVRT_RGB:
3778 case COMPRESSED_PVRT_RGBA: bpp = 4; break;
3779 default: break;
3780 }
3781
3782 int dataSize = image.width*image.height*bpp/8; // Total data size in bytes
3783 image.data = (unsigned char *)RL_MALLOC(dataSize*sizeof(unsigned char));
3784
3785 // Read data from file
3786 fread(image.data, dataSize, 1, pvrFile);
3787 }
3788 }
3789 else if (pvrVersion == 52) TRACELOG(LOG_INFO, "IMAGE: [%s] PVRv2 format not supported, update your files to PVRv3", fileName);
3790
3791 fclose(pvrFile); // Close file pointer
3792 }
3793
3794 return image;
3795}
3796#endif
3797
3798#if defined(SUPPORT_FILEFORMAT_ASTC)
3799// Load ASTC compressed image data (ASTC compression)
3800static Image LoadASTC(const char *fileName)
3801{
3802 // Required extensions:
3803 // GL_KHR_texture_compression_astc_hdr
3804 // GL_KHR_texture_compression_astc_ldr
3805
3806 // Supported tokens (defined by extensions)
3807 // GL_COMPRESSED_RGBA_ASTC_4x4_KHR 0x93b0
3808 // GL_COMPRESSED_RGBA_ASTC_8x8_KHR 0x93b7
3809
3810 // ASTC file Header (16 bytes)
3811 typedef struct {
3812 unsigned char id[4]; // Signature: 0x13 0xAB 0xA1 0x5C
3813 unsigned char blockX; // Block X dimensions
3814 unsigned char blockY; // Block Y dimensions
3815 unsigned char blockZ; // Block Z dimensions (1 for 2D images)
3816 unsigned char width[3]; // Image width in pixels (24bit value)
3817 unsigned char height[3]; // Image height in pixels (24bit value)
3818 unsigned char length[3]; // Image Z-size (1 for 2D images)
3819 } ASTCHeader;
3820
3821 Image image = { 0 };
3822
3823 FILE *astcFile = fopen(fileName, "rb");
3824
3825 if (astcFile == NULL)
3826 {
3827 TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to load ASTC file", fileName);
3828 }
3829 else
3830 {
3831 ASTCHeader astcHeader = { 0 };
3832
3833 // Get ASTC image header
3834 fread(&astcHeader, sizeof(ASTCHeader), 1, astcFile);
3835
3836 if ((astcHeader.id[3] != 0x5c) || (astcHeader.id[2] != 0xa1) || (astcHeader.id[1] != 0xab) || (astcHeader.id[0] != 0x13))
3837 {
3838 TRACELOG(LOG_WARNING, "IMAGE: [%s] ASTC file not a valid image", fileName);
3839 }
3840 else
3841 {
3842 // NOTE: Assuming Little Endian (could it be wrong?)
3843 image.width = 0x00000000 | ((int)astcHeader.width[2] << 16) | ((int)astcHeader.width[1] << 8) | ((int)astcHeader.width[0]);
3844 image.height = 0x00000000 | ((int)astcHeader.height[2] << 16) | ((int)astcHeader.height[1] << 8) | ((int)astcHeader.height[0]);
3845
3846 TRACELOGD("IMAGE: [%s] ASTC file info:", fileName);
3847 TRACELOGD(" > Image width: %i", image.width);
3848 TRACELOGD(" > Image height: %i", image.height);
3849 TRACELOGD(" > Image blocks: %ix%i", astcHeader.blockX, astcHeader.blockY);
3850
3851 image.mipmaps = 1; // NOTE: ASTC format only contains one mipmap level
3852
3853 // NOTE: Each block is always stored in 128bit so we can calculate the bpp
3854 int bpp = 128/(astcHeader.blockX*astcHeader.blockY);
3855
3856 // NOTE: Currently we only support 2 blocks configurations: 4x4 and 8x8
3857 if ((bpp == 8) || (bpp == 2))
3858 {
3859 int dataSize = image.width*image.height*bpp/8; // Data size in bytes
3860
3861 image.data = (unsigned char *)RL_MALLOC(dataSize*sizeof(unsigned char));
3862 fread(image.data, dataSize, 1, astcFile);
3863
3864 if (bpp == 8) image.format = COMPRESSED_ASTC_4x4_RGBA;
3865 else if (bpp == 2) image.format = COMPRESSED_ASTC_8x8_RGBA;
3866 }
3867 else TRACELOG(LOG_WARNING, "IMAGE: [%s] ASTC block size configuration not supported", fileName);
3868 }
3869
3870 fclose(astcFile);
3871 }
3872
3873 return image;
3874}
3875#endif
3876