diff options
| author | raysan5 <[email protected]> | 2020-05-09 12:05:00 +0200 |
|---|---|---|
| committer | raysan5 <[email protected]> | 2020-05-09 12:05:00 +0200 |
| commit | 959447d8ed69e63b279bc5b72354c9d14a266e8f (patch) | |
| tree | d5f948766f9f893dfb20eaa6e4cf6137be447539 /src | |
| parent | 4e29294caa2ff80bb3dadbeb8bb495b5150fd764 (diff) | |
| download | raylib-959447d8ed69e63b279bc5b72354c9d14a266e8f.tar.gz raylib-959447d8ed69e63b279bc5b72354c9d14a266e8f.zip | |
Reorganized texture functions
Removed ImageAlphaMask() dependency on [text] LoadBMFont()
Diffstat (limited to 'src')
| -rw-r--r-- | src/config.h | 9 | ||||
| -rw-r--r-- | src/raylib.h | 13 | ||||
| -rw-r--r-- | src/text.c | 13 | ||||
| -rw-r--r-- | src/textures.c | 2486 |
4 files changed, 1276 insertions, 1245 deletions
diff --git a/src/config.h b/src/config.h index ebaa8a62..7650e08c 100644 --- a/src/config.h +++ b/src/config.h @@ -98,11 +98,14 @@ // Support image export functionality (.png, .bmp, .tga, .jpg) #define SUPPORT_IMAGE_EXPORT 1 -// Support multiple image editing functions to scale, adjust colors, flip, draw on images, crop... -// If not defined only three image editing functions supported: ImageFormat(), ImageAlphaMask(), ImageToPOT() -#define SUPPORT_IMAGE_MANIPULATION 1 + // Support procedural image generation functionality (gradient, spot, perlin-noise, cellular) #define SUPPORT_IMAGE_GENERATION 1 +// Support multiple image editing functions to scale, adjust colors, flip, draw on images, crop... +// If not defined only three image editing functions supported: ImageFormat(), ImageCrop(), ImageToPOT() +#define SUPPORT_IMAGE_MANIPULATION 1 +// Support drawing on image (software rendering) +#define SUPPORT_IMAGE_DRAWING 1 //------------------------------------------------------------------------------------ // Module: text - Configuration Flags diff --git a/src/raylib.h b/src/raylib.h index 5289061f..8fa20f49 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -1110,8 +1110,6 @@ RLAPI Image LoadImageRaw(const char *fileName, int width, int height, int format RLAPI void UnloadImage(Image image); // Unload image from CPU memory (RAM) RLAPI void ExportImage(Image image, const char *fileName); // Export image data to file RLAPI void ExportImageAsCode(Image image, const char *fileName); // Export image as code file defining an array of bytes -RLAPI Color *GetImageData(Image image); // Get pixel data from image as a Color struct array -RLAPI Vector4 *GetImageDataNormalized(Image image); // Get pixel data from image as Vector4 array (float normalized) // Image generation functions RLAPI Image GenImageColor(int width, int height, Color color); // Generate image: plain color @@ -1128,13 +1126,13 @@ RLAPI Image ImageCopy(Image image); RLAPI Image ImageFromImage(Image image, Rectangle rec); // Create an image from another image piece RLAPI Image ImageText(const char *text, int fontSize, Color color); // Create an image from text (default font) RLAPI Image ImageTextEx(Font font, const char *text, float fontSize, float spacing, Color tint); // Create an image from text (custom sprite font) -RLAPI void ImageToPOT(Image *image, Color fillColor); // Convert image to POT (power-of-two) RLAPI void ImageFormat(Image *image, int newFormat); // Convert image data to desired format -RLAPI void ImageAlphaMask(Image *image, Image alphaMask); // Apply alpha mask to image -RLAPI void ImageAlphaClear(Image *image, Color color, float threshold); // Clear alpha channel to desired color +RLAPI void ImageToPOT(Image *image, Color fillColor); // Convert image to POT (power-of-two) +RLAPI void ImageCrop(Image *image, Rectangle crop); // Crop an image to a defined rectangle RLAPI void ImageAlphaCrop(Image *image, float threshold); // Crop image depending on alpha value +RLAPI void ImageAlphaClear(Image *image, Color color, float threshold); // Clear alpha channel to desired color +RLAPI void ImageAlphaMask(Image *image, Image alphaMask); // Apply alpha mask to image RLAPI void ImageAlphaPremultiply(Image *image); // Premultiply alpha channel -RLAPI void ImageCrop(Image *image, Rectangle crop); // Crop an image to a defined rectangle RLAPI void ImageResize(Image *image, int newWidth, int newHeight); // Resize image (Bicubic scaling algorithm) RLAPI void ImageResizeNN(Image *image, int newWidth,int newHeight); // Resize image (Nearest-Neighbor scaling algorithm) RLAPI void ImageResizeCanvas(Image *image, int newWidth, int newHeight, int offsetX, int offsetY, Color color); // Resize canvas and fill with color @@ -1150,7 +1148,10 @@ RLAPI void ImageColorGrayscale(Image *image); RLAPI void ImageColorContrast(Image *image, float contrast); // Modify image color: contrast (-100 to 100) RLAPI void ImageColorBrightness(Image *image, int brightness); // Modify image color: brightness (-255 to 255) RLAPI void ImageColorReplace(Image *image, Color color, Color replace); // Modify image color: replace color + +RLAPI Color *GetImageData(Image image); // Get pixel data from image as a Color struct array RLAPI Color *ImageExtractPalette(Image image, int maxPaletteSize, int *extractCount); // Extract color palette from image to maximum size (memory should be freed) +RLAPI Vector4 *GetImageDataNormalized(Image image); // Get pixel data from image as Vector4 array (float normalized) RLAPI Rectangle GetImageAlphaBorder(Image image, float threshold); // Get image alpha border rectangle // Image drawing functions @@ -1733,8 +1733,17 @@ static Font LoadBMFont(const char *fileName) if (imFont.format == UNCOMPRESSED_GRAYSCALE) { // Convert image to GRAYSCALE + ALPHA, using the mask as the alpha channel - ImageAlphaMask(&imFont, imFont); - for (int p = 0; p < (imFont.width*imFont.height*2); p += 2) ((unsigned char *)(imFont.data))[p] = 0xff; + Image imFontAlpha = ImageCopy(imFont); + ImageFormat(&imFontAlpha, UNCOMPRESSED_GRAY_ALPHA); + + for (int p = 0, i = 0; p < (imFont.width*imFont.height*2); p += 2, i++) + { + ((unsigned char *)(imFontAlpha.data))[p] = 0xff; + ((unsigned char *)(imFontAlpha.data))[p + 1] = ((unsigned char *)imFont.data)[i]; + } + + UnloadImage(imFont); + imFont = imFontAlpha; } font.texture = LoadTextureFromImage(imFont); diff --git a/src/textures.c b/src/textures.c index d08ee950..74d4ebd5 100644 --- a/src/textures.c +++ b/src/textures.c @@ -363,518 +363,325 @@ Image LoadImageRaw(const char *fileName, int width, int height, int format, int return image; } -// Load texture from file into GPU memory (VRAM) -Texture2D LoadTexture(const char *fileName) -{ - Texture2D texture = { 0 }; - - Image image = LoadImage(fileName); - - if (image.data != NULL) - { - texture = LoadTextureFromImage(image); - UnloadImage(image); - } - - return texture; -} - -// Load a texture from image data -// NOTE: image is not unloaded, it must be done manually -Texture2D LoadTextureFromImage(Image image) -{ - Texture2D texture = { 0 }; - - if ((image.data != NULL) && (image.width != 0) && (image.height != 0)) - { - texture.id = rlLoadTexture(image.data, image.width, image.height, image.format, image.mipmaps); - } - else TRACELOG(LOG_WARNING, "IMAGE: Data is not valid to load texture"); - - texture.width = image.width; - texture.height = image.height; - texture.mipmaps = image.mipmaps; - texture.format = image.format; - - return texture; -} - -// Load texture for rendering (framebuffer) -// NOTE: Render texture is loaded by default with RGBA color attachment and depth RenderBuffer -RenderTexture2D LoadRenderTexture(int width, int height) -{ - RenderTexture2D target = rlLoadRenderTexture(width, height, UNCOMPRESSED_R8G8B8A8, 24, false); - - return target; -} - // Unload image from CPU memory (RAM) void UnloadImage(Image image) { RL_FREE(image.data); } -// Unload texture from GPU memory (VRAM) -void UnloadTexture(Texture2D texture) +// Export image data to file +// NOTE: File format depends on fileName extension +void ExportImage(Image image, const char *fileName) { - if (texture.id > 0) - { - rlDeleteTextures(texture.id); + int success = 0; - TRACELOG(LOG_INFO, "TEXTURE: [ID %i] Unloaded texture data from VRAM (GPU)", texture.id); +#if defined(SUPPORT_IMAGE_EXPORT) + // NOTE: Getting Color array as RGBA unsigned char values + unsigned char *imgData = (unsigned char *)GetImageData(image); + +#if defined(SUPPORT_FILEFORMAT_PNG) + if (IsFileExtension(fileName, ".png")) success = stbi_write_png(fileName, image.width, image.height, 4, imgData, image.width*4); +#else + if (false) {} +#endif +#if defined(SUPPORT_FILEFORMAT_BMP) + else if (IsFileExtension(fileName, ".bmp")) success = stbi_write_bmp(fileName, image.width, image.height, 4, imgData); +#endif +#if defined(SUPPORT_FILEFORMAT_TGA) + else if (IsFileExtension(fileName, ".tga")) success = stbi_write_tga(fileName, image.width, image.height, 4, imgData); +#endif +#if defined(SUPPORT_FILEFORMAT_JPG) + else if (IsFileExtension(fileName, ".jpg")) success = stbi_write_jpg(fileName, image.width, image.height, 4, imgData, 80); // JPG quality: between 1 and 100 +#endif +#if defined(SUPPORT_FILEFORMAT_KTX) + else if (IsFileExtension(fileName, ".ktx")) success = SaveKTX(image, fileName); +#endif + else if (IsFileExtension(fileName, ".raw")) + { + // Export raw pixel data (without header) + // NOTE: It's up to the user to track image parameters + SaveFileData(fileName, image.data, GetPixelDataSize(image.width, image.height, image.format)); + success = true; } -} -// Unload render texture from GPU memory (VRAM) -void UnloadRenderTexture(RenderTexture2D target) -{ - if (target.id > 0) rlDeleteRenderTextures(target); + RL_FREE(imgData); +#endif + + if (success != 0) TRACELOG(LOG_INFO, "FILEIO: [%s] Image exported successfully", fileName); + else TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to export image", fileName); } -// Get pixel data from image in the form of Color struct array -Color *GetImageData(Image image) +// Export image as code file (.h) defining an array of bytes +void ExportImageAsCode(Image image, const char *fileName) { - if ((image.width == 0) || (image.height == 0)) return NULL; +#ifndef TEXT_BYTES_PER_LINE + #define TEXT_BYTES_PER_LINE 20 +#endif - Color *pixels = (Color *)RL_MALLOC(image.width*image.height*sizeof(Color)); + FILE *txtFile = fopen(fileName, "wt"); - if (image.format >= COMPRESSED_DXT1_RGB) TRACELOG(LOG_WARNING, "IMAGE: Pixel data retrieval not supported for compressed image formats"); - else + if (txtFile != NULL) { - if ((image.format == UNCOMPRESSED_R32) || - (image.format == UNCOMPRESSED_R32G32B32) || - (image.format == UNCOMPRESSED_R32G32B32A32)) TRACELOG(LOG_WARNING, "IMAGE: Pixel format converted from 32bit to 8bit per channel"); - - for (int i = 0, k = 0; i < image.width*image.height; i++) - { - switch (image.format) - { - case UNCOMPRESSED_GRAYSCALE: - { - pixels[i].r = ((unsigned char *)image.data)[i]; - pixels[i].g = ((unsigned char *)image.data)[i]; - pixels[i].b = ((unsigned char *)image.data)[i]; - pixels[i].a = 255; - - } break; - case UNCOMPRESSED_GRAY_ALPHA: - { - pixels[i].r = ((unsigned char *)image.data)[k]; - pixels[i].g = ((unsigned char *)image.data)[k]; - pixels[i].b = ((unsigned char *)image.data)[k]; - pixels[i].a = ((unsigned char *)image.data)[k + 1]; + char varFileName[256] = { 0 }; + int dataSize = GetPixelDataSize(image.width, image.height, image.format); - k += 2; - } break; - case UNCOMPRESSED_R5G5B5A1: - { - unsigned short pixel = ((unsigned short *)image.data)[i]; + fprintf(txtFile, "////////////////////////////////////////////////////////////////////////////////////////\n"); + fprintf(txtFile, "// //\n"); + fprintf(txtFile, "// ImageAsCode exporter v1.0 - Image pixel data exported as an array of bytes //\n"); + fprintf(txtFile, "// //\n"); + fprintf(txtFile, "// more info and bugs-report: github.com/raysan5/raylib //\n"); + fprintf(txtFile, "// feedback and support: ray[at]raylib.com //\n"); + fprintf(txtFile, "// //\n"); + fprintf(txtFile, "// Copyright (c) 2020 Ramon Santamaria (@raysan5) //\n"); + fprintf(txtFile, "// //\n"); + fprintf(txtFile, "////////////////////////////////////////////////////////////////////////////////////////\n\n"); - pixels[i].r = (unsigned char)((float)((pixel & 0b1111100000000000) >> 11)*(255/31)); - pixels[i].g = (unsigned char)((float)((pixel & 0b0000011111000000) >> 6)*(255/31)); - pixels[i].b = (unsigned char)((float)((pixel & 0b0000000000111110) >> 1)*(255/31)); - pixels[i].a = (unsigned char)((pixel & 0b0000000000000001)*255); + // Get file name from path and convert variable name to uppercase + strcpy(varFileName, GetFileNameWithoutExt(fileName)); + for (int i = 0; varFileName[i] != '\0'; i++) if ((varFileName[i] >= 'a') && (varFileName[i] <= 'z')) { varFileName[i] = varFileName[i] - 32; } - } break; - case UNCOMPRESSED_R5G6B5: - { - unsigned short pixel = ((unsigned short *)image.data)[i]; + // Add image information + fprintf(txtFile, "// Image data information\n"); + fprintf(txtFile, "#define %s_WIDTH %i\n", varFileName, image.width); + fprintf(txtFile, "#define %s_HEIGHT %i\n", varFileName, image.height); + fprintf(txtFile, "#define %s_FORMAT %i // raylib internal pixel format\n\n", varFileName, image.format); - pixels[i].r = (unsigned char)((float)((pixel & 0b1111100000000000) >> 11)*(255/31)); - pixels[i].g = (unsigned char)((float)((pixel & 0b0000011111100000) >> 5)*(255/63)); - pixels[i].b = (unsigned char)((float)(pixel & 0b0000000000011111)*(255/31)); - pixels[i].a = 255; + fprintf(txtFile, "static unsigned char %s_DATA[%i] = { ", varFileName, dataSize); + for (int i = 0; i < dataSize - 1; i++) fprintf(txtFile, ((i%TEXT_BYTES_PER_LINE == 0)? "0x%x,\n" : "0x%x, "), ((unsigned char *)image.data)[i]); + fprintf(txtFile, "0x%x };\n", ((unsigned char *)image.data)[dataSize - 1]); - } break; - case UNCOMPRESSED_R4G4B4A4: - { - unsigned short pixel = ((unsigned short *)image.data)[i]; + fclose(txtFile); + } +} - pixels[i].r = (unsigned char)((float)((pixel & 0b1111000000000000) >> 12)*(255/15)); - pixels[i].g = (unsigned char)((float)((pixel & 0b0000111100000000) >> 8)*(255/15)); - pixels[i].b = (unsigned char)((float)((pixel & 0b0000000011110000) >> 4)*(255/15)); - pixels[i].a = (unsigned char)((float)(pixel & 0b0000000000001111)*(255/15)); +//------------------------------------------------------------------------------------ +// Image generation functions +//------------------------------------------------------------------------------------ +// Generate image: plain color +Image GenImageColor(int width, int height, Color color) +{ + Color *pixels = (Color *)RL_CALLOC(width*height, sizeof(Color)); - } break; - case UNCOMPRESSED_R8G8B8A8: - { - pixels[i].r = ((unsigned char *)image.data)[k]; - pixels[i].g = ((unsigned char *)image.data)[k + 1]; - pixels[i].b = ((unsigned char *)image.data)[k + 2]; - pixels[i].a = ((unsigned char *)image.data)[k + 3]; + for (int i = 0; i < width*height; i++) pixels[i] = color; - k += 4; - } break; - case UNCOMPRESSED_R8G8B8: - { - pixels[i].r = (unsigned char)((unsigned char *)image.data)[k]; - pixels[i].g = (unsigned char)((unsigned char *)image.data)[k + 1]; - pixels[i].b = (unsigned char)((unsigned char *)image.data)[k + 2]; - pixels[i].a = 255; + Image image = LoadImageEx(pixels, width, height); - k += 3; - } break; - case UNCOMPRESSED_R32: - { - pixels[i].r = (unsigned char)(((float *)image.data)[k]*255.0f); - pixels[i].g = 0; - pixels[i].b = 0; - pixels[i].a = 255; + RL_FREE(pixels); - } break; - case UNCOMPRESSED_R32G32B32: - { - pixels[i].r = (unsigned char)(((float *)image.data)[k]*255.0f); - pixels[i].g = (unsigned char)(((float *)image.data)[k + 1]*255.0f); - pixels[i].b = (unsigned char)(((float *)image.data)[k + 2]*255.0f); - pixels[i].a = 255; + return image; +} - k += 3; - } break; - case UNCOMPRESSED_R32G32B32A32: - { - pixels[i].r = (unsigned char)(((float *)image.data)[k]*255.0f); - pixels[i].g = (unsigned char)(((float *)image.data)[k]*255.0f); - pixels[i].b = (unsigned char)(((float *)image.data)[k]*255.0f); - pixels[i].a = (unsigned char)(((float *)image.data)[k]*255.0f); +#if defined(SUPPORT_IMAGE_GENERATION) +// Generate image: vertical gradient +Image GenImageGradientV(int width, int height, Color top, Color bottom) +{ + Color *pixels = (Color *)RL_MALLOC(width*height*sizeof(Color)); - k += 4; - } break; - default: break; - } + for (int j = 0; j < height; j++) + { + float factor = (float)j/(float)height; + for (int i = 0; i < width; i++) + { + pixels[j*width + i].r = (int)((float)bottom.r*factor + (float)top.r*(1.f - factor)); + pixels[j*width + i].g = (int)((float)bottom.g*factor + (float)top.g*(1.f - factor)); + pixels[j*width + i].b = (int)((float)bottom.b*factor + (float)top.b*(1.f - factor)); + pixels[j*width + i].a = (int)((float)bottom.a*factor + (float)top.a*(1.f - factor)); } } - return pixels; + Image image = LoadImageEx(pixels, width, height); + RL_FREE(pixels); + + return image; } -// Get pixel data from image as Vector4 array (float normalized) -Vector4 *GetImageDataNormalized(Image image) +// Generate image: horizontal gradient +Image GenImageGradientH(int width, int height, Color left, Color right) { - Vector4 *pixels = (Vector4 *)RL_MALLOC(image.width*image.height*sizeof(Vector4)); + Color *pixels = (Color *)RL_MALLOC(width*height*sizeof(Color)); - if (image.format >= COMPRESSED_DXT1_RGB) TRACELOG(LOG_WARNING, "IMAGE: Pixel data retrieval not supported for compressed image formats"); - else + for (int i = 0; i < width; i++) { - for (int i = 0, k = 0; i < image.width*image.height; i++) + float factor = (float)i/(float)width; + for (int j = 0; j < height; j++) { - switch (image.format) - { - case UNCOMPRESSED_GRAYSCALE: - { - pixels[i].x = (float)((unsigned char *)image.data)[i]/255.0f; - pixels[i].y = (float)((unsigned char *)image.data)[i]/255.0f; - pixels[i].z = (float)((unsigned char *)image.data)[i]/255.0f; - pixels[i].w = 1.0f; - - } break; - case UNCOMPRESSED_GRAY_ALPHA: - { - pixels[i].x = (float)((unsigned char *)image.data)[k]/255.0f; - pixels[i].y = (float)((unsigned char *)image.data)[k]/255.0f; - pixels[i].z = (float)((unsigned char *)image.data)[k]/255.0f; - pixels[i].w = (float)((unsigned char *)image.data)[k + 1]/255.0f; - - k += 2; - } break; - case UNCOMPRESSED_R5G5B5A1: - { - unsigned short pixel = ((unsigned short *)image.data)[i]; - - pixels[i].x = (float)((pixel & 0b1111100000000000) >> 11)*(1.0f/31); - pixels[i].y = (float)((pixel & 0b0000011111000000) >> 6)*(1.0f/31); - pixels[i].z = (float)((pixel & 0b0000000000111110) >> 1)*(1.0f/31); - pixels[i].w = ((pixel & 0b0000000000000001) == 0)? 0.0f : 1.0f; - - } break; - case UNCOMPRESSED_R5G6B5: - { - unsigned short pixel = ((unsigned short *)image.data)[i]; - - pixels[i].x = (float)((pixel & 0b1111100000000000) >> 11)*(1.0f/31); - pixels[i].y = (float)((pixel & 0b0000011111100000) >> 5)*(1.0f/63); - pixels[i].z = (float)(pixel & 0b0000000000011111)*(1.0f/31); - pixels[i].w = 1.0f; - - } break; - case UNCOMPRESSED_R4G4B4A4: - { - unsigned short pixel = ((unsigned short *)image.data)[i]; + pixels[j*width + i].r = (int)((float)right.r*factor + (float)left.r*(1.f - factor)); + pixels[j*width + i].g = (int)((float)right.g*factor + (float)left.g*(1.f - factor)); + pixels[j*width + i].b = (int)((float)right.b*factor + (float)left.b*(1.f - factor)); + pixels[j*width + i].a = (int)((float)right.a*factor + (float)left.a*(1.f - factor)); + } + } - pixels[i].x = (float)((pixel & 0b1111000000000000) >> 12)*(1.0f/15); - pixels[i].y = (float)((pixel & 0b0000111100000000) >> 8)*(1.0f/15); - pixels[i].z = (float)((pixel & 0b0000000011110000) >> 4)*(1.0f/15); - pixels[i].w = (float)(pixel & 0b0000000000001111)*(1.0f/15); + Image image = LoadImageEx(pixels, width, height); + RL_FREE(pixels); - } break; - case UNCOMPRESSED_R8G8B8A8: - { - pixels[i].x = (float)((unsigned char *)image.data)[k]/255.0f; - pixels[i].y = (float)((unsigned char *)image.data)[k + 1]/255.0f; - pixels[i].z = (float)((unsigned char *)image.data)[k + 2]/255.0f; - pixels[i].w = (float)((unsigned char *)image.data)[k + 3]/255.0f; + return image; +} - k += 4; - } break; - case UNCOMPRESSED_R8G8B8: - { - pixels[i].x = (float)((unsigned char *)image.data)[k]/255.0f; - pixels[i].y = (float)((unsigned char *)image.data)[k + 1]/255.0f; - pixels[i].z = (float)((unsigned char *)image.data)[k + 2]/255.0f; - pixels[i].w = 1.0f; +// Generate image: radial gradient +Image GenImageGradientRadial(int width, int height, float density, Color inner, Color outer) +{ + Color *pixels = (Color *)RL_MALLOC(width*height*sizeof(Color)); + float radius = (width < height)? (float)width/2.0f : (float)height/2.0f; - k += 3; - } break; - case UNCOMPRESSED_R32: - { - pixels[i].x = ((float *)image.data)[k]; - pixels[i].y = 0.0f; - pixels[i].z = 0.0f; - pixels[i].w = 1.0f; + float centerX = (float)width/2.0f; + float centerY = (float)height/2.0f; - } break; - case UNCOMPRESSED_R32G32B32: - { - pixels[i].x = ((float *)image.data)[k]; - pixels[i].y = ((float *)image.data)[k + 1]; - pixels[i].z = ((float *)image.data)[k + 2]; - pixels[i].w = 1.0f; + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x++) + { + float dist = hypotf((float)x - centerX, (float)y - centerY); + float factor = (dist - radius*density)/(radius*(1.0f - density)); - k += 3; - } break; - case UNCOMPRESSED_R32G32B32A32: - { - pixels[i].x = ((float *)image.data)[k]; - pixels[i].y = ((float *)image.data)[k + 1]; - pixels[i].z = ((float *)image.data)[k + 2]; - pixels[i].w = ((float *)image.data)[k + 3]; + factor = (float)fmax(factor, 0.f); + factor = (float)fmin(factor, 1.f); // dist can be bigger than radius so we have to check - k += 4; - } - default: break; - } + pixels[y*width + x].r = (int)((float)outer.r*factor + (float)inner.r*(1.0f - factor)); + pixels[y*width + x].g = (int)((float)outer.g*factor + (float)inner.g*(1.0f - factor)); + pixels[y*width + x].b = (int)((float)outer.b*factor + (float)inner.b*(1.0f - factor)); + pixels[y*width + x].a = (int)((float)outer.a*factor + (float)inner.a*(1.0f - factor)); } } - return pixels; + Image image = LoadImageEx(pixels, width, height); + RL_FREE(pixels); + + return image; } -// Get image alpha border rectangle -Rectangle GetImageAlphaBorder(Image image, float threshold) +// Generate image: checked +Image GenImageChecked(int width, int height, int checksX, int checksY, Color col1, Color col2) { - Rectangle crop = { 0 }; - - Color *pixels = GetImageData(image); + Color *pixels = (Color *)RL_MALLOC(width*height*sizeof(Color)); - if (pixels != NULL) + for (int y = 0; y < height; y++) { - int xMin = 65536; // Define a big enough number - int xMax = 0; - int yMin = 65536; - int yMax = 0; - - for (int y = 0; y < image.height; y++) - { - for (int x = 0; x < image.width; x++) - { - if (pixels[y*image.width + x].a > (unsigned char)(threshold*255.0f)) - { - if (x < xMin) xMin = x; - if (x > xMax) xMax = x; - if (y < yMin) yMin = y; - if (y > yMax) yMax = y; - } - } - } - - // Check for empty blank image - if ((xMin != 65536) && (xMax != 65536)) + for (int x = 0; x < width; x++) { - crop = (Rectangle){ (float)xMin, (float)yMin, (float)((xMax + 1) - xMin), (float)((yMax + 1) - yMin) }; + if ((x/checksX + y/checksY)%2 == 0) pixels[y*width + x] = col1; + else pixels[y*width + x] = col2; } - - RL_FREE(pixels); } - return crop; + Image image = LoadImageEx(pixels, width, height); + RL_FREE(pixels); + + return image; } -// Get pixel data size in bytes (image or texture) -// NOTE: Size depends on pixel format -int GetPixelDataSize(int width, int height, int format) +// Generate image: white noise +Image GenImageWhiteNoise(int width, int height, float factor) { - int dataSize = 0; // Size in bytes - int bpp = 0; // Bits per pixel + Color *pixels = (Color *)RL_MALLOC(width*height*sizeof(Color)); - switch (format) + for (int i = 0; i < width*height; i++) { - case UNCOMPRESSED_GRAYSCALE: bpp = 8; break; - case UNCOMPRESSED_GRAY_ALPHA: - case UNCOMPRESSED_R5G6B5: - case UNCOMPRESSED_R5G5B5A1: - case UNCOMPRESSED_R4G4B4A4: bpp = 16; break; - case UNCOMPRESSED_R8G8B8A8: bpp = 32; break; - case UNCOMPRESSED_R8G8B8: bpp = 24; break; - case UNCOMPRESSED_R32: bpp = 32; break; - case UNCOMPRESSED_R32G32B32: bpp = 32*3; break; - case UNCOMPRESSED_R32G32B32A32: bpp = 32*4; break; - case COMPRESSED_DXT1_RGB: - case COMPRESSED_DXT1_RGBA: - case COMPRESSED_ETC1_RGB: - case COMPRESSED_ETC2_RGB: - case COMPRESSED_PVRT_RGB: - case COMPRESSED_PVRT_RGBA: bpp = 4; break; - case COMPRESSED_DXT3_RGBA: - case COMPRESSED_DXT5_RGBA: - case COMPRESSED_ETC2_EAC_RGBA: - case COMPRESSED_ASTC_4x4_RGBA: bpp = 8; break; - case COMPRESSED_ASTC_8x8_RGBA: bpp = 2; break; - default: break; + if (GetRandomValue(0, 99) < (int)(factor*100.0f)) pixels[i] = WHITE; + else pixels[i] = BLACK; } - dataSize = width*height*bpp/8; // Total data size in bytes + Image image = LoadImageEx(pixels, width, height); + RL_FREE(pixels); - return dataSize; + return image; } -// Get pixel data from GPU texture and return an Image -// NOTE: Compressed texture formats not supported -Image GetTextureData(Texture2D texture) +// Generate image: perlin noise +Image GenImagePerlinNoise(int width, int height, int offsetX, int offsetY, float scale) { - Image image = { 0 }; + Color *pixels = (Color *)RL_MALLOC(width*height*sizeof(Color)); - if (texture.format < 8) + for (int y = 0; y < height; y++) { - image.data = rlReadTexturePixels(texture); - - if (image.data != NULL) + for (int x = 0; x < width; x++) { - image.width = texture.width; - image.height = texture.height; - image.format = texture.format; - image.mipmaps = 1; + float nx = (float)(x + offsetX)*scale/(float)width; + float ny = (float)(y + offsetY)*scale/(float)height; -#if defined(GRAPHICS_API_OPENGL_ES2) - // NOTE: Data retrieved on OpenGL ES 2.0 should be RGBA, - // coming from FBO color buffer attachment, but it seems - // original texture format is retrieved on RPI... - image.format = UNCOMPRESSED_R8G8B8A8; -#endif - TRACELOG(LOG_INFO, "TEXTURE: [ID %i] Pixel data retrieved successfully", texture.id); - } - else TRACELOG(LOG_WARNING, "TEXTURE: [ID %i] Failed to retrieve pixel data", texture.id); - } - else TRACELOG(LOG_WARNING, "TEXTURE: [ID %i] Failed to retrieve compressed pixel data", texture.id); + // Typical values to start playing with: + // lacunarity = ~2.0 -- spacing between successive octaves (use exactly 2.0 for wrapping output) + // gain = 0.5 -- relative weighting applied to each successive octave + // octaves = 6 -- number of "octaves" of noise3() to sum - return image; -} + // NOTE: We need to translate the data from [-1..1] to [0..1] + float p = (stb_perlin_fbm_noise3(nx, ny, 1.0f, 2.0f, 0.5f, 6) + 1.0f)/2.0f; -// Get pixel data from GPU frontbuffer and return an Image (screenshot) -Image GetScreenData(void) -{ - Image image = { 0 }; + int intensity = (int)(p*255.0f); + pixels[y*width + x] = (Color){intensity, intensity, intensity, 255}; + } + } - image.width = GetScreenWidth(); - image.height = GetScreenHeight(); - image.mipmaps = 1; - image.format = UNCOMPRESSED_R8G8B8A8; - image.data = rlReadScreenPixels(image.width, image.height); + Image image = LoadImageEx(pixels, width, height); + RL_FREE(pixels); return image; } -// Update GPU texture with new data -// NOTE: pixels data must match texture.format -void UpdateTexture(Texture2D texture, const void *pixels) +// Generate image: cellular algorithm. Bigger tileSize means bigger cells +Image GenImageCellular(int width, int height, int tileSize) { - rlUpdateTexture(texture.id, texture.width, texture.height, texture.format, pixels); -} + Color *pixels = (Color *)RL_MALLOC(width*height*sizeof(Color)); -// Export image data to file -// NOTE: File format depends on fileName extension -void ExportImage(Image image, const char *fileName) -{ - int success = 0; + int seedsPerRow = width/tileSize; + int seedsPerCol = height/tileSize; + int seedsCount = seedsPerRow * seedsPerCol; -#if defined(SUPPORT_IMAGE_EXPORT) - // NOTE: Getting Color array as RGBA unsigned char values - unsigned char *imgData = (unsigned char *)GetImageData(image); + Vector2 *seeds = (Vector2 *)RL_MALLOC(seedsCount*sizeof(Vector2)); -#if defined(SUPPORT_FILEFORMAT_PNG) - if (IsFileExtension(fileName, ".png")) success = stbi_write_png(fileName, image.width, image.height, 4, imgData, image.width*4); -#else - if (false) {} -#endif -#if defined(SUPPORT_FILEFORMAT_BMP) - else if (IsFileExtension(fileName, ".bmp")) success = stbi_write_bmp(fileName, image.width, image.height, 4, imgData); -#endif -#if defined(SUPPORT_FILEFORMAT_TGA) - else if (IsFileExtension(fileName, ".tga")) success = stbi_write_tga(fileName, image.width, image.height, 4, imgData); -#endif -#if defined(SUPPORT_FILEFORMAT_JPG) - else if (IsFileExtension(fileName, ".jpg")) success = stbi_write_jpg(fileName, image.width, image.height, 4, imgData, 80); // JPG quality: between 1 and 100 -#endif -#if defined(SUPPORT_FILEFORMAT_KTX) - else if (IsFileExtension(fileName, ".ktx")) success = SaveKTX(image, fileName); -#endif - else if (IsFileExtension(fileName, ".raw")) + for (int i = 0; i < seedsCount; i++) { - // Export raw pixel data (without header) - // NOTE: It's up to the user to track image parameters - SaveFileData(fileName, image.data, GetPixelDataSize(image.width, image.height, image.format)); - success = true; + int y = (i/seedsPerRow)*tileSize + GetRandomValue(0, tileSize - 1); + int x = (i%seedsPerRow)*tileSize + GetRandomValue(0, tileSize - 1); + seeds[i] = (Vector2){ (float)x, (float)y}; } - RL_FREE(imgData); -#endif - - if (success != 0) TRACELOG(LOG_INFO, "FILEIO: [%s] Image exported successfully", fileName); - else TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to export image", fileName); -} + for (int y = 0; y < height; y++) + { + int tileY = y/tileSize; -// Export image as code file (.h) defining an array of bytes -void ExportImageAsCode(Image image, const char *fileName) -{ -#ifndef TEXT_BYTES_PER_LINE - #define TEXT_BYTES_PER_LINE 20 -#endif + for (int x = 0; x < width; x++) + { + int tileX = x/tileSize; - FILE *txtFile = fopen(fileName, "wt"); + float minDistance = (float)strtod("Inf", NULL); - if (txtFile != NULL) - { - char varFileName[256] = { 0 }; - int dataSize = GetPixelDataSize(image.width, image.height, image.format); + // Check all adjacent tiles + for (int i = -1; i < 2; i++) + { + if ((tileX + i < 0) || (tileX + i >= seedsPerRow)) continue; - fprintf(txtFile, "////////////////////////////////////////////////////////////////////////////////////////\n"); - fprintf(txtFile, "// //\n"); - fprintf(txtFile, "// ImageAsCode exporter v1.0 - Image pixel data exported as an array of bytes //\n"); - fprintf(txtFile, "// //\n"); - fprintf(txtFile, "// more info and bugs-report: github.com/raysan5/raylib //\n"); - fprintf(txtFile, "// feedback and support: ray[at]raylib.com //\n"); - fprintf(txtFile, "// //\n"); - fprintf(txtFile, "// Copyright (c) 2020 Ramon Santamaria (@raysan5) //\n"); - fprintf(txtFile, "// //\n"); - fprintf(txtFile, "////////////////////////////////////////////////////////////////////////////////////////\n\n"); + for (int j = -1; j < 2; j++) + { + if ((tileY + j < 0) || (tileY + j >= seedsPerCol)) continue; - // Get file name from path and convert variable name to uppercase - strcpy(varFileName, GetFileNameWithoutExt(fileName)); - for (int i = 0; varFileName[i] != '\0'; i++) if ((varFileName[i] >= 'a') && (varFileName[i] <= 'z')) { varFileName[i] = varFileName[i] - 32; } + Vector2 neighborSeed = seeds[(tileY + j)*seedsPerRow + tileX + i]; - // Add image information - fprintf(txtFile, "// Image data information\n"); - fprintf(txtFile, "#define %s_WIDTH %i\n", varFileName, image.width); - fprintf(txtFile, "#define %s_HEIGHT %i\n", varFileName, image.height); - fprintf(txtFile, "#define %s_FORMAT %i // raylib internal pixel format\n\n", varFileName, image.format); + float dist = (float)hypot(x - (int)neighborSeed.x, y - (int)neighborSeed.y); + minDistance = (float)fmin(minDistance, dist); + } + } - fprintf(txtFile, "static unsigned char %s_DATA[%i] = { ", varFileName, dataSize); - for (int i = 0; i < dataSize - 1; i++) fprintf(txtFile, ((i%TEXT_BYTES_PER_LINE == 0)? "0x%x,\n" : "0x%x, "), ((unsigned char *)image.data)[i]); - fprintf(txtFile, "0x%x };\n", ((unsigned char *)image.data)[dataSize - 1]); + // I made this up but it seems to give good results at all tile sizes + int intensity = (int)(minDistance*256.0f/tileSize); + if (intensity > 255) intensity = 255; - fclose(txtFile); + pixels[y*width + x] = (Color){ intensity, intensity, intensity, 255 }; + } } + + RL_FREE(seeds); + + Image image = LoadImageEx(pixels, width, height); + RL_FREE(pixels); + + return image; } +#endif // SUPPORT_IMAGE_GENERATION +//------------------------------------------------------------------------------------ +// Image manipulation functions +//------------------------------------------------------------------------------------ // Copy an image to a new image Image ImageCopy(Image image) { @@ -917,58 +724,52 @@ Image ImageFromImage(Image image, Rectangle rec) { Image result = ImageCopy(image); -#if defined(SUPPORT_IMAGE_MANIPULATION) ImageCrop(&result, rec); -#endif return result; } -// Convert image to POT (power-of-two) -// NOTE: It could be useful on OpenGL ES 2.0 (RPI, HTML5) -void ImageToPOT(Image *image, Color fillColor) +// Crop an image to area defined by a rectangle +// NOTE: Security checks are performed in case rectangle goes out of bounds +void ImageCrop(Image *image, Rectangle crop) { // Security check to avoid program crash if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; - // Calculate next power-of-two values - // NOTE: Just add the required amount of pixels at the right and bottom sides of image... - int potWidth = (int)powf(2, ceilf(logf((float)image->width)/logf(2))); - int potHeight = (int)powf(2, ceilf(logf((float)image->height)/logf(2))); + // Security checks to validate crop rectangle + if (crop.x < 0) { crop.width += crop.x; crop.x = 0; } + if (crop.y < 0) { crop.height += crop.y; crop.y = 0; } + if ((crop.x + crop.width) > image->width) crop.width = image->width - crop.x; + if ((crop.y + crop.height) > image->height) crop.height = image->height - crop.y; - // Check if POT texture generation is required (if texture is not already POT) - if ((potWidth != image->width) || (potHeight != image->height)) + if ((crop.x < image->width) && (crop.y < image->height)) { - Color *pixels = GetImageData(*image); // Get pixels data - Color *pixelsPOT = NULL; - - // Generate POT array from NPOT data - pixelsPOT = (Color *)RL_MALLOC(potWidth*potHeight*sizeof(Color)); + // Start the cropping process + Color *pixels = GetImageData(*image); // Get data as Color pixels array + Color *cropPixels = (Color *)RL_MALLOC((int)crop.width*(int)crop.height*sizeof(Color)); - for (int j = 0; j < potHeight; j++) + for (int j = (int)crop.y; j < (int)(crop.y + crop.height); j++) { - for (int i = 0; i < potWidth; i++) + for (int i = (int)crop.x; i < (int)(crop.x + crop.width); i++) { - if ((j < image->height) && (i < image->width)) pixelsPOT[j*potWidth + i] = pixels[j*image->width + i]; - else pixelsPOT[j*potWidth + i] = fillColor; + cropPixels[(j - (int)crop.y)*(int)crop.width + (i - (int)crop.x)] = pixels[j*image->width + i]; } } - RL_FREE(pixels); // Free pixels data - RL_FREE(image->data); // Free old image data + RL_FREE(pixels); - int format = image->format; // Store image data format to reconvert later + int format = image->format; - // NOTE: Image size changes, new width and height - *image = LoadImageEx(pixelsPOT, potWidth, potHeight); + UnloadImage(*image); - RL_FREE(pixelsPOT); // Free POT pixels data + *image = LoadImageEx(cropPixels, (int)crop.width, (int)crop.height); - ImageFormat(image, format); // Reconvert image to previous format - - // TODO: Verification required for log - TRACELOG(LOG_WARNING, "IMAGE: Converted to POT: (%ix%i) -> (%ix%i)", image->width, image->height, potWidth, potHeight); + RL_FREE(cropPixels); + + // Reformat 32bit RGBA image to original format + ImageFormat(image, format); } + else TRACELOG(LOG_WARNING, "IMAGE: Failed to crop, rectangle out of bounds"); } // Convert image data to desired format @@ -1147,227 +948,125 @@ void ImageFormat(Image *image, int newFormat) } } -// Apply alpha mask to image -// NOTE 1: Returned image is GRAY_ALPHA (16bit) or RGBA (32bit) -// NOTE 2: alphaMask should be same size as image -void ImageAlphaMask(Image *image, Image alphaMask) +// Convert image to POT (power-of-two) +// NOTE: It could be useful on OpenGL ES 2.0 (RPI, HTML5) +void ImageToPOT(Image *image, Color fillColor) { - if ((image->width != alphaMask.width) || (image->height != alphaMask.height)) - { - TRACELOG(LOG_WARNING, "IMAGE: Alpha mask must be same size as image"); - } - else if (image->format >= COMPRESSED_DXT1_RGB) - { - TRACELOG(LOG_WARNING, "IMAGE: Alpha mask can not be applied to compressed data formats"); - } - else - { - // Force mask to be Grayscale - Image mask = ImageCopy(alphaMask); - if (mask.format != UNCOMPRESSED_GRAYSCALE) ImageFormat(&mask, UNCOMPRESSED_GRAYSCALE); + // Security check to avoid program crash + if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; - // In case image is only grayscale, we just add alpha channel - if (image->format == UNCOMPRESSED_GRAYSCALE) - { - unsigned char *data = (unsigned char *)RL_MALLOC(image->width*image->height*2); + // Calculate next power-of-two values + // NOTE: Just add the required amount of pixels at the right and bottom sides of image... + int potWidth = (int)powf(2, ceilf(logf((float)image->width)/logf(2))); + int potHeight = (int)powf(2, ceilf(logf((float)image->height)/logf(2))); - // Apply alpha mask to alpha channel - for (int i = 0, k = 0; (i < mask.width*mask.height) || (i < image->width*image->height); i++, k += 2) - { - data[k] = ((unsigned char *)image->data)[i]; - data[k + 1] = ((unsigned char *)mask.data)[i]; - } + // Check if POT texture generation is required (if texture is not already POT) + if ((potWidth != image->width) || (potHeight != image->height)) + { + Color *pixels = GetImageData(*image); // Get pixels data + Color *pixelsPOT = NULL; - RL_FREE(image->data); - image->data = data; - image->format = UNCOMPRESSED_GRAY_ALPHA; - } - else - { - // Convert image to RGBA - if (image->format != UNCOMPRESSED_R8G8B8A8) ImageFormat(image, UNCOMPRESSED_R8G8B8A8); + // Generate POT array from NPOT data + pixelsPOT = (Color *)RL_MALLOC(potWidth*potHeight*sizeof(Color)); - // Apply alpha mask to alpha channel - for (int i = 0, k = 3; (i < mask.width*mask.height) || (i < image->width*image->height); i++, k += 4) + for (int j = 0; j < potHeight; j++) + { + for (int i = 0; i < potWidth; i++) { - ((unsigned char *)image->data)[k] = ((unsigned char *)mask.data)[i]; + if ((j < image->height) && (i < image->width)) pixelsPOT[j*potWidth + i] = pixels[j*image->width + i]; + else pixelsPOT[j*potWidth + i] = fillColor; } } - UnloadImage(mask); - } -} - -// Clear alpha channel to desired color -// NOTE: Threshold defines the alpha limit, 0.0f to 1.0f -void ImageAlphaClear(Image *image, Color color, float threshold) -{ - // Security check to avoid program crash - if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; - - Color *pixels = GetImageData(*image); + RL_FREE(pixels); // Free pixels data + RL_FREE(image->data); // Free old image data - for (int i = 0; i < image->width*image->height; i++) if (pixels[i].a <= (unsigned char)(threshold*255.0f)) pixels[i] = color; + int format = image->format; // Store image data format to reconvert later - UnloadImage(*image); + // NOTE: Image size changes, new width and height + *image = LoadImageEx(pixelsPOT, potWidth, potHeight); - int prevFormat = image->format; - *image = LoadImageEx(pixels, image->width, image->height); + RL_FREE(pixelsPOT); // Free POT pixels data - ImageFormat(image, prevFormat); - RL_FREE(pixels); + ImageFormat(image, format); // Reconvert image to previous format + + // TODO: Verification required for log + TRACELOG(LOG_WARNING, "IMAGE: Converted to POT: (%ix%i) -> (%ix%i)", image->width, image->height, potWidth, potHeight); + } } -// Premultiply alpha channel -void ImageAlphaPremultiply(Image *image) +#if defined(SUPPORT_IMAGE_MANIPULATION) +// Create an image from text (default font) +Image ImageText(const char *text, int fontSize, Color color) { - // Security check to avoid program crash - if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; - - float alpha = 0.0f; - Color *pixels = GetImageData(*image); - - for (int i = 0; i < image->width*image->height; i++) - { - alpha = (float)pixels[i].a/255.0f; - pixels[i].r = (unsigned char)((float)pixels[i].r*alpha); - pixels[i].g = (unsigned char)((float)pixels[i].g*alpha); - pixels[i].b = (unsigned char)((float)pixels[i].b*alpha); - } - - UnloadImage(*image); + int defaultFontSize = 10; // Default Font chars height in pixel + if (fontSize < defaultFontSize) fontSize = defaultFontSize; + int spacing = fontSize / defaultFontSize; - int prevFormat = image->format; - *image = LoadImageEx(pixels, image->width, image->height); + Image imText = ImageTextEx(GetFontDefault(), text, (float)fontSize, (float)spacing, color); - ImageFormat(image, prevFormat); - RL_FREE(pixels); + return imText; } -#if defined(SUPPORT_IMAGE_MANIPULATION) -// Load cubemap from image, multiple image cubemap layouts supported -TextureCubemap LoadTextureCubemap(Image image, int layoutType) +// Create an image from text (custom sprite font) +Image ImageTextEx(Font font, const char *text, float fontSize, float spacing, Color tint) { - TextureCubemap cubemap = { 0 }; + int length = (int)strlen(text); - if (layoutType == CUBEMAP_AUTO_DETECT) // Try to automatically guess layout type - { - // Check image width/height to determine the type of cubemap provided - if (image.width > image.height) - { - if ((image.width/6) == image.height) { layoutType = CUBEMAP_LINE_HORIZONTAL; cubemap.width = image.width/6; } - else if ((image.width/4) == (image.height/3)) { layoutType = CUBEMAP_CROSS_FOUR_BY_THREE; cubemap.width = image.width/4; } - else if (image.width >= (int)((float)image.height*1.85f)) { layoutType = CUBEMAP_PANORAMA; cubemap.width = image.width/4; } - } - else if (image.height > image.width) - { - if ((image.height/6) == image.width) { layoutType = CUBEMAP_LINE_VERTICAL; cubemap.width = image.height/6; } - else if ((image.width/3) == (image.height/4)) { layoutType = CUBEMAP_CROSS_THREE_BY_FOUR; cubemap.width = image.width/3; } - } + int textOffsetX = 0; // Image drawing position X + int textOffsetY = 0; // Offset between lines (on line break '\n') - cubemap.height = cubemap.width; - } + // NOTE: Text image is generated at font base size, later scaled to desired font size + Vector2 imSize = MeasureTextEx(font, text, (float)font.baseSize, spacing); - if (layoutType != CUBEMAP_AUTO_DETECT) + // Create image to store text + Image imText = GenImageColor((int)imSize.x, (int)imSize.y, BLANK); + + for (int i = 0; i < length; i++) { - int size = cubemap.width; + // Get next codepoint from byte string and glyph index in font + int codepointByteCount = 0; + int codepoint = GetNextCodepoint(&text[i], &codepointByteCount); + int index = GetGlyphIndex(font, codepoint); - Image faces = { 0 }; // Vertical column image - Rectangle faceRecs[6] = { 0 }; // Face source rectangles - for (int i = 0; i < 6; i++) faceRecs[i] = (Rectangle){ 0, 0, (float)size, (float)size }; + // NOTE: Normally we exit the decoding sequence as soon as a bad byte is found (and return 0x3f) + // but we need to draw all of the bad bytes using the '?' symbol moving one byte + if (codepoint == 0x3f) codepointByteCount = 1; - if (layoutType == CUBEMAP_LINE_VERTICAL) - { - faces = image; - for (int i = 0; i < 6; i++) faceRecs[i].y = (float)size*i; - } - else if (layoutType == CUBEMAP_PANORAMA) + if (codepoint == '\n') { - // TODO: Convert panorama image to square faces... - // Ref: https://github.com/denivip/panorama/blob/master/panorama.cpp + // NOTE: Fixed line spacing of 1.5 line-height + // TODO: Support custom line spacing defined by user + textOffsetY += (font.baseSize + font.baseSize/2); + textOffsetX = 0; } else { - if (layoutType == CUBEMAP_LINE_HORIZONTAL) for (int i = 0; i < 6; i++) faceRecs[i].x = (float)size*i; - else if (layoutType == CUBEMAP_CROSS_THREE_BY_FOUR) - { - faceRecs[0].x = (float)size; faceRecs[0].y = (float)size; - faceRecs[1].x = (float)size; faceRecs[1].y = (float)size*3; - faceRecs[2].x = (float)size; faceRecs[2].y = 0; - faceRecs[3].x = (float)size; faceRecs[3].y = (float)size*2; - faceRecs[4].x = 0; faceRecs[4].y = (float)size; - faceRecs[5].x = (float)size*2; faceRecs[5].y = (float)size; - } - else if (layoutType == CUBEMAP_CROSS_FOUR_BY_THREE) + if ((codepoint != ' ') && (codepoint != '\t')) { - faceRecs[0].x = (float)size*2; faceRecs[0].y = (float)size; - faceRecs[1].x = 0; faceRecs[1].y = (float)size; - faceRecs[2].x = (float)size; faceRecs[2].y = 0; - faceRecs[3].x = (float)size; faceRecs[3].y = (float)size*2; - faceRecs[4].x = (float)size; faceRecs[4].y = (float)size; - faceRecs[5].x = (float)size*3; faceRecs[5].y = (float)size; + Rectangle rec = { (float)(textOffsetX + font.chars[index].offsetX), (float)(textOffsetY + font.chars[index].offsetY), (float)font.recs[index].width, (float)font.recs[index].height }; + ImageDraw(&imText, font.chars[index].image, (Rectangle){ 0, 0, (float)font.chars[index].image.width, (float)font.chars[index].image.height }, rec, tint); } - // Convert image data to 6 faces in a vertical column, that's the optimum layout for loading - faces = GenImageColor(size, size*6, MAGENTA); - ImageFormat(&faces, image.format); - - // TODO: Image formating does not work with compressed textures! + if (font.chars[index].advanceX == 0) textOffsetX += (int)(font.recs[index].width + spacing); + else textOffsetX += font.chars[index].advanceX + (int)spacing; } - for (int i = 0; i < 6; i++) ImageDraw(&faces, image, faceRecs[i], (Rectangle){ 0, (float)size*i, (float)size, (float)size }, WHITE); - - cubemap.id = rlLoadTextureCubemap(faces.data, size, faces.format); - if (cubemap.id == 0) TRACELOG(LOG_WARNING, "IMAGE: Failed to load cubemap image"); - - UnloadImage(faces); + i += (codepointByteCount - 1); // Move text bytes counter to next codepoint } - else TRACELOG(LOG_WARNING, "IMAGE: Failed to detect cubemap image layout"); - - return cubemap; -} - -// Crop an image to area defined by a rectangle -// NOTE: Security checks are performed in case rectangle goes out of bounds -void ImageCrop(Image *image, Rectangle crop) -{ - // Security check to avoid program crash - if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; - - // Security checks to validate crop rectangle - if (crop.x < 0) { crop.width += crop.x; crop.x = 0; } - if (crop.y < 0) { crop.height += crop.y; crop.y = 0; } - if ((crop.x + crop.width) > image->width) crop.width = image->width - crop.x; - if ((crop.y + crop.height) > image->height) crop.height = image->height - crop.y; - if ((crop.x < image->width) && (crop.y < image->height)) + // Scale image depending on text size + if (fontSize > imSize.y) { - // Start the cropping process - Color *pixels = GetImageData(*image); // Get data as Color pixels array - Color *cropPixels = (Color *)RL_MALLOC((int)crop.width*(int)crop.height*sizeof(Color)); - - for (int j = (int)crop.y; j < (int)(crop.y + crop.height); j++) - { - for (int i = (int)crop.x; i < (int)(crop.x + crop.width); i++) - { - cropPixels[(j - (int)crop.y)*(int)crop.width + (i - (int)crop.x)] = pixels[j*image->width + i]; - } - } - - RL_FREE(pixels); - - int format = image->format; - - UnloadImage(*image); - - *image = LoadImageEx(cropPixels, (int)crop.width, (int)crop.height); - - RL_FREE(cropPixels); + float scaleFactor = fontSize/imSize.y; + TRACELOG(LOG_INFO, "IMAGE: Text scaled by factor: %f", scaleFactor); - // Reformat 32bit RGBA image to original format - ImageFormat(image, format); + // Using nearest-neighbor scaling algorithm for default font + if (font.texture.id == GetFontDefault().texture.id) ImageResizeNN(&imText, (int)(imSize.x*scaleFactor), (int)(imSize.y*scaleFactor)); + else ImageResize(&imText, (int)(imSize.x*scaleFactor), (int)(imSize.y*scaleFactor)); } - else TRACELOG(LOG_WARNING, "IMAGE: Failed to crop, rectangle out of bounds"); + + return imText; } // Crop image depending on alpha value @@ -1405,6 +1104,103 @@ void ImageAlphaCrop(Image *image, float threshold) if (!((xMax < xMin) || (yMax < yMin))) ImageCrop(image, crop); } +// Clear alpha channel to desired color +// NOTE: Threshold defines the alpha limit, 0.0f to 1.0f +void ImageAlphaClear(Image *image, Color color, float threshold) +{ + // Security check to avoid program crash + if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; + + Color *pixels = GetImageData(*image); + + for (int i = 0; i < image->width*image->height; i++) if (pixels[i].a <= (unsigned char)(threshold*255.0f)) pixels[i] = color; + + UnloadImage(*image); + + int prevFormat = image->format; + *image = LoadImageEx(pixels, image->width, image->height); + + ImageFormat(image, prevFormat); + RL_FREE(pixels); +} + +// Apply alpha mask to image +// NOTE 1: Returned image is GRAY_ALPHA (16bit) or RGBA (32bit) +// NOTE 2: alphaMask should be same size as image +void ImageAlphaMask(Image *image, Image alphaMask) +{ + if ((image->width != alphaMask.width) || (image->height != alphaMask.height)) + { + TRACELOG(LOG_WARNING, "IMAGE: Alpha mask must be same size as image"); + } + else if (image->format >= COMPRESSED_DXT1_RGB) + { + TRACELOG(LOG_WARNING, "IMAGE: Alpha mask can not be applied to compressed data formats"); + } + else + { + // Force mask to be Grayscale + Image mask = ImageCopy(alphaMask); + if (mask.format != UNCOMPRESSED_GRAYSCALE) ImageFormat(&mask, UNCOMPRESSED_GRAYSCALE); + + // In case image is only grayscale, we just add alpha channel + if (image->format == UNCOMPRESSED_GRAYSCALE) + { + unsigned char *data = (unsigned char *)RL_MALLOC(image->width*image->height*2); + + // Apply alpha mask to alpha channel + for (int i = 0, k = 0; (i < mask.width*mask.height) || (i < image->width*image->height); i++, k += 2) + { + data[k] = ((unsigned char *)image->data)[i]; + data[k + 1] = ((unsigned char *)mask.data)[i]; + } + + RL_FREE(image->data); + image->data = data; + image->format = UNCOMPRESSED_GRAY_ALPHA; + } + else + { + // Convert image to RGBA + if (image->format != UNCOMPRESSED_R8G8B8A8) ImageFormat(image, UNCOMPRESSED_R8G8B8A8); + + // Apply alpha mask to alpha channel + for (int i = 0, k = 3; (i < mask.width*mask.height) || (i < image->width*image->height); i++, k += 4) + { + ((unsigned char *)image->data)[k] = ((unsigned char *)mask.data)[i]; + } + } + + UnloadImage(mask); + } +} + +// Premultiply alpha channel +void ImageAlphaPremultiply(Image *image) +{ + // Security check to avoid program crash + if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; + + float alpha = 0.0f; + Color *pixels = GetImageData(*image); + + for (int i = 0; i < image->width*image->height; i++) + { + alpha = (float)pixels[i].a/255.0f; + pixels[i].r = (unsigned char)((float)pixels[i].r*alpha); + pixels[i].g = (unsigned char)((float)pixels[i].g*alpha); + pixels[i].b = (unsigned char)((float)pixels[i].b*alpha); + } + + UnloadImage(*image); + + int prevFormat = image->format; + *image = LoadImageEx(pixels, image->width, image->height); + + ImageFormat(image, prevFormat); + RL_FREE(pixels); +} + // Resize and image to new size // NOTE: Uses stb default scaling filters (both bicubic): // STBIR_DEFAULT_FILTER_UPSAMPLE STBIR_FILTER_CATMULLROM @@ -1728,487 +1524,6 @@ void ImageDither(Image *image, int rBpp, int gBpp, int bBpp, int aBpp) } } -// Extract color palette from image to maximum size -// NOTE: Memory allocated should be freed manually! -Color *ImageExtractPalette(Image image, int maxPaletteSize, int *extractCount) -{ - #define COLOR_EQUAL(col1, col2) ((col1.r == col2.r)&&(col1.g == col2.g)&&(col1.b == col2.b)&&(col1.a == col2.a)) - - Color *pixels = GetImageData(image); - Color *palette = (Color *)RL_MALLOC(maxPaletteSize*sizeof(Color)); - - int palCount = 0; - for (int i = 0; i < maxPaletteSize; i++) palette[i] = BLANK; // Set all colors to BLANK - - for (int i = 0; i < image.width*image.height; i++) - { - if (pixels[i].a > 0) - { - bool colorInPalette = false; - - // Check if the color is already on palette - for (int j = 0; j < maxPaletteSize; j++) - { - if (COLOR_EQUAL(pixels[i], palette[j])) - { - colorInPalette = true; - break; - } - } - - // Store color if not on the palette - if (!colorInPalette) - { - palette[palCount] = pixels[i]; // Add pixels[i] to palette - palCount++; - - // We reached the limit of colors supported by palette - if (palCount >= maxPaletteSize) - { - i = image.width*image.height; // Finish palette get - TRACELOG(LOG_WARNING, "IMAGE: Palette is greater than %i colors", maxPaletteSize); - } - } - } - } - - RL_FREE(pixels); - - *extractCount = palCount; - - return palette; -} - -// Draw an image (source) within an image (destination) -// NOTE: Color tint is applied to source image -void ImageDraw(Image *dst, Image src, Rectangle srcRec, Rectangle dstRec, Color tint) -{ - // Security check to avoid program crash - if ((dst->data == NULL) || (dst->width == 0) || (dst->height == 0) || - (src.data == NULL) || (src.width == 0) || (src.height == 0)) return; - - // Security checks to avoid size and rectangle issues (out of bounds) - // Check that srcRec is inside src image - if (srcRec.x < 0) srcRec.x = 0; - if (srcRec.y < 0) srcRec.y = 0; - - if ((srcRec.x + srcRec.width) > src.width) - { - srcRec.width = src.width - srcRec.x; - TRACELOG(LOG_WARNING, "IMAGE: Source rectangle width out of bounds, rescaled width: %i", srcRec.width); - } - - if ((srcRec.y + srcRec.height) > src.height) - { - srcRec.height = src.height - srcRec.y; - TRACELOG(LOG_WARNING, "IMAGE: Source rectangle height out of bounds, rescaled height: %i", srcRec.height); - } - - Image srcCopy = ImageCopy(src); // Make a copy of source image to work with it - - // Crop source image to desired source rectangle (if required) - if ((src.width != (int)srcRec.width) && (src.height != (int)srcRec.height)) ImageCrop(&srcCopy, srcRec); - - // Scale source image in case destination rec size is different than source rec size - if (((int)dstRec.width != (int)srcRec.width) || ((int)dstRec.height != (int)srcRec.height)) - { - ImageResize(&srcCopy, (int)dstRec.width, (int)dstRec.height); - } - - // Check that dstRec is inside dst image - // Allow negative position within destination with cropping - if (dstRec.x < 0) - { - ImageCrop(&srcCopy, (Rectangle) { -dstRec.x, 0, dstRec.width + dstRec.x, dstRec.height }); - dstRec.width = dstRec.width + dstRec.x; - dstRec.x = 0; - } - - if ((dstRec.x + dstRec.width) > dst->width) - { - ImageCrop(&srcCopy, (Rectangle) { 0, 0, dst->width - dstRec.x, dstRec.height }); - dstRec.width = dst->width - dstRec.x; - } - - if (dstRec.y < 0) - { - ImageCrop(&srcCopy, (Rectangle) { 0, -dstRec.y, dstRec.width, dstRec.height + dstRec.y }); - dstRec.height = dstRec.height + dstRec.y; - dstRec.y = 0; - } - - if ((dstRec.y + dstRec.height) > dst->height) - { - ImageCrop(&srcCopy, (Rectangle) { 0, 0, dstRec.width, dst->height - dstRec.y }); - dstRec.height = dst->height - dstRec.y; - } - - // Get image data as Color pixels array to work with it - Color *dstPixels = GetImageData(*dst); - Color *srcPixels = GetImageData(srcCopy); - - UnloadImage(srcCopy); // Source copy not required any more - - Vector4 fsrc, fdst, fout; // Normalized pixel data (ready for operation) - Vector4 ftint = ColorNormalize(tint); // Normalized color tint - - // Blit pixels, copy source image into destination - // TODO: Maybe out-of-bounds blitting could be considered here instead of so much cropping - for (int j = (int)dstRec.y; j < (int)(dstRec.y + dstRec.height); j++) - { - for (int i = (int)dstRec.x; i < (int)(dstRec.x + dstRec.width); i++) - { - // Alpha blending (https://en.wikipedia.org/wiki/Alpha_compositing) - - fdst = ColorNormalize(dstPixels[j*(int)dst->width + i]); - fsrc = ColorNormalize(srcPixels[(j - (int)dstRec.y)*(int)dstRec.width + (i - (int)dstRec.x)]); - - // Apply color tint to source image - fsrc.x *= ftint.x; fsrc.y *= ftint.y; fsrc.z *= ftint.z; fsrc.w *= ftint.w; - - fout.w = fsrc.w + fdst.w*(1.0f - fsrc.w); - - if (fout.w <= 0.0f) - { - fout.x = 0.0f; - fout.y = 0.0f; - fout.z = 0.0f; - } - else - { - fout.x = (fsrc.x*fsrc.w + fdst.x*fdst.w*(1 - fsrc.w))/fout.w; - fout.y = (fsrc.y*fsrc.w + fdst.y*fdst.w*(1 - fsrc.w))/fout.w; - fout.z = (fsrc.z*fsrc.w + fdst.z*fdst.w*(1 - fsrc.w))/fout.w; - } - - dstPixels[j*(int)dst->width + i] = (Color){ (unsigned char)(fout.x*255.0f), - (unsigned char)(fout.y*255.0f), - (unsigned char)(fout.z*255.0f), - (unsigned char)(fout.w*255.0f) }; - - // TODO: Support other blending options - } - } - - UnloadImage(*dst); - - *dst = LoadImageEx(dstPixels, (int)dst->width, (int)dst->height); - ImageFormat(dst, dst->format); - - RL_FREE(srcPixels); - RL_FREE(dstPixels); -} - -// Create an image from text (default font) -Image ImageText(const char *text, int fontSize, Color color) -{ - int defaultFontSize = 10; // Default Font chars height in pixel - if (fontSize < defaultFontSize) fontSize = defaultFontSize; - int spacing = fontSize / defaultFontSize; - - Image imText = ImageTextEx(GetFontDefault(), text, (float)fontSize, (float)spacing, color); - - return imText; -} - -// Create an image from text (custom sprite font) -Image ImageTextEx(Font font, const char *text, float fontSize, float spacing, Color tint) -{ - int length = (int)strlen(text); - - int textOffsetX = 0; // Image drawing position X - int textOffsetY = 0; // Offset between lines (on line break '\n') - - // NOTE: Text image is generated at font base size, later scaled to desired font size - Vector2 imSize = MeasureTextEx(font, text, (float)font.baseSize, spacing); - - // Create image to store text - Image imText = GenImageColor((int)imSize.x, (int)imSize.y, BLANK); - - for (int i = 0; i < length; i++) - { - // Get next codepoint from byte string and glyph index in font - int codepointByteCount = 0; - int codepoint = GetNextCodepoint(&text[i], &codepointByteCount); - int index = GetGlyphIndex(font, codepoint); - - // NOTE: Normally we exit the decoding sequence as soon as a bad byte is found (and return 0x3f) - // but we need to draw all of the bad bytes using the '?' symbol moving one byte - if (codepoint == 0x3f) codepointByteCount = 1; - - if (codepoint == '\n') - { - // NOTE: Fixed line spacing of 1.5 line-height - // TODO: Support custom line spacing defined by user - textOffsetY += (font.baseSize + font.baseSize/2); - textOffsetX = 0; - } - else - { - if ((codepoint != ' ') && (codepoint != '\t')) - { - Rectangle rec = { (float)(textOffsetX + font.chars[index].offsetX), (float)(textOffsetY + font.chars[index].offsetY), (float)font.recs[index].width, (float)font.recs[index].height }; - ImageDraw(&imText, font.chars[index].image, (Rectangle){ 0, 0, (float)font.chars[index].image.width, (float)font.chars[index].image.height }, rec, tint); - } - - if (font.chars[index].advanceX == 0) textOffsetX += (int)(font.recs[index].width + spacing); - else textOffsetX += font.chars[index].advanceX + (int)spacing; - } - - i += (codepointByteCount - 1); // Move text bytes counter to next codepoint - } - - // Scale image depending on text size - if (fontSize > imSize.y) - { - float scaleFactor = fontSize/imSize.y; - TRACELOG(LOG_INFO, "IMAGE: Text scaled by factor: %f", scaleFactor); - - // Using nearest-neighbor scaling algorithm for default font - if (font.texture.id == GetFontDefault().texture.id) ImageResizeNN(&imText, (int)(imSize.x*scaleFactor), (int)(imSize.y*scaleFactor)); - else ImageResize(&imText, (int)(imSize.x*scaleFactor), (int)(imSize.y*scaleFactor)); - } - - return imText; -} - -// Draw rectangle within an image -void ImageDrawRectangle(Image *dst, int posX, int posY, int width, int height, Color color) -{ - ImageDrawRectangleRec(dst, (Rectangle){ (float)posX, (float)posY, (float)width, (float)height }, color); -} - -// Draw rectangle within an image (Vector version) -void ImageDrawRectangleV(Image *dst, Vector2 position, Vector2 size, Color color) -{ - ImageDrawRectangle(dst, (int)position.x, (int)position.y, (int)size.x, (int)size.y, color); -} - -// Draw rectangle within an image -void ImageDrawRectangleRec(Image *dst, Rectangle rec, Color color) -{ - // Security check to avoid program crash - if ((dst->data == NULL) || (dst->width == 0) || (dst->height == 0)) return; - - Image imRec = GenImageColor((int)rec.width, (int)rec.height, color); - ImageDraw(dst, imRec, (Rectangle){ 0, 0, rec.width, rec.height }, rec, WHITE); - UnloadImage(imRec); -} - -// Draw rectangle lines within an image -void ImageDrawRectangleLines(Image *dst, Rectangle rec, int thick, Color color) -{ - ImageDrawRectangle(dst, (int)rec.x, (int)rec.y, (int)rec.width, thick, color); - ImageDrawRectangle(dst, (int)rec.x, (int)(rec.y + thick), thick, (int)(rec.height - thick*2), color); - ImageDrawRectangle(dst, (int)(rec.x + rec.width - thick), (int)(rec.y + thick), thick, (int)(rec.height - thick*2), color); - ImageDrawRectangle(dst, (int)rec.x, (int)(rec.y + rec.height - thick), (int)rec.width, thick, color); -} - -// Clear image background with given color -void ImageClearBackground(Image *dst, Color color) -{ - ImageDrawRectangle(dst, 0, 0, dst->width, dst->height, color); -} - -// Draw pixel within an image -// NOTE: Compressed image formats not supported -void ImageDrawPixel(Image *dst, int x, int y, Color color) -{ - // Security check to avoid program crash - if ((dst->data == NULL) || (x < 0) || (x >= dst->width) || (y < 0) || (y >= dst->height)) return; - - switch (dst->format) - { - case UNCOMPRESSED_GRAYSCALE: - { - // NOTE: Calculate grayscale equivalent color - Vector3 coln = { (float)color.r/255.0f, (float)color.g/255.0f, (float)color.b/255.0f }; - unsigned char gray = (unsigned char)((coln.x*0.299f + coln.y*0.587f + coln.z*0.114f)*255.0f); - - ((unsigned char *)dst->data)[y*dst->width + x] = gray; - - } break; - case UNCOMPRESSED_GRAY_ALPHA: - { - // NOTE: Calculate grayscale equivalent color - Vector3 coln = { (float)color.r/255.0f, (float)color.g/255.0f, (float)color.b/255.0f }; - unsigned char gray = (unsigned char)((coln.x*0.299f + coln.y*0.587f + coln.z*0.114f)*255.0f); - - ((unsigned char *)dst->data)[(y*dst->width + x)*2] = gray; - ((unsigned char *)dst->data)[(y*dst->width + x)*2 + 1] = color.a; - - } break; - case UNCOMPRESSED_R5G6B5: - { - // NOTE: Calculate R5G6B5 equivalent color - Vector3 coln = { (float)color.r/255.0f, (float)color.g/255.0f, (float)color.b/255.0f }; - - unsigned char r = (unsigned char)(round(coln.x*31.0f)); - unsigned char g = (unsigned char)(round(coln.y*63.0f)); - unsigned char b = (unsigned char)(round(coln.z*31.0f)); - - ((unsigned short *)dst->data)[y*dst->width + x] = (unsigned short)r << 11 | (unsigned short)g << 5 | (unsigned short)b; - - } break; - case UNCOMPRESSED_R5G5B5A1: - { - // NOTE: Calculate R5G5B5A1 equivalent color - Vector4 coln = { (float)color.r/255.0f, (float)color.g/255.0f, (float)color.b/255.0f, (float)color.a/255.0f }; - - unsigned char r = (unsigned char)(round(coln.x*31.0f)); - unsigned char g = (unsigned char)(round(coln.y*31.0f)); - unsigned char b = (unsigned char)(round(coln.z*31.0f)); - unsigned char a = (coln.w > ((float)R5G5B5A1_ALPHA_THRESHOLD/255.0f))? 1 : 0;; - - ((unsigned short *)dst->data)[y*dst->width + x] = (unsigned short)r << 11 | (unsigned short)g << 6 | (unsigned short)b << 1 | (unsigned short)a; - - } break; - case UNCOMPRESSED_R4G4B4A4: - { - // NOTE: Calculate R5G5B5A1 equivalent color - Vector4 coln = { (float)color.r/255.0f, (float)color.g/255.0f, (float)color.b/255.0f, (float)color.a/255.0f }; - - unsigned char r = (unsigned char)(round(coln.x*15.0f)); - unsigned char g = (unsigned char)(round(coln.y*15.0f)); - unsigned char b = (unsigned char)(round(coln.z*15.0f)); - unsigned char a = (unsigned char)(round(coln.w*15.0f)); - - ((unsigned short *)dst->data)[y*dst->width + x] = (unsigned short)r << 12 | (unsigned short)g << 8 | (unsigned short)b << 4 | (unsigned short)a; - - } break; - case UNCOMPRESSED_R8G8B8: - { - ((unsigned char *)dst->data)[(y*dst->width + x)*3] = color.r; - ((unsigned char *)dst->data)[(y*dst->width + x)*3 + 1] = color.g; - ((unsigned char *)dst->data)[(y*dst->width + x)*3 + 2] = color.b; - - } break; - case UNCOMPRESSED_R8G8B8A8: - { - ((unsigned char *)dst->data)[(y*dst->width + x)*4] = color.r; - ((unsigned char *)dst->data)[(y*dst->width + x)*4 + 1] = color.g; - ((unsigned char *)dst->data)[(y*dst->width + x)*4 + 2] = color.b; - ((unsigned char *)dst->data)[(y*dst->width + x)*4 + 3] = color.a; - - } break; - case UNCOMPRESSED_R32: - { - // NOTE: Calculate grayscale equivalent color (normalized to 32bit) - Vector3 coln = { (float)color.r/255.0f, (float)color.g/255.0f, (float)color.b/255.0f }; - - ((float *)dst->data)[y*dst->width + x] = coln.x*0.299f + coln.y*0.587f + coln.z*0.114f; - - } break; - case UNCOMPRESSED_R32G32B32: - { - // NOTE: Calculate R32G32B32 equivalent color (normalized to 32bit) - Vector3 coln = { (float)color.r/255.0f, (float)color.g/255.0f, (float)color.b/255.0f }; - - ((float *)dst->data)[(y*dst->width + x)*3] = coln.x; - ((float *)dst->data)[(y*dst->width + x)*3 + 1] = coln.y; - ((float *)dst->data)[(y*dst->width + x)*3 + 2] = coln.z; - } break; - case UNCOMPRESSED_R32G32B32A32: - { - // NOTE: Calculate R32G32B32A32 equivalent color (normalized to 32bit) - Vector4 coln = { (float)color.r/255.0f, (float)color.g/255.0f, (float)color.b/255.0f, (float)color.a/255.0f }; - - ((float *)dst->data)[(y*dst->width + x)*4] = coln.x; - ((float *)dst->data)[(y*dst->width + x)*4 + 1] = coln.y; - ((float *)dst->data)[(y*dst->width + x)*4 + 2] = coln.z; - ((float *)dst->data)[(y*dst->width + x)*4 + 3] = coln.w; - - } break; - default: break; - } -} - -// Draw pixel within an image (Vector version) -void ImageDrawPixelV(Image *dst, Vector2 position, Color color) -{ - ImageDrawPixel(dst, (int)position.x, (int)position.y, color); -} - -// Draw circle within an image -void ImageDrawCircle(Image *dst, int centerX, int centerY, int radius, Color color) -{ - int x = 0, y = radius; - int decesionParameter = 3 - 2*radius; - - while (y >= x) - { - ImageDrawPixel(dst, centerX + x, centerY + y, color); - ImageDrawPixel(dst, centerX - x, centerY + y, color); - ImageDrawPixel(dst, centerX + x, centerY - y, color); - ImageDrawPixel(dst, centerX - x, centerY - y, color); - ImageDrawPixel(dst, centerX + y, centerY + x, color); - ImageDrawPixel(dst, centerX - y, centerY + x, color); - ImageDrawPixel(dst, centerX + y, centerY - x, color); - ImageDrawPixel(dst, centerX - y, centerY - x, color); - x++; - - if (decesionParameter > 0) - { - y--; - decesionParameter = decesionParameter + 4*(x - y) + 10; - } - else decesionParameter = decesionParameter + 4*x + 6; - } -} - -// Draw circle within an image (Vector version) -void ImageDrawCircleV(Image *dst, Vector2 center, int radius, Color color) -{ - ImageDrawCircle(dst, (int)center.x, (int)center.y, radius, color); -} - -// Draw line within an image -void ImageDrawLine(Image *dst, int startPosX, int startPosY, int endPosX, int endPosY, Color color) -{ - int m = 2*(endPosY - startPosY); - int slopeError = m - (endPosX - startPosX); - - for (int x = startPosX, y = startPosY; x <= endPosX; x++) - { - ImageDrawPixel(dst, x, y, color); - slopeError += m; - - if (slopeError >= 0) - { - y++; - slopeError -= 2*(endPosX - startPosX); - } - } -} - -// Draw line within an image (Vector version) -void ImageDrawLineV(Image *dst, Vector2 start, Vector2 end, Color color) -{ - ImageDrawLine(dst, (int)start.x, (int)start.y, (int)end.x, (int)end.y, color); -} - -// Draw text (default font) within an image (destination) -void ImageDrawText(Image *dst, const char *text, int posX, int posY, int fontSize, Color color) -{ - Vector2 position = { (float)posX, (float)posY }; - - // NOTE: For default font, sapcing is set to desired font size / default font size (10) - ImageDrawTextEx(dst, GetFontDefault(), text, position, (float)fontSize, (float)fontSize/10, color); -} - -// Draw text (custom sprite font) within an image (destination) -void ImageDrawTextEx(Image *dst, Font font, const char *text, Vector2 position, float fontSize, float spacing, Color tint) -{ - Image imText = ImageTextEx(font, text, fontSize, spacing, tint); - - Rectangle srcRec = { 0.0f, 0.0f, (float)imText.width, (float)imText.height }; - Rectangle dstRec = { position.x, position.y, (float)imText.width, (float)imText.height }; - - ImageDraw(dst, imText, srcRec, dstRec, WHITE); - - UnloadImage(imText); -} - // Flip image vertically void ImageFlipVertical(Image *image) { @@ -2525,228 +1840,890 @@ void ImageColorReplace(Image *image, Color color, Color replace) } #endif // SUPPORT_IMAGE_MANIPULATION -// Generate image: plain color -Image GenImageColor(int width, int height, Color color) +// Get pixel data from image in the form of Color struct array +Color *GetImageData(Image image) { - Color *pixels = (Color *)RL_CALLOC(width*height, sizeof(Color)); + if ((image.width == 0) || (image.height == 0)) return NULL; - for (int i = 0; i < width*height; i++) pixels[i] = color; + Color *pixels = (Color *)RL_MALLOC(image.width*image.height*sizeof(Color)); - Image image = LoadImageEx(pixels, width, height); + if (image.format >= COMPRESSED_DXT1_RGB) TRACELOG(LOG_WARNING, "IMAGE: Pixel data retrieval not supported for compressed image formats"); + else + { + if ((image.format == UNCOMPRESSED_R32) || + (image.format == UNCOMPRESSED_R32G32B32) || + (image.format == UNCOMPRESSED_R32G32B32A32)) TRACELOG(LOG_WARNING, "IMAGE: Pixel format converted from 32bit to 8bit per channel"); - RL_FREE(pixels); + for (int i = 0, k = 0; i < image.width*image.height; i++) + { + switch (image.format) + { + case UNCOMPRESSED_GRAYSCALE: + { + pixels[i].r = ((unsigned char *)image.data)[i]; + pixels[i].g = ((unsigned char *)image.data)[i]; + pixels[i].b = ((unsigned char *)image.data)[i]; + pixels[i].a = 255; - return image; + } break; + case UNCOMPRESSED_GRAY_ALPHA: + { + pixels[i].r = ((unsigned char *)image.data)[k]; + pixels[i].g = ((unsigned char *)image.data)[k]; + pixels[i].b = ((unsigned char *)image.data)[k]; + pixels[i].a = ((unsigned char *)image.data)[k + 1]; + + k += 2; + } break; + case UNCOMPRESSED_R5G5B5A1: + { + unsigned short pixel = ((unsigned short *)image.data)[i]; + + pixels[i].r = (unsigned char)((float)((pixel & 0b1111100000000000) >> 11)*(255/31)); + pixels[i].g = (unsigned char)((float)((pixel & 0b0000011111000000) >> 6)*(255/31)); + pixels[i].b = (unsigned char)((float)((pixel & 0b0000000000111110) >> 1)*(255/31)); + pixels[i].a = (unsigned char)((pixel & 0b0000000000000001)*255); + + } break; + case UNCOMPRESSED_R5G6B5: + { + unsigned short pixel = ((unsigned short *)image.data)[i]; + + pixels[i].r = (unsigned char)((float)((pixel & 0b1111100000000000) >> 11)*(255/31)); + pixels[i].g = (unsigned char)((float)((pixel & 0b0000011111100000) >> 5)*(255/63)); + pixels[i].b = (unsigned char)((float)(pixel & 0b0000000000011111)*(255/31)); + pixels[i].a = 255; + + } break; + case UNCOMPRESSED_R4G4B4A4: + { + unsigned short pixel = ((unsigned short *)image.data)[i]; + + pixels[i].r = (unsigned char)((float)((pixel & 0b1111000000000000) >> 12)*(255/15)); + pixels[i].g = (unsigned char)((float)((pixel & 0b0000111100000000) >> 8)*(255/15)); + pixels[i].b = (unsigned char)((float)((pixel & 0b0000000011110000) >> 4)*(255/15)); + pixels[i].a = (unsigned char)((float)(pixel & 0b0000000000001111)*(255/15)); + + } break; + case UNCOMPRESSED_R8G8B8A8: + { + pixels[i].r = ((unsigned char *)image.data)[k]; + pixels[i].g = ((unsigned char *)image.data)[k + 1]; + pixels[i].b = ((unsigned char *)image.data)[k + 2]; + pixels[i].a = ((unsigned char *)image.data)[k + 3]; + + k += 4; + } break; + case UNCOMPRESSED_R8G8B8: + { + pixels[i].r = (unsigned char)((unsigned char *)image.data)[k]; + pixels[i].g = (unsigned char)((unsigned char *)image.data)[k + 1]; + pixels[i].b = (unsigned char)((unsigned char *)image.data)[k + 2]; + pixels[i].a = 255; + + k += 3; + } break; + case UNCOMPRESSED_R32: + { + pixels[i].r = (unsigned char)(((float *)image.data)[k]*255.0f); + pixels[i].g = 0; + pixels[i].b = 0; + pixels[i].a = 255; + + } break; + case UNCOMPRESSED_R32G32B32: + { + pixels[i].r = (unsigned char)(((float *)image.data)[k]*255.0f); + pixels[i].g = (unsigned char)(((float *)image.data)[k + 1]*255.0f); + pixels[i].b = (unsigned char)(((float *)image.data)[k + 2]*255.0f); + pixels[i].a = 255; + + k += 3; + } break; + case UNCOMPRESSED_R32G32B32A32: + { + pixels[i].r = (unsigned char)(((float *)image.data)[k]*255.0f); + pixels[i].g = (unsigned char)(((float *)image.data)[k]*255.0f); + pixels[i].b = (unsigned char)(((float *)image.data)[k]*255.0f); + pixels[i].a = (unsigned char)(((float *)image.data)[k]*255.0f); + + k += 4; + } break; + default: break; + } + } + } + + return pixels; } -#if defined(SUPPORT_IMAGE_GENERATION) -// Generate image: vertical gradient -Image GenImageGradientV(int width, int height, Color top, Color bottom) +// Extract color palette from image to maximum size +// NOTE: Memory allocated should be freed manually! +Color *ImageExtractPalette(Image image, int maxPaletteSize, int *extractCount) { - Color *pixels = (Color *)RL_MALLOC(width*height*sizeof(Color)); + #define COLOR_EQUAL(col1, col2) ((col1.r == col2.r)&&(col1.g == col2.g)&&(col1.b == col2.b)&&(col1.a == col2.a)) - for (int j = 0; j < height; j++) + Color *pixels = GetImageData(image); + Color *palette = (Color *)RL_MALLOC(maxPaletteSize*sizeof(Color)); + + int palCount = 0; + for (int i = 0; i < maxPaletteSize; i++) palette[i] = BLANK; // Set all colors to BLANK + + for (int i = 0; i < image.width*image.height; i++) { - float factor = (float)j/(float)height; - for (int i = 0; i < width; i++) + if (pixels[i].a > 0) { - pixels[j*width + i].r = (int)((float)bottom.r*factor + (float)top.r*(1.f - factor)); - pixels[j*width + i].g = (int)((float)bottom.g*factor + (float)top.g*(1.f - factor)); - pixels[j*width + i].b = (int)((float)bottom.b*factor + (float)top.b*(1.f - factor)); - pixels[j*width + i].a = (int)((float)bottom.a*factor + (float)top.a*(1.f - factor)); + bool colorInPalette = false; + + // Check if the color is already on palette + for (int j = 0; j < maxPaletteSize; j++) + { + if (COLOR_EQUAL(pixels[i], palette[j])) + { + colorInPalette = true; + break; + } + } + + // Store color if not on the palette + if (!colorInPalette) + { + palette[palCount] = pixels[i]; // Add pixels[i] to palette + palCount++; + + // We reached the limit of colors supported by palette + if (palCount >= maxPaletteSize) + { + i = image.width*image.height; // Finish palette get + TRACELOG(LOG_WARNING, "IMAGE: Palette is greater than %i colors", maxPaletteSize); + } + } } } - Image image = LoadImageEx(pixels, width, height); RL_FREE(pixels); - return image; + *extractCount = palCount; + + return palette; } -// Generate image: horizontal gradient -Image GenImageGradientH(int width, int height, Color left, Color right) +// Get pixel data from image as Vector4 array (float normalized) +Vector4 *GetImageDataNormalized(Image image) { - Color *pixels = (Color *)RL_MALLOC(width*height*sizeof(Color)); + Vector4 *pixels = (Vector4 *)RL_MALLOC(image.width*image.height*sizeof(Vector4)); - for (int i = 0; i < width; i++) + if (image.format >= COMPRESSED_DXT1_RGB) TRACELOG(LOG_WARNING, "IMAGE: Pixel data retrieval not supported for compressed image formats"); + else { - float factor = (float)i/(float)width; - for (int j = 0; j < height; j++) + for (int i = 0, k = 0; i < image.width*image.height; i++) { - pixels[j*width + i].r = (int)((float)right.r*factor + (float)left.r*(1.f - factor)); - pixels[j*width + i].g = (int)((float)right.g*factor + (float)left.g*(1.f - factor)); - pixels[j*width + i].b = (int)((float)right.b*factor + (float)left.b*(1.f - factor)); - pixels[j*width + i].a = (int)((float)right.a*factor + (float)left.a*(1.f - factor)); + switch (image.format) + { + case UNCOMPRESSED_GRAYSCALE: + { + pixels[i].x = (float)((unsigned char *)image.data)[i]/255.0f; + pixels[i].y = (float)((unsigned char *)image.data)[i]/255.0f; + pixels[i].z = (float)((unsigned char *)image.data)[i]/255.0f; + pixels[i].w = 1.0f; + + } break; + case UNCOMPRESSED_GRAY_ALPHA: + { + pixels[i].x = (float)((unsigned char *)image.data)[k]/255.0f; + pixels[i].y = (float)((unsigned char *)image.data)[k]/255.0f; + pixels[i].z = (float)((unsigned char *)image.data)[k]/255.0f; + pixels[i].w = (float)((unsigned char *)image.data)[k + 1]/255.0f; + + k += 2; + } break; + case UNCOMPRESSED_R5G5B5A1: + { + unsigned short pixel = ((unsigned short *)image.data)[i]; + + pixels[i].x = (float)((pixel & 0b1111100000000000) >> 11)*(1.0f/31); + pixels[i].y = (float)((pixel & 0b0000011111000000) >> 6)*(1.0f/31); + pixels[i].z = (float)((pixel & 0b0000000000111110) >> 1)*(1.0f/31); + pixels[i].w = ((pixel & 0b0000000000000001) == 0)? 0.0f : 1.0f; + + } break; + case UNCOMPRESSED_R5G6B5: + { + unsigned short pixel = ((unsigned short *)image.data)[i]; + + pixels[i].x = (float)((pixel & 0b1111100000000000) >> 11)*(1.0f/31); + pixels[i].y = (float)((pixel & 0b0000011111100000) >> 5)*(1.0f/63); + pixels[i].z = (float)(pixel & 0b0000000000011111)*(1.0f/31); + pixels[i].w = 1.0f; + + } break; + case UNCOMPRESSED_R4G4B4A4: + { + unsigned short pixel = ((unsigned short *)image.data)[i]; + + pixels[i].x = (float)((pixel & 0b1111000000000000) >> 12)*(1.0f/15); + pixels[i].y = (float)((pixel & 0b0000111100000000) >> 8)*(1.0f/15); + pixels[i].z = (float)((pixel & 0b0000000011110000) >> 4)*(1.0f/15); + pixels[i].w = (float)(pixel & 0b0000000000001111)*(1.0f/15); + + } break; + case UNCOMPRESSED_R8G8B8A8: + { + pixels[i].x = (float)((unsigned char *)image.data)[k]/255.0f; + pixels[i].y = (float)((unsigned char *)image.data)[k + 1]/255.0f; + pixels[i].z = (float)((unsigned char *)image.data)[k + 2]/255.0f; + pixels[i].w = (float)((unsigned char *)image.data)[k + 3]/255.0f; + + k += 4; + } break; + case UNCOMPRESSED_R8G8B8: + { + pixels[i].x = (float)((unsigned char *)image.data)[k]/255.0f; + pixels[i].y = (float)((unsigned char *)image.data)[k + 1]/255.0f; + pixels[i].z = (float)((unsigned char *)image.data)[k + 2]/255.0f; + pixels[i].w = 1.0f; + + k += 3; + } break; + case UNCOMPRESSED_R32: + { + pixels[i].x = ((float *)image.data)[k]; + pixels[i].y = 0.0f; + pixels[i].z = 0.0f; + pixels[i].w = 1.0f; + + } break; + case UNCOMPRESSED_R32G32B32: + { + pixels[i].x = ((float *)image.data)[k]; + pixels[i].y = ((float *)image.data)[k + 1]; + pixels[i].z = ((float *)image.data)[k + 2]; + pixels[i].w = 1.0f; + + k += 3; + } break; + case UNCOMPRESSED_R32G32B32A32: + { + pixels[i].x = ((float *)image.data)[k]; + pixels[i].y = ((float *)image.data)[k + 1]; + pixels[i].z = ((float *)image.data)[k + 2]; + pixels[i].w = ((float *)image.data)[k + 3]; + + k += 4; + } + default: break; + } } } - Image image = LoadImageEx(pixels, width, height); - RL_FREE(pixels); - - return image; + return pixels; } -// Generate image: radial gradient -Image GenImageGradientRadial(int width, int height, float density, Color inner, Color outer) +// Get image alpha border rectangle +Rectangle GetImageAlphaBorder(Image image, float threshold) { - Color *pixels = (Color *)RL_MALLOC(width*height*sizeof(Color)); - float radius = (width < height)? (float)width/2.0f : (float)height/2.0f; + Rectangle crop = { 0 }; - float centerX = (float)width/2.0f; - float centerY = (float)height/2.0f; + Color *pixels = GetImageData(image); - for (int y = 0; y < height; y++) + if (pixels != NULL) { - for (int x = 0; x < width; x++) - { - float dist = hypotf((float)x - centerX, (float)y - centerY); - float factor = (dist - radius*density)/(radius*(1.0f - density)); + int xMin = 65536; // Define a big enough number + int xMax = 0; + int yMin = 65536; + int yMax = 0; - factor = (float)fmax(factor, 0.f); - factor = (float)fmin(factor, 1.f); // dist can be bigger than radius so we have to check + for (int y = 0; y < image.height; y++) + { + for (int x = 0; x < image.width; x++) + { + if (pixels[y*image.width + x].a > (unsigned char)(threshold*255.0f)) + { + if (x < xMin) xMin = x; + if (x > xMax) xMax = x; + if (y < yMin) yMin = y; + if (y > yMax) yMax = y; + } + } + } - pixels[y*width + x].r = (int)((float)outer.r*factor + (float)inner.r*(1.0f - factor)); - pixels[y*width + x].g = (int)((float)outer.g*factor + (float)inner.g*(1.0f - factor)); - pixels[y*width + x].b = (int)((float)outer.b*factor + (float)inner.b*(1.0f - factor)); - pixels[y*width + x].a = (int)((float)outer.a*factor + (float)inner.a*(1.0f - factor)); + // Check for empty blank image + if ((xMin != 65536) && (xMax != 65536)) + { + crop = (Rectangle){ (float)xMin, (float)yMin, (float)((xMax + 1) - xMin), (float)((yMax + 1) - yMin) }; } + + RL_FREE(pixels); } - Image image = LoadImageEx(pixels, width, height); - RL_FREE(pixels); + return crop; +} - return image; +//------------------------------------------------------------------------------------ +// Image drawing functions +//------------------------------------------------------------------------------------ +#if defined(SUPPORT_IMAGE_DRAWING) +// Clear image background with given color +void ImageClearBackground(Image *dst, Color color) +{ + ImageDrawRectangle(dst, 0, 0, dst->width, dst->height, color); } -// Generate image: checked -Image GenImageChecked(int width, int height, int checksX, int checksY, Color col1, Color col2) +// Draw pixel within an image +// NOTE: Compressed image formats not supported +void ImageDrawPixel(Image *dst, int x, int y, Color color) { - Color *pixels = (Color *)RL_MALLOC(width*height*sizeof(Color)); + // Security check to avoid program crash + if ((dst->data == NULL) || (x < 0) || (x >= dst->width) || (y < 0) || (y >= dst->height)) return; + + switch (dst->format) + { + case UNCOMPRESSED_GRAYSCALE: + { + // NOTE: Calculate grayscale equivalent color + Vector3 coln = { (float)color.r/255.0f, (float)color.g/255.0f, (float)color.b/255.0f }; + unsigned char gray = (unsigned char)((coln.x*0.299f + coln.y*0.587f + coln.z*0.114f)*255.0f); + + ((unsigned char *)dst->data)[y*dst->width + x] = gray; + + } break; + case UNCOMPRESSED_GRAY_ALPHA: + { + // NOTE: Calculate grayscale equivalent color + Vector3 coln = { (float)color.r/255.0f, (float)color.g/255.0f, (float)color.b/255.0f }; + unsigned char gray = (unsigned char)((coln.x*0.299f + coln.y*0.587f + coln.z*0.114f)*255.0f); + + ((unsigned char *)dst->data)[(y*dst->width + x)*2] = gray; + ((unsigned char *)dst->data)[(y*dst->width + x)*2 + 1] = color.a; - for (int y = 0; y < height; y++) + } break; + case UNCOMPRESSED_R5G6B5: + { + // NOTE: Calculate R5G6B5 equivalent color + Vector3 coln = { (float)color.r/255.0f, (float)color.g/255.0f, (float)color.b/255.0f }; + + unsigned char r = (unsigned char)(round(coln.x*31.0f)); + unsigned char g = (unsigned char)(round(coln.y*63.0f)); + unsigned char b = (unsigned char)(round(coln.z*31.0f)); + + ((unsigned short *)dst->data)[y*dst->width + x] = (unsigned short)r << 11 | (unsigned short)g << 5 | (unsigned short)b; + + } break; + case UNCOMPRESSED_R5G5B5A1: + { + // NOTE: Calculate R5G5B5A1 equivalent color + Vector4 coln = { (float)color.r/255.0f, (float)color.g/255.0f, (float)color.b/255.0f, (float)color.a/255.0f }; + + unsigned char r = (unsigned char)(round(coln.x*31.0f)); + unsigned char g = (unsigned char)(round(coln.y*31.0f)); + unsigned char b = (unsigned char)(round(coln.z*31.0f)); + unsigned char a = (coln.w > ((float)R5G5B5A1_ALPHA_THRESHOLD/255.0f))? 1 : 0;; + + ((unsigned short *)dst->data)[y*dst->width + x] = (unsigned short)r << 11 | (unsigned short)g << 6 | (unsigned short)b << 1 | (unsigned short)a; + + } break; + case UNCOMPRESSED_R4G4B4A4: + { + // NOTE: Calculate R5G5B5A1 equivalent color + Vector4 coln = { (float)color.r/255.0f, (float)color.g/255.0f, (float)color.b/255.0f, (float)color.a/255.0f }; + + unsigned char r = (unsigned char)(round(coln.x*15.0f)); + unsigned char g = (unsigned char)(round(coln.y*15.0f)); + unsigned char b = (unsigned char)(round(coln.z*15.0f)); + unsigned char a = (unsigned char)(round(coln.w*15.0f)); + + ((unsigned short *)dst->data)[y*dst->width + x] = (unsigned short)r << 12 | (unsigned short)g << 8 | (unsigned short)b << 4 | (unsigned short)a; + + } break; + case UNCOMPRESSED_R8G8B8: + { + ((unsigned char *)dst->data)[(y*dst->width + x)*3] = color.r; + ((unsigned char *)dst->data)[(y*dst->width + x)*3 + 1] = color.g; + ((unsigned char *)dst->data)[(y*dst->width + x)*3 + 2] = color.b; + + } break; + case UNCOMPRESSED_R8G8B8A8: + { + ((unsigned char *)dst->data)[(y*dst->width + x)*4] = color.r; + ((unsigned char *)dst->data)[(y*dst->width + x)*4 + 1] = color.g; + ((unsigned char *)dst->data)[(y*dst->width + x)*4 + 2] = color.b; + ((unsigned char *)dst->data)[(y*dst->width + x)*4 + 3] = color.a; + + } break; + case UNCOMPRESSED_R32: + { + // NOTE: Calculate grayscale equivalent color (normalized to 32bit) + Vector3 coln = { (float)color.r/255.0f, (float)color.g/255.0f, (float)color.b/255.0f }; + + ((float *)dst->data)[y*dst->width + x] = coln.x*0.299f + coln.y*0.587f + coln.z*0.114f; + + } break; + case UNCOMPRESSED_R32G32B32: + { + // NOTE: Calculate R32G32B32 equivalent color (normalized to 32bit) + Vector3 coln = { (float)color.r/255.0f, (float)color.g/255.0f, (float)color.b/255.0f }; + + ((float *)dst->data)[(y*dst->width + x)*3] = coln.x; + ((float *)dst->data)[(y*dst->width + x)*3 + 1] = coln.y; + ((float *)dst->data)[(y*dst->width + x)*3 + 2] = coln.z; + } break; + case UNCOMPRESSED_R32G32B32A32: + { + // NOTE: Calculate R32G32B32A32 equivalent color (normalized to 32bit) + Vector4 coln = { (float)color.r/255.0f, (float)color.g/255.0f, (float)color.b/255.0f, (float)color.a/255.0f }; + + ((float *)dst->data)[(y*dst->width + x)*4] = coln.x; + ((float *)dst->data)[(y*dst->width + x)*4 + 1] = coln.y; + ((float *)dst->data)[(y*dst->width + x)*4 + 2] = coln.z; + ((float *)dst->data)[(y*dst->width + x)*4 + 3] = coln.w; + + } break; + default: break; + } +} + +// Draw pixel within an image (Vector version) +void ImageDrawPixelV(Image *dst, Vector2 position, Color color) +{ + ImageDrawPixel(dst, (int)position.x, (int)position.y, color); +} + +// Draw line within an image +void ImageDrawLine(Image *dst, int startPosX, int startPosY, int endPosX, int endPosY, Color color) +{ + int m = 2*(endPosY - startPosY); + int slopeError = m - (endPosX - startPosX); + + for (int x = startPosX, y = startPosY; x <= endPosX; x++) { - for (int x = 0; x < width; x++) + ImageDrawPixel(dst, x, y, color); + slopeError += m; + + if (slopeError >= 0) { - if ((x/checksX + y/checksY)%2 == 0) pixels[y*width + x] = col1; - else pixels[y*width + x] = col2; + y++; + slopeError -= 2*(endPosX - startPosX); } } +} - Image image = LoadImageEx(pixels, width, height); - RL_FREE(pixels); - - return image; +// Draw line within an image (Vector version) +void ImageDrawLineV(Image *dst, Vector2 start, Vector2 end, Color color) +{ + ImageDrawLine(dst, (int)start.x, (int)start.y, (int)end.x, (int)end.y, color); } -// Generate image: white noise -Image GenImageWhiteNoise(int width, int height, float factor) +// Draw circle within an image +void ImageDrawCircle(Image *dst, int centerX, int centerY, int radius, Color color) { - Color *pixels = (Color *)RL_MALLOC(width*height*sizeof(Color)); + int x = 0, y = radius; + int decesionParameter = 3 - 2*radius; - for (int i = 0; i < width*height; i++) + while (y >= x) { - if (GetRandomValue(0, 99) < (int)(factor*100.0f)) pixels[i] = WHITE; - else pixels[i] = BLACK; + ImageDrawPixel(dst, centerX + x, centerY + y, color); + ImageDrawPixel(dst, centerX - x, centerY + y, color); + ImageDrawPixel(dst, centerX + x, centerY - y, color); + ImageDrawPixel(dst, centerX - x, centerY - y, color); + ImageDrawPixel(dst, centerX + y, centerY + x, color); + ImageDrawPixel(dst, centerX - y, centerY + x, color); + ImageDrawPixel(dst, centerX + y, centerY - x, color); + ImageDrawPixel(dst, centerX - y, centerY - x, color); + x++; + + if (decesionParameter > 0) + { + y--; + decesionParameter = decesionParameter + 4*(x - y) + 10; + } + else decesionParameter = decesionParameter + 4*x + 6; } +} - Image image = LoadImageEx(pixels, width, height); - RL_FREE(pixels); +// Draw circle within an image (Vector version) +void ImageDrawCircleV(Image *dst, Vector2 center, int radius, Color color) +{ + ImageDrawCircle(dst, (int)center.x, (int)center.y, radius, color); +} - return image; +// Draw rectangle within an image +void ImageDrawRectangle(Image *dst, int posX, int posY, int width, int height, Color color) +{ + ImageDrawRectangleRec(dst, (Rectangle){ (float)posX, (float)posY, (float)width, (float)height }, color); } -// Generate image: perlin noise -Image GenImagePerlinNoise(int width, int height, int offsetX, int offsetY, float scale) +// Draw rectangle within an image (Vector version) +void ImageDrawRectangleV(Image *dst, Vector2 position, Vector2 size, Color color) { - Color *pixels = (Color *)RL_MALLOC(width*height*sizeof(Color)); + ImageDrawRectangle(dst, (int)position.x, (int)position.y, (int)size.x, (int)size.y, color); +} - for (int y = 0; y < height; y++) +// Draw rectangle within an image +void ImageDrawRectangleRec(Image *dst, Rectangle rec, Color color) +{ + // Security check to avoid program crash + if ((dst->data == NULL) || (dst->width == 0) || (dst->height == 0)) return; + + Image imRec = GenImageColor((int)rec.width, (int)rec.height, color); + ImageDraw(dst, imRec, (Rectangle){ 0, 0, rec.width, rec.height }, rec, WHITE); + UnloadImage(imRec); +} + +// Draw rectangle lines within an image +void ImageDrawRectangleLines(Image *dst, Rectangle rec, int thick, Color color) +{ + ImageDrawRectangle(dst, (int)rec.x, (int)rec.y, (int)rec.width, thick, color); + ImageDrawRectangle(dst, (int)rec.x, (int)(rec.y + thick), thick, (int)(rec.height - thick*2), color); + ImageDrawRectangle(dst, (int)(rec.x + rec.width - thick), (int)(rec.y + thick), thick, (int)(rec.height - thick*2), color); + ImageDrawRectangle(dst, (int)rec.x, (int)(rec.y + rec.height - thick), (int)rec.width, thick, color); +} + +// Draw an image (source) within an image (destination) +// NOTE: Color tint is applied to source image +void ImageDraw(Image *dst, Image src, Rectangle srcRec, Rectangle dstRec, Color tint) +{ + // Security check to avoid program crash + if ((dst->data == NULL) || (dst->width == 0) || (dst->height == 0) || + (src.data == NULL) || (src.width == 0) || (src.height == 0)) return; + + // Security checks to avoid size and rectangle issues (out of bounds) + // Check that srcRec is inside src image + if (srcRec.x < 0) srcRec.x = 0; + if (srcRec.y < 0) srcRec.y = 0; + + if ((srcRec.x + srcRec.width) > src.width) { - for (int x = 0; x < width; x++) + srcRec.width = src.width - srcRec.x; + TRACELOG(LOG_WARNING, "IMAGE: Source rectangle width out of bounds, rescaled width: %i", srcRec.width); + } + + if ((srcRec.y + srcRec.height) > src.height) + { + srcRec.height = src.height - srcRec.y; + TRACELOG(LOG_WARNING, "IMAGE: Source rectangle height out of bounds, rescaled height: %i", srcRec.height); + } + + Image srcCopy = ImageCopy(src); // Make a copy of source image to work with it + + // Crop source image to desired source rectangle (if required) + if ((src.width != (int)srcRec.width) && (src.height != (int)srcRec.height)) ImageCrop(&srcCopy, srcRec); + + // Scale source image in case destination rec size is different than source rec size + if (((int)dstRec.width != (int)srcRec.width) || ((int)dstRec.height != (int)srcRec.height)) + { + ImageResize(&srcCopy, (int)dstRec.width, (int)dstRec.height); + } + + // Check that dstRec is inside dst image + // Allow negative position within destination with cropping + if (dstRec.x < 0) + { + ImageCrop(&srcCopy, (Rectangle) { -dstRec.x, 0, dstRec.width + dstRec.x, dstRec.height }); + dstRec.width = dstRec.width + dstRec.x; + dstRec.x = 0; + } + + if ((dstRec.x + dstRec.width) > dst->width) + { + ImageCrop(&srcCopy, (Rectangle) { 0, 0, dst->width - dstRec.x, dstRec.height }); + dstRec.width = dst->width - dstRec.x; + } + + if (dstRec.y < 0) + { + ImageCrop(&srcCopy, (Rectangle) { 0, -dstRec.y, dstRec.width, dstRec.height + dstRec.y }); + dstRec.height = dstRec.height + dstRec.y; + dstRec.y = 0; + } + + if ((dstRec.y + dstRec.height) > dst->height) + { + ImageCrop(&srcCopy, (Rectangle) { 0, 0, dstRec.width, dst->height - dstRec.y }); + dstRec.height = dst->height - dstRec.y; + } + + // Get image data as Color pixels array to work with it + Color *dstPixels = GetImageData(*dst); + Color *srcPixels = GetImageData(srcCopy); + + UnloadImage(srcCopy); // Source copy not required any more + + Vector4 fsrc, fdst, fout; // Normalized pixel data (ready for operation) + Vector4 ftint = ColorNormalize(tint); // Normalized color tint + + // Blit pixels, copy source image into destination + // TODO: Maybe out-of-bounds blitting could be considered here instead of so much cropping + for (int j = (int)dstRec.y; j < (int)(dstRec.y + dstRec.height); j++) + { + for (int i = (int)dstRec.x; i < (int)(dstRec.x + dstRec.width); i++) { - float nx = (float)(x + offsetX)*scale/(float)width; - float ny = (float)(y + offsetY)*scale/(float)height; + // Alpha blending (https://en.wikipedia.org/wiki/Alpha_compositing) - // Typical values to start playing with: - // lacunarity = ~2.0 -- spacing between successive octaves (use exactly 2.0 for wrapping output) - // gain = 0.5 -- relative weighting applied to each successive octave - // octaves = 6 -- number of "octaves" of noise3() to sum + fdst = ColorNormalize(dstPixels[j*(int)dst->width + i]); + fsrc = ColorNormalize(srcPixels[(j - (int)dstRec.y)*(int)dstRec.width + (i - (int)dstRec.x)]); - // NOTE: We need to translate the data from [-1..1] to [0..1] - float p = (stb_perlin_fbm_noise3(nx, ny, 1.0f, 2.0f, 0.5f, 6) + 1.0f)/2.0f; + // Apply color tint to source image + fsrc.x *= ftint.x; fsrc.y *= ftint.y; fsrc.z *= ftint.z; fsrc.w *= ftint.w; - int intensity = (int)(p*255.0f); - pixels[y*width + x] = (Color){intensity, intensity, intensity, 255}; + fout.w = fsrc.w + fdst.w*(1.0f - fsrc.w); + + if (fout.w <= 0.0f) + { + fout.x = 0.0f; + fout.y = 0.0f; + fout.z = 0.0f; + } + else + { + fout.x = (fsrc.x*fsrc.w + fdst.x*fdst.w*(1 - fsrc.w))/fout.w; + fout.y = (fsrc.y*fsrc.w + fdst.y*fdst.w*(1 - fsrc.w))/fout.w; + fout.z = (fsrc.z*fsrc.w + fdst.z*fdst.w*(1 - fsrc.w))/fout.w; + } + + dstPixels[j*(int)dst->width + i] = (Color){ (unsigned char)(fout.x*255.0f), + (unsigned char)(fout.y*255.0f), + (unsigned char)(fout.z*255.0f), + (unsigned char)(fout.w*255.0f) }; + + // TODO: Support other blending options } } - Image image = LoadImageEx(pixels, width, height); - RL_FREE(pixels); + UnloadImage(*dst); - return image; + *dst = LoadImageEx(dstPixels, (int)dst->width, (int)dst->height); + ImageFormat(dst, dst->format); + + RL_FREE(srcPixels); + RL_FREE(dstPixels); } -// Generate image: cellular algorithm. Bigger tileSize means bigger cells -Image GenImageCellular(int width, int height, int tileSize) +// Draw text (default font) within an image (destination) +void ImageDrawText(Image *dst, const char *text, int posX, int posY, int fontSize, Color color) { - Color *pixels = (Color *)RL_MALLOC(width*height*sizeof(Color)); + Vector2 position = { (float)posX, (float)posY }; - int seedsPerRow = width/tileSize; - int seedsPerCol = height/tileSize; - int seedsCount = seedsPerRow * seedsPerCol; + // NOTE: For default font, sapcing is set to desired font size / default font size (10) + ImageDrawTextEx(dst, GetFontDefault(), text, position, (float)fontSize, (float)fontSize/10, color); +} - Vector2 *seeds = (Vector2 *)RL_MALLOC(seedsCount*sizeof(Vector2)); +// Draw text (custom sprite font) within an image (destination) +void ImageDrawTextEx(Image *dst, Font font, const char *text, Vector2 position, float fontSize, float spacing, Color tint) +{ + Image imText = ImageTextEx(font, text, fontSize, spacing, tint); - for (int i = 0; i < seedsCount; i++) + Rectangle srcRec = { 0.0f, 0.0f, (float)imText.width, (float)imText.height }; + Rectangle dstRec = { position.x, position.y, (float)imText.width, (float)imText.height }; + + ImageDraw(dst, imText, srcRec, dstRec, WHITE); + + UnloadImage(imText); +} +#endif // SUPPORT_IMAGE_DRAWING + +//------------------------------------------------------------------------------------ +// Texture loading functions +//------------------------------------------------------------------------------------ +// Load texture from file into GPU memory (VRAM) +Texture2D LoadTexture(const char *fileName) +{ + Texture2D texture = { 0 }; + + Image image = LoadImage(fileName); + + if (image.data != NULL) { - int y = (i/seedsPerRow)*tileSize + GetRandomValue(0, tileSize - 1); - int x = (i%seedsPerRow)*tileSize + GetRandomValue(0, tileSize - 1); - seeds[i] = (Vector2){ (float)x, (float)y}; + texture = LoadTextureFromImage(image); + UnloadImage(image); } - for (int y = 0; y < height; y++) + return texture; +} + +// Load a texture from image data +// NOTE: image is not unloaded, it must be done manually +Texture2D LoadTextureFromImage(Image image) +{ + Texture2D texture = { 0 }; + + if ((image.data != NULL) && (image.width != 0) && (image.height != 0)) { - int tileY = y/tileSize; + texture.id = rlLoadTexture(image.data, image.width, image.height, image.format, image.mipmaps); + } + else TRACELOG(LOG_WARNING, "IMAGE: Data is not valid to load texture"); - for (int x = 0; x < width; x++) + texture.width = image.width; + texture.height = image.height; + texture.mipmaps = image.mipmaps; + texture.format = image.format; + + return texture; +} + +// Load cubemap from image, multiple image cubemap layouts supported +TextureCubemap LoadTextureCubemap(Image image, int layoutType) +{ + TextureCubemap cubemap = { 0 }; + + if (layoutType == CUBEMAP_AUTO_DETECT) // Try to automatically guess layout type + { + // Check image width/height to determine the type of cubemap provided + if (image.width > image.height) { - int tileX = x/tileSize; + if ((image.width/6) == image.height) { layoutType = CUBEMAP_LINE_HORIZONTAL; cubemap.width = image.width/6; } + else if ((image.width/4) == (image.height/3)) { layoutType = CUBEMAP_CROSS_FOUR_BY_THREE; cubemap.width = image.width/4; } + else if (image.width >= (int)((float)image.height*1.85f)) { layoutType = CUBEMAP_PANORAMA; cubemap.width = image.width/4; } + } + else if (image.height > image.width) + { + if ((image.height/6) == image.width) { layoutType = CUBEMAP_LINE_VERTICAL; cubemap.width = image.height/6; } + else if ((image.width/3) == (image.height/4)) { layoutType = CUBEMAP_CROSS_THREE_BY_FOUR; cubemap.width = image.width/3; } + } - float minDistance = (float)strtod("Inf", NULL); + cubemap.height = cubemap.width; + } - // Check all adjacent tiles - for (int i = -1; i < 2; i++) + if (layoutType != CUBEMAP_AUTO_DETECT) + { + int size = cubemap.width; + + Image faces = { 0 }; // Vertical column image + Rectangle faceRecs[6] = { 0 }; // Face source rectangles + for (int i = 0; i < 6; i++) faceRecs[i] = (Rectangle){ 0, 0, (float)size, (float)size }; + + if (layoutType == CUBEMAP_LINE_VERTICAL) + { + faces = image; + for (int i = 0; i < 6; i++) faceRecs[i].y = (float)size*i; + } + else if (layoutType == CUBEMAP_PANORAMA) + { + // TODO: Convert panorama image to square faces... + // Ref: https://github.com/denivip/panorama/blob/master/panorama.cpp + } + else + { + if (layoutType == CUBEMAP_LINE_HORIZONTAL) for (int i = 0; i < 6; i++) faceRecs[i].x = (float)size*i; + else if (layoutType == CUBEMAP_CROSS_THREE_BY_FOUR) { - if ((tileX + i < 0) || (tileX + i >= seedsPerRow)) continue; + faceRecs[0].x = (float)size; faceRecs[0].y = (float)size; + faceRecs[1].x = (float)size; faceRecs[1].y = (float)size*3; + faceRecs[2].x = (float)size; faceRecs[2].y = 0; + faceRecs[3].x = (float)size; faceRecs[3].y = (float)size*2; + faceRecs[4].x = 0; faceRecs[4].y = (float)size; + faceRecs[5].x = (float)size*2; faceRecs[5].y = (float)size; + } + else if (layoutType == CUBEMAP_CROSS_FOUR_BY_THREE) + { + faceRecs[0].x = (float)size*2; faceRecs[0].y = (float)size; + faceRecs[1].x = 0; faceRecs[1].y = (float)size; + faceRecs[2].x = (float)size; faceRecs[2].y = 0; + faceRecs[3].x = (float)size; faceRecs[3].y = (float)size*2; + faceRecs[4].x = (float)size; faceRecs[4].y = (float)size; + faceRecs[5].x = (float)size*3; faceRecs[5].y = (float)size; + } - for (int j = -1; j < 2; j++) - { - if ((tileY + j < 0) || (tileY + j >= seedsPerCol)) continue; + // Convert image data to 6 faces in a vertical column, that's the optimum layout for loading + faces = GenImageColor(size, size*6, MAGENTA); + ImageFormat(&faces, image.format); - Vector2 neighborSeed = seeds[(tileY + j)*seedsPerRow + tileX + i]; + // TODO: Image formating does not work with compressed textures! + } - float dist = (float)hypot(x - (int)neighborSeed.x, y - (int)neighborSeed.y); - minDistance = (float)fmin(minDistance, dist); - } - } + //for (int i = 0; i < 6; i++) ImageDraw(&faces, image, faceRecs[i], (Rectangle){ 0, (float)size*i, (float)size, (float)size }, WHITE); - // I made this up but it seems to give good results at all tile sizes - int intensity = (int)(minDistance*256.0f/tileSize); - if (intensity > 255) intensity = 255; + cubemap.id = rlLoadTextureCubemap(faces.data, size, faces.format); + if (cubemap.id == 0) TRACELOG(LOG_WARNING, "IMAGE: Failed to load cubemap image"); - pixels[y*width + x] = (Color){ intensity, intensity, intensity, 255 }; + UnloadImage(faces); + } + else TRACELOG(LOG_WARNING, "IMAGE: Failed to detect cubemap image layout"); + + return cubemap; +} + +// Load texture for rendering (framebuffer) +// NOTE: Render texture is loaded by default with RGBA color attachment and depth RenderBuffer +RenderTexture2D LoadRenderTexture(int width, int height) +{ + RenderTexture2D target = rlLoadRenderTexture(width, height, UNCOMPRESSED_R8G8B8A8, 24, false); + + return target; +} + +// Unload texture from GPU memory (VRAM) +void UnloadTexture(Texture2D texture) +{ + if (texture.id > 0) + { + rlDeleteTextures(texture.id); + + TRACELOG(LOG_INFO, "TEXTURE: [ID %i] Unloaded texture data from VRAM (GPU)", texture.id); + } +} + +// Unload render texture from GPU memory (VRAM) +void UnloadRenderTexture(RenderTexture2D target) +{ + if (target.id > 0) rlDeleteRenderTextures(target); +} + +// Update GPU texture with new data +// NOTE: pixels data must match texture.format +void UpdateTexture(Texture2D texture, const void *pixels) +{ + rlUpdateTexture(texture.id, texture.width, texture.height, texture.format, pixels); +} + +// Get pixel data from GPU texture and return an Image +// NOTE: Compressed texture formats not supported +Image GetTextureData(Texture2D texture) +{ + Image image = { 0 }; + + if (texture.format < 8) + { + image.data = rlReadTexturePixels(texture); + + if (image.data != NULL) + { + image.width = texture.width; + image.height = texture.height; + image.format = texture.format; + image.mipmaps = 1; + +#if defined(GRAPHICS_API_OPENGL_ES2) + // NOTE: Data retrieved on OpenGL ES 2.0 should be RGBA, + // coming from FBO color buffer attachment, but it seems + // original texture format is retrieved on RPI... + image.format = UNCOMPRESSED_R8G8B8A8; +#endif + TRACELOG(LOG_INFO, "TEXTURE: [ID %i] Pixel data retrieved successfully", texture.id); } + else TRACELOG(LOG_WARNING, "TEXTURE: [ID %i] Failed to retrieve pixel data", texture.id); } + else TRACELOG(LOG_WARNING, "TEXTURE: [ID %i] Failed to retrieve compressed pixel data", texture.id); - RL_FREE(seeds); + return image; +} - Image image = LoadImageEx(pixels, width, height); - RL_FREE(pixels); +// Get pixel data from GPU frontbuffer and return an Image (screenshot) +Image GetScreenData(void) +{ + Image image = { 0 }; + + image.width = GetScreenWidth(); + image.height = GetScreenHeight(); + image.mipmaps = 1; + image.format = UNCOMPRESSED_R8G8B8A8; + image.data = rlReadScreenPixels(image.width, image.height); return image; } -#endif // SUPPORT_IMAGE_GENERATION +//------------------------------------------------------------------------------------ +// Texture configuration functions +//------------------------------------------------------------------------------------ // Generate GPU mipmaps for a texture void GenTextureMipmaps(Texture2D *texture) { @@ -2850,6 +2827,9 @@ void SetTextureWrap(Texture2D texture, int wrapMode) } } +//------------------------------------------------------------------------------------ +// Texture drawing functions +//------------------------------------------------------------------------------------ // Draw a Texture2D void DrawTexture(Texture2D texture, int posX, int posY, Color tint) { @@ -3140,6 +3120,44 @@ void DrawTextureNPatch(Texture2D texture, NPatchInfo nPatchInfo, Rectangle destR } } +// Get pixel data size in bytes (image or texture) +// NOTE: Size depends on pixel format +int GetPixelDataSize(int width, int height, int format) +{ + int dataSize = 0; // Size in bytes + int bpp = 0; // Bits per pixel + + switch (format) + { + case UNCOMPRESSED_GRAYSCALE: bpp = 8; break; + case UNCOMPRESSED_GRAY_ALPHA: + case UNCOMPRESSED_R5G6B5: + case UNCOMPRESSED_R5G5B5A1: + case UNCOMPRESSED_R4G4B4A4: bpp = 16; break; + case UNCOMPRESSED_R8G8B8A8: bpp = 32; break; + case UNCOMPRESSED_R8G8B8: bpp = 24; break; + case UNCOMPRESSED_R32: bpp = 32; break; + case UNCOMPRESSED_R32G32B32: bpp = 32*3; break; + case UNCOMPRESSED_R32G32B32A32: bpp = 32*4; break; + case COMPRESSED_DXT1_RGB: + case COMPRESSED_DXT1_RGBA: + case COMPRESSED_ETC1_RGB: + case COMPRESSED_ETC2_RGB: + case COMPRESSED_PVRT_RGB: + case COMPRESSED_PVRT_RGBA: bpp = 4; break; + case COMPRESSED_DXT3_RGBA: + case COMPRESSED_DXT5_RGBA: + case COMPRESSED_ETC2_EAC_RGBA: + case COMPRESSED_ASTC_4x4_RGBA: bpp = 8; break; + case COMPRESSED_ASTC_8x8_RGBA: bpp = 2; break; + default: break; + } + + dataSize = width*height*bpp/8; // Total data size in bytes + + return dataSize; +} + //---------------------------------------------------------------------------------- // Module specific Functions Definition //---------------------------------------------------------------------------------- |
