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) |
172 | static Image LoadAnimatedGIF(const char *fileName, int *frames, int **delays); // Load animated GIF file |
173 | #endif |
174 | #if defined(SUPPORT_FILEFORMAT_DDS) |
175 | static Image LoadDDS(const char *fileName); // Load DDS file |
176 | #endif |
177 | #if defined(SUPPORT_FILEFORMAT_PKM) |
178 | static Image LoadPKM(const char *fileName); // Load PKM file |
179 | #endif |
180 | #if defined(SUPPORT_FILEFORMAT_KTX) |
181 | static Image LoadKTX(const char *fileName); // Load KTX file |
182 | static int SaveKTX(Image image, const char *fileName); // Save image data as KTX file |
183 | #endif |
184 | #if defined(SUPPORT_FILEFORMAT_PVR) |
185 | static Image LoadPVR(const char *fileName); // Load PVR file |
186 | #endif |
187 | #if defined(SUPPORT_FILEFORMAT_ASTC) |
188 | static 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) |
196 | Image 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 |
310 | Image 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 |
336 | Image LoadImageRaw(const char *fileName, int width, int height, int format, int ) |
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) |
364 | Texture2D 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 |
381 | Texture2D 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 |
401 | RenderTexture2D 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) |
409 | void UnloadImage(Image image) |
410 | { |
411 | RL_FREE(image.data); |
412 | } |
413 | |
414 | // Unload texture from GPU memory (VRAM) |
415 | void 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) |
426 | void 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 |
432 | Color *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) |
549 | Vector4 *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 |
660 | Rectangle 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 |
701 | int 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 |
739 | Image 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) |
770 | Image 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 |
785 | void 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 |
792 | void 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 |
833 | void 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 |
874 | Image 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 |
911 | Image 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) |
924 | void 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 |
970 | void 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 |
1150 | void 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 |
1200 | void 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 |
1219 | void 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 |
1246 | TextureCubemap 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 |
1329 | void 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 |
1371 | void 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) |
1409 | void 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 |
1433 | void 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 |
1470 | void 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 |
1550 | void 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 |
1619 | void 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! |
1730 | Color *(Image image, int maxPaletteSize, int *) |
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 |
1781 | void 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) |
1900 | Image 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) |
1912 | Image 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 |
1973 | void 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) |
1979 | void 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 |
1985 | void 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 |
1996 | void 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 |
2005 | void 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 |
2012 | void 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) |
2126 | void 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 |
2132 | void 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) |
2159 | void 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 |
2165 | void 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) |
2184 | void 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) |
2190 | void 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) |
2199 | void 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 |
2212 | void 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 |
2239 | void 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 |
2266 | void 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 |
2295 | void 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 |
2324 | void 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 |
2362 | void 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 |
2388 | void ImageColorGrayscale(Image *image) |
2389 | { |
2390 | ImageFormat(image, UNCOMPRESSED_GRAYSCALE); |
2391 | } |
2392 | |
2393 | // Modify image color: contrast |
2394 | // NOTE: Contrast values between -100 and 100 |
2395 | void 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 |
2452 | void 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 |
2494 | void 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 |
2528 | Image 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 |
2543 | Image 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 |
2566 | Image 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 |
2589 | Image 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 |
2621 | Image 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 |
2641 | Image 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 |
2658 | Image 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 |
2689 | Image 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 |
2750 | void 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 |
2758 | void 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 |
2824 | void 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 |
2853 | void 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 |
2859 | void 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 |
2865 | void 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) |
2875 | void 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 |
2886 | void 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 |
2896 | void 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 |
2947 | void 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 |
3151 | static 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) |
3175 | static 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 | } ; |
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 [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 = { 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) |
3370 | static 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) |
3457 | static 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 | } ; |
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 = { 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) |
3551 | static 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 | } ; |
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 = { 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 |
3647 | static 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) |
3800 | static 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 | } ; |
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 = { 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 | |