From 959447d8ed69e63b279bc5b72354c9d14a266e8f Mon Sep 17 00:00:00 2001 From: raysan5 Date: Sat, 9 May 2020 12:05:00 +0200 Subject: Reorganized texture functions Removed ImageAlphaMask() dependency on [text] LoadBMFont() --- src/config.h | 9 +- src/raylib.h | 13 +- src/text.c | 13 +- src/textures.c | 3010 ++++++++++++++++++++++++++++---------------------------- 4 files changed, 1538 insertions(+), 1507 deletions(-) (limited to 'src') 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 diff --git a/src/text.c b/src/text.c index c85d27b6..8439472d 100644 --- a/src/text.c +++ b/src/text.c @@ -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,540 +363,347 @@ 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) +// Unload image from CPU memory (RAM) +void UnloadImage(Image image) { - Texture2D texture = { 0 }; + RL_FREE(image.data); +} - Image image = LoadImage(fileName); +// Export image data to file +// NOTE: File format depends on fileName extension +void ExportImage(Image image, const char *fileName) +{ + int success = 0; - if (image.data != NULL) +#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")) { - texture = LoadTextureFromImage(image); - UnloadImage(image); + // 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; } - return texture; + 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); } -// Load a texture from image data -// NOTE: image is not unloaded, it must be done manually -Texture2D LoadTextureFromImage(Image image) +// Export image as code file (.h) defining an array of bytes +void ExportImageAsCode(Image image, const char *fileName) { - Texture2D texture = { 0 }; +#ifndef TEXT_BYTES_PER_LINE + #define TEXT_BYTES_PER_LINE 20 +#endif - if ((image.data != NULL) && (image.width != 0) && (image.height != 0)) + FILE *txtFile = fopen(fileName, "wt"); + + if (txtFile != NULL) { - 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"); + char varFileName[256] = { 0 }; + int dataSize = GetPixelDataSize(image.width, image.height, image.format); - texture.width = image.width; - texture.height = image.height; - texture.mipmaps = image.mipmaps; - texture.format = image.format; + 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"); - return texture; -} + // 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; } -// 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); + // 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); - return target; + 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]); + + fclose(txtFile); + } } -// Unload image from CPU memory (RAM) -void UnloadImage(Image image) +//------------------------------------------------------------------------------------ +// Image generation functions +//------------------------------------------------------------------------------------ +// Generate image: plain color +Image GenImageColor(int width, int height, Color color) { - RL_FREE(image.data); + Color *pixels = (Color *)RL_CALLOC(width*height, sizeof(Color)); + + for (int i = 0; i < width*height; i++) pixels[i] = color; + + Image image = LoadImageEx(pixels, width, height); + + RL_FREE(pixels); + + return image; } -// Unload texture from GPU memory (VRAM) -void UnloadTexture(Texture2D texture) +#if defined(SUPPORT_IMAGE_GENERATION) +// Generate image: vertical gradient +Image GenImageGradientV(int width, int height, Color top, Color bottom) { - if (texture.id > 0) - { - rlDeleteTextures(texture.id); + Color *pixels = (Color *)RL_MALLOC(width*height*sizeof(Color)); - TRACELOG(LOG_INFO, "TEXTURE: [ID %i] Unloaded texture data from VRAM (GPU)", texture.id); + 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)); + } } + + Image image = LoadImageEx(pixels, width, height); + RL_FREE(pixels); + + return image; } -// Unload render texture from GPU memory (VRAM) -void UnloadRenderTexture(RenderTexture2D target) +// Generate image: horizontal gradient +Image GenImageGradientH(int width, int height, Color left, Color right) { - if (target.id > 0) rlDeleteRenderTextures(target); + Color *pixels = (Color *)RL_MALLOC(width*height*sizeof(Color)); + + for (int i = 0; i < width; i++) + { + float factor = (float)i/(float)width; + for (int j = 0; j < height; j++) + { + 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)); + } + } + + Image image = LoadImageEx(pixels, width, height); + RL_FREE(pixels); + + return image; } -// Get pixel data from image in the form of Color struct array -Color *GetImageData(Image image) +// Generate image: radial gradient +Image GenImageGradientRadial(int width, int height, float density, Color inner, Color outer) { - if ((image.width == 0) || (image.height == 0)) return NULL; + Color *pixels = (Color *)RL_MALLOC(width*height*sizeof(Color)); + float radius = (width < height)? (float)width/2.0f : (float)height/2.0f; - Color *pixels = (Color *)RL_MALLOC(image.width*image.height*sizeof(Color)); + float centerX = (float)width/2.0f; + float centerY = (float)height/2.0f; - if (image.format >= COMPRESSED_DXT1_RGB) TRACELOG(LOG_WARNING, "IMAGE: Pixel data retrieval not supported for compressed image formats"); - else + for (int y = 0; y < height; y++) { - 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++) + for (int x = 0; x < width; x++) { - 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; + float dist = hypotf((float)x - centerX, (float)y - centerY); + float factor = (dist - radius*density)/(radius*(1.0f - density)); - } 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]; + factor = (float)fmax(factor, 0.f); + factor = (float)fmin(factor, 1.f); // dist can be bigger than radius so we have to check - k += 2; - } break; - case UNCOMPRESSED_R5G5B5A1: - { - unsigned short pixel = ((unsigned short *)image.data)[i]; + 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)); + } + } - 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); + Image image = LoadImageEx(pixels, width, height); + RL_FREE(pixels); - } break; - case UNCOMPRESSED_R5G6B5: - { - unsigned short pixel = ((unsigned short *)image.data)[i]; + return image; +} - 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; +// Generate image: checked +Image GenImageChecked(int width, int height, int checksX, int checksY, Color col1, Color col2) +{ + Color *pixels = (Color *)RL_MALLOC(width*height*sizeof(Color)); - } break; - case UNCOMPRESSED_R4G4B4A4: - { - unsigned short pixel = ((unsigned short *)image.data)[i]; + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x++) + { + if ((x/checksX + y/checksY)%2 == 0) pixels[y*width + x] = col1; + else pixels[y*width + x] = col2; + } + } - 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 image = LoadImageEx(pixels, width, height); + RL_FREE(pixels); - } 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; + 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); +// Generate image: white noise +Image GenImageWhiteNoise(int width, int height, float factor) +{ + Color *pixels = (Color *)RL_MALLOC(width*height*sizeof(Color)); - k += 4; - } break; - default: break; - } - } + for (int i = 0; i < width*height; i++) + { + if (GetRandomValue(0, 99) < (int)(factor*100.0f)) pixels[i] = WHITE; + else pixels[i] = BLACK; } - 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: perlin noise +Image GenImagePerlinNoise(int width, int height, int offsetX, int offsetY, float scale) { - 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 y = 0; y < height; y++) { - for (int i = 0, k = 0; i < image.width*image.height; i++) + for (int x = 0; x < width; x++) { - 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; + float nx = (float)(x + offsetX)*scale/(float)width; + float ny = (float)(y + offsetY)*scale/(float)height; - } 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; + // 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 - k += 2; - } break; - case UNCOMPRESSED_R5G5B5A1: - { - unsigned short pixel = ((unsigned short *)image.data)[i]; + // 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; - 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; + int intensity = (int)(p*255.0f); + pixels[y*width + x] = (Color){intensity, intensity, intensity, 255}; + } + } - } break; - case UNCOMPRESSED_R5G6B5: - { - unsigned short pixel = ((unsigned short *)image.data)[i]; + Image image = LoadImageEx(pixels, width, height); + RL_FREE(pixels); - 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; + return image; +} - } break; - case UNCOMPRESSED_R4G4B4A4: - { - unsigned short pixel = ((unsigned short *)image.data)[i]; +// Generate image: cellular algorithm. Bigger tileSize means bigger cells +Image GenImageCellular(int width, int height, int tileSize) +{ + Color *pixels = (Color *)RL_MALLOC(width*height*sizeof(Color)); - 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); + int seedsPerRow = width/tileSize; + int seedsPerCol = height/tileSize; + int seedsCount = seedsPerRow * seedsPerCol; - } 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; + Vector2 *seeds = (Vector2 *)RL_MALLOC(seedsCount*sizeof(Vector2)); - 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; + for (int i = 0; i < seedsCount; i++) + { + 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}; + } - 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; + for (int y = 0; y < height; y++) + { + int tileY = y/tileSize; - } 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 x = 0; x < width; x++) + { + int tileX = x/tileSize; - k += 3; - } break; - case UNCOMPRESSED_R32G32B32A32: + float minDistance = (float)strtod("Inf", NULL); + + // Check all adjacent tiles + for (int i = -1; i < 2; i++) + { + if ((tileX + i < 0) || (tileX + i >= seedsPerRow)) continue; + + for (int j = -1; j < 2; j++) { - 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]; + if ((tileY + j < 0) || (tileY + j >= seedsPerCol)) continue; - k += 4; + Vector2 neighborSeed = seeds[(tileY + j)*seedsPerRow + tileX + i]; + + float dist = (float)hypot(x - (int)neighborSeed.x, y - (int)neighborSeed.y); + minDistance = (float)fmin(minDistance, dist); } - default: break; } + + // 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; + + pixels[y*width + x] = (Color){ intensity, intensity, intensity, 255 }; } } - return pixels; + RL_FREE(seeds); + + Image image = LoadImageEx(pixels, width, height); + RL_FREE(pixels); + + return image; } +#endif // SUPPORT_IMAGE_GENERATION -// Get image alpha border rectangle -Rectangle GetImageAlphaBorder(Image image, float threshold) +//------------------------------------------------------------------------------------ +// Image manipulation functions +//------------------------------------------------------------------------------------ +// Copy an image to a new image +Image ImageCopy(Image image) { - Rectangle crop = { 0 }; + Image newImage = { 0 }; - Color *pixels = GetImageData(image); + int width = image.width; + int height = image.height; + int size = 0; - if (pixels != NULL) + for (int i = 0; i < image.mipmaps; i++) { - 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; - } - } - } + size += GetPixelDataSize(width, height, image.format); - // Check for empty blank image - if ((xMin != 65536) && (xMax != 65536)) - { - crop = (Rectangle){ (float)xMin, (float)yMin, (float)((xMax + 1) - xMin), (float)((yMax + 1) - yMin) }; - } + width /= 2; + height /= 2; - RL_FREE(pixels); + // Security check for NPOT textures + if (width < 1) width = 1; + if (height < 1) height = 1; } - return crop; -} - -// 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; -} - -// 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); - - return image; -} - -// 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; -} - -// 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); -} - -// Export image data to file -// NOTE: File format depends on fileName extension -void ExportImage(Image image, const char *fileName) -{ - int success = 0; - -#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; - } - - 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); -} - -// 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 - - FILE *txtFile = fopen(fileName, "wt"); - - if (txtFile != NULL) - { - char varFileName[256] = { 0 }; - int dataSize = GetPixelDataSize(image.width, image.height, image.format); - - 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"); - - // 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; } - - // 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); - - 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]); - - fclose(txtFile); - } -} - -// Copy an image to a new image -Image ImageCopy(Image image) -{ - Image newImage = { 0 }; - - int width = image.width; - int height = image.height; - int size = 0; - - for (int i = 0; i < image.mipmaps; i++) - { - size += GetPixelDataSize(width, height, image.format); - - width /= 2; - height /= 2; - - // Security check for NPOT textures - if (width < 1) width = 1; - if (height < 1) height = 1; - } - - newImage.data = RL_MALLOC(size); + newImage.data = RL_MALLOC(size); if (newImage.data != NULL) { @@ -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,11 +948,187 @@ 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) +{ + // 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))); + + // 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; + + // Generate POT array from NPOT data + pixelsPOT = (Color *)RL_MALLOC(potWidth*potHeight*sizeof(Color)); + + for (int j = 0; j < potHeight; j++) + { + for (int i = 0; i < potWidth; i++) + { + if ((j < image->height) && (i < image->width)) pixelsPOT[j*potWidth + i] = pixels[j*image->width + i]; + else pixelsPOT[j*potWidth + i] = fillColor; + } + } + + RL_FREE(pixels); // Free pixels data + RL_FREE(image->data); // Free old image data + + int format = image->format; // Store image data format to reconvert later + + // NOTE: Image size changes, new width and height + *image = LoadImageEx(pixelsPOT, potWidth, potHeight); + + RL_FREE(pixelsPOT); // Free POT pixels data + + 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); + } +} + +#if defined(SUPPORT_IMAGE_MANIPULATION) +// 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; +} + +// Crop image depending on alpha value +void ImageAlphaCrop(Image *image, float threshold) +{ + // Security check to avoid program crash + if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; + + Color *pixels = GetImageData(*image); + + 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; + } + } + } + + Rectangle crop = { (float)xMin, (float)yMin, (float)((xMax + 1) - xMin), (float)((yMax + 1) - yMin) }; + + RL_FREE(pixels); + + // Check for not empty image brefore cropping + 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"); @@ -1198,16 +1175,22 @@ void ImageAlphaMask(Image *image, Image alphaMask) } } -// 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) +// 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++) if (pixels[i].a <= (unsigned char)(threshold*255.0f)) pixels[i] = color; + 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); @@ -1218,244 +1201,57 @@ void ImageAlphaClear(Image *image, Color color, float threshold) RL_FREE(pixels); } -// Premultiply alpha channel -void ImageAlphaPremultiply(Image *image) +// Resize and image to new size +// NOTE: Uses stb default scaling filters (both bicubic): +// STBIR_DEFAULT_FILTER_UPSAMPLE STBIR_FILTER_CATMULLROM +// STBIR_DEFAULT_FILTER_DOWNSAMPLE STBIR_FILTER_MITCHELL (high-quality Catmull-Rom) +void ImageResize(Image *image, int newWidth, int newHeight) { // Security check to avoid program crash if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; - float alpha = 0.0f; + // Get data as Color pixels array to work with it Color *pixels = GetImageData(*image); + Color *output = (Color *)RL_MALLOC(newWidth*newHeight*sizeof(Color)); - 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); - } + // NOTE: Color data is casted to (unsigned char *), there shouldn't been any problem... + stbir_resize_uint8((unsigned char *)pixels, image->width, image->height, 0, (unsigned char *)output, newWidth, newHeight, 0, 4); + + int format = image->format; UnloadImage(*image); - int prevFormat = image->format; - *image = LoadImageEx(pixels, image->width, image->height); + *image = LoadImageEx(output, newWidth, newHeight); + ImageFormat(image, format); // Reformat 32bit RGBA image to original format - ImageFormat(image, prevFormat); + RL_FREE(output); RL_FREE(pixels); } -#if defined(SUPPORT_IMAGE_MANIPULATION) -// Load cubemap from image, multiple image cubemap layouts supported -TextureCubemap LoadTextureCubemap(Image image, int layoutType) +// Resize and image to new size using Nearest-Neighbor scaling algorithm +void ImageResizeNN(Image *image,int newWidth,int newHeight) { - TextureCubemap cubemap = { 0 }; + // Security check to avoid program crash + if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; - if (layoutType == CUBEMAP_AUTO_DETECT) // Try to automatically guess layout type + Color *pixels = GetImageData(*image); + Color *output = (Color *)RL_MALLOC(newWidth*newHeight*sizeof(Color)); + + // EDIT: added +1 to account for an early rounding problem + int xRatio = (int)((image->width << 16)/newWidth) + 1; + int yRatio = (int)((image->height << 16)/newHeight) + 1; + + int x2, y2; + for (int y = 0; y < newHeight; y++) { - // 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) + for (int x = 0; x < newWidth; x++) { - 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; } + x2 = ((x*xRatio) >> 16); + y2 = ((y*yRatio) >> 16); + + output[(y*newWidth) + x] = pixels[(y2*image->width) + x2] ; } - - cubemap.height = cubemap.width; - } - - 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) - { - 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; - } - - // 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! - } - - 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); - } - 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)) - { - // 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); - - // Reformat 32bit RGBA image to original format - ImageFormat(image, format); - } - else TRACELOG(LOG_WARNING, "IMAGE: Failed to crop, rectangle out of bounds"); -} - -// Crop image depending on alpha value -void ImageAlphaCrop(Image *image, float threshold) -{ - // Security check to avoid program crash - if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; - - Color *pixels = GetImageData(*image); - - 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; - } - } - } - - Rectangle crop = { (float)xMin, (float)yMin, (float)((xMax + 1) - xMin), (float)((yMax + 1) - yMin) }; - - RL_FREE(pixels); - - // Check for not empty image brefore cropping - if (!((xMax < xMin) || (yMax < yMin))) ImageCrop(image, crop); -} - -// Resize and image to new size -// NOTE: Uses stb default scaling filters (both bicubic): -// STBIR_DEFAULT_FILTER_UPSAMPLE STBIR_FILTER_CATMULLROM -// STBIR_DEFAULT_FILTER_DOWNSAMPLE STBIR_FILTER_MITCHELL (high-quality Catmull-Rom) -void ImageResize(Image *image, int newWidth, int newHeight) -{ - // Security check to avoid program crash - if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; - - // Get data as Color pixels array to work with it - Color *pixels = GetImageData(*image); - Color *output = (Color *)RL_MALLOC(newWidth*newHeight*sizeof(Color)); - - // NOTE: Color data is casted to (unsigned char *), there shouldn't been any problem... - stbir_resize_uint8((unsigned char *)pixels, image->width, image->height, 0, (unsigned char *)output, newWidth, newHeight, 0, 4); - - int format = image->format; - - UnloadImage(*image); - - *image = LoadImageEx(output, newWidth, newHeight); - ImageFormat(image, format); // Reformat 32bit RGBA image to original format - - RL_FREE(output); - RL_FREE(pixels); -} - -// Resize and image to new size using Nearest-Neighbor scaling algorithm -void ImageResizeNN(Image *image,int newWidth,int newHeight) -{ - // Security check to avoid program crash - if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; - - Color *pixels = GetImageData(*image); - Color *output = (Color *)RL_MALLOC(newWidth*newHeight*sizeof(Color)); - - // EDIT: added +1 to account for an early rounding problem - int xRatio = (int)((image->width << 16)/newWidth) + 1; - int yRatio = (int)((image->height << 16)/newHeight) + 1; - - int x2, y2; - for (int y = 0; y < newHeight; y++) - { - for (int x = 0; x < newWidth; x++) - { - x2 = ((x*xRatio) >> 16); - y2 = ((y*yRatio) >> 16); - - output[(y*newWidth) + x] = pixels[(y2*image->width) + x2] ; - } - } + } int format = image->format; @@ -1728,282 +1524,645 @@ 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) +// Flip image vertically +void ImageFlipVertical(Image *image) { - #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)); + // Security check to avoid program crash + if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; - int palCount = 0; - for (int i = 0; i < maxPaletteSize; i++) palette[i] = BLANK; // Set all colors to BLANK + Color *srcPixels = GetImageData(*image); + Color *dstPixels = (Color *)RL_MALLOC(image->width*image->height*sizeof(Color)); - for (int i = 0; i < image.width*image.height; i++) + for (int y = 0; y < image->height; y++) { - if (pixels[i].a > 0) + for (int x = 0; x < image->width; x++) { - 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); - } - } + dstPixels[y*image->width + x] = srcPixels[(image->height - 1 - y)*image->width + x]; } } - RL_FREE(pixels); + Image processed = LoadImageEx(dstPixels, image->width, image->height); + ImageFormat(&processed, image->format); + UnloadImage(*image); - *extractCount = palCount; + RL_FREE(srcPixels); + RL_FREE(dstPixels); - return palette; + image->data = processed.data; } -// 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) +// Flip image horizontally +void ImageFlipHorizontal(Image *image) { // 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; + if ((image->data == NULL) || (image->width == 0) || (image->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; + Color *srcPixels = GetImageData(*image); + Color *dstPixels = (Color *)RL_MALLOC(image->width*image->height*sizeof(Color)); - if ((srcRec.x + srcRec.width) > src.width) + for (int y = 0; y < image->height; y++) { - srcRec.width = src.width - srcRec.x; - TRACELOG(LOG_WARNING, "IMAGE: Source rectangle width out of bounds, rescaled width: %i", srcRec.width); + for (int x = 0; x < image->width; x++) + { + dstPixels[y*image->width + x] = srcPixels[y*image->width + (image->width - 1 - x)]; + } } - 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 processed = LoadImageEx(dstPixels, image->width, image->height); + ImageFormat(&processed, image->format); + UnloadImage(*image); - Image srcCopy = ImageCopy(src); // Make a copy of source image to work with it + RL_FREE(srcPixels); + RL_FREE(dstPixels); - // Crop source image to desired source rectangle (if required) - if ((src.width != (int)srcRec.width) && (src.height != (int)srcRec.height)) ImageCrop(&srcCopy, srcRec); + image->data = processed.data; +} - // 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); - } +// Rotate image clockwise 90deg +void ImageRotateCW(Image *image) +{ + // Security check to avoid program crash + if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; - // 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; - } + Color *srcPixels = GetImageData(*image); + Color *rotPixels = (Color *)RL_MALLOC(image->width*image->height*sizeof(Color)); - if ((dstRec.x + dstRec.width) > dst->width) + for (int y = 0; y < image->height; y++) { - ImageCrop(&srcCopy, (Rectangle) { 0, 0, dst->width - dstRec.x, dstRec.height }); - dstRec.width = dst->width - dstRec.x; + for (int x = 0; x < image->width; x++) + { + rotPixels[x*image->height + (image->height - y - 1)] = srcPixels[y*image->width + 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; - } + Image processed = LoadImageEx(rotPixels, image->height, image->width); + ImageFormat(&processed, image->format); + UnloadImage(*image); - if ((dstRec.y + dstRec.height) > dst->height) - { - ImageCrop(&srcCopy, (Rectangle) { 0, 0, dstRec.width, dst->height - dstRec.y }); - dstRec.height = dst->height - dstRec.y; - } + RL_FREE(srcPixels); + RL_FREE(rotPixels); - // Get image data as Color pixels array to work with it - Color *dstPixels = GetImageData(*dst); - Color *srcPixels = GetImageData(srcCopy); + image->data = processed.data; + image->width = processed.width; + image->height = processed.height; +} - UnloadImage(srcCopy); // Source copy not required any more +// Rotate image counter-clockwise 90deg +void ImageRotateCCW(Image *image) +{ + // Security check to avoid program crash + if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; - Vector4 fsrc, fdst, fout; // Normalized pixel data (ready for operation) - Vector4 ftint = ColorNormalize(tint); // Normalized color tint + Color *srcPixels = GetImageData(*image); + Color *rotPixels = (Color *)RL_MALLOC(image->width*image->height*sizeof(Color)); - // 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 y = 0; y < image->height; y++) { - for (int i = (int)dstRec.x; i < (int)(dstRec.x + dstRec.width); i++) + for (int x = 0; x < image->width; x++) { - // 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 + rotPixels[x*image->height + y] = srcPixels[y*image->width + (image->width - x - 1)]; } } - UnloadImage(*dst); - - *dst = LoadImageEx(dstPixels, (int)dst->width, (int)dst->height); - ImageFormat(dst, dst->format); + Image processed = LoadImageEx(rotPixels, image->height, image->width); + ImageFormat(&processed, image->format); + UnloadImage(*image); RL_FREE(srcPixels); - RL_FREE(dstPixels); + RL_FREE(rotPixels); + + image->data = processed.data; + image->width = processed.width; + image->height = processed.height; } -// Create an image from text (default font) -Image ImageText(const char *text, int fontSize, Color color) +// Modify image color: tint +void ImageColorTint(Image *image, Color color) { - int defaultFontSize = 10; // Default Font chars height in pixel - if (fontSize < defaultFontSize) fontSize = defaultFontSize; - int spacing = fontSize / defaultFontSize; + // Security check to avoid program crash + if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; - Image imText = ImageTextEx(GetFontDefault(), text, (float)fontSize, (float)spacing, color); + Color *pixels = GetImageData(*image); - return imText; -} + float cR = (float)color.r/255; + float cG = (float)color.g/255; + float cB = (float)color.b/255; + float cA = (float)color.a/255; -// 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); + for (int y = 0; y < image->height; y++) + { + for (int x = 0; x < image->width; x++) + { + int index = y * image->width + x; + unsigned char r = (unsigned char)(((float)pixels[index].r/255*cR)*255.0f); + unsigned char g = (unsigned char)(((float)pixels[index].g/255*cG)*255.0f); + unsigned char b = (unsigned char)(((float)pixels[index].b/255*cB)*255.0f); + unsigned char a = (unsigned char)(((float)pixels[index].a/255*cA)*255.0f); - int textOffsetX = 0; // Image drawing position X - int textOffsetY = 0; // Offset between lines (on line break '\n') + pixels[y*image->width + x].r = r; + pixels[y*image->width + x].g = g; + pixels[y*image->width + x].b = b; + pixels[y*image->width + x].a = a; + } + } - // NOTE: Text image is generated at font base size, later scaled to desired font size - Vector2 imSize = MeasureTextEx(font, text, (float)font.baseSize, spacing); + Image processed = LoadImageEx(pixels, image->width, image->height); + ImageFormat(&processed, image->format); + UnloadImage(*image); + RL_FREE(pixels); - // Create image to store text - Image imText = GenImageColor((int)imSize.x, (int)imSize.y, BLANK); + image->data = processed.data; +} - 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); +// Modify image color: invert +void ImageColorInvert(Image *image) +{ + // Security check to avoid program crash + if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; - // 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; + Color *pixels = GetImageData(*image); - 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 + for (int y = 0; y < image->height; y++) + { + for (int x = 0; x < image->width; x++) { - 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; + pixels[y*image->width + x].r = 255 - pixels[y*image->width + x].r; + pixels[y*image->width + x].g = 255 - pixels[y*image->width + x].g; + pixels[y*image->width + x].b = 255 - pixels[y*image->width + x].b; } - - 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)); - } + Image processed = LoadImageEx(pixels, image->width, image->height); + ImageFormat(&processed, image->format); + UnloadImage(*image); + RL_FREE(pixels); - return imText; + image->data = processed.data; } -// Draw rectangle within an image -void ImageDrawRectangle(Image *dst, int posX, int posY, int width, int height, Color color) +// Modify image color: grayscale +void ImageColorGrayscale(Image *image) { - ImageDrawRectangleRec(dst, (Rectangle){ (float)posX, (float)posY, (float)width, (float)height }, color); + ImageFormat(image, UNCOMPRESSED_GRAYSCALE); } -// Draw rectangle within an image (Vector version) -void ImageDrawRectangleV(Image *dst, Vector2 position, Vector2 size, Color color) +// Modify image color: contrast +// NOTE: Contrast values between -100 and 100 +void ImageColorContrast(Image *image, float contrast) { - ImageDrawRectangle(dst, (int)position.x, (int)position.y, (int)size.x, (int)size.y, color); -} + // Security check to avoid program crash + if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; + + if (contrast < -100) contrast = -100; + if (contrast > 100) contrast = 100; + + contrast = (100.0f + contrast)/100.0f; + contrast *= contrast; + + Color *pixels = GetImageData(*image); + + for (int y = 0; y < image->height; y++) + { + for (int x = 0; x < image->width; x++) + { + float pR = (float)pixels[y*image->width + x].r/255.0f; + pR -= 0.5; + pR *= contrast; + pR += 0.5; + pR *= 255; + if (pR < 0) pR = 0; + if (pR > 255) pR = 255; + + float pG = (float)pixels[y*image->width + x].g/255.0f; + pG -= 0.5; + pG *= contrast; + pG += 0.5; + pG *= 255; + if (pG < 0) pG = 0; + if (pG > 255) pG = 255; + + float pB = (float)pixels[y*image->width + x].b/255.0f; + pB -= 0.5; + pB *= contrast; + pB += 0.5; + pB *= 255; + if (pB < 0) pB = 0; + if (pB > 255) pB = 255; + + pixels[y*image->width + x].r = (unsigned char)pR; + pixels[y*image->width + x].g = (unsigned char)pG; + pixels[y*image->width + x].b = (unsigned char)pB; + } + } + + Image processed = LoadImageEx(pixels, image->width, image->height); + ImageFormat(&processed, image->format); + UnloadImage(*image); + RL_FREE(pixels); + + image->data = processed.data; +} + +// Modify image color: brightness +// NOTE: Brightness values between -255 and 255 +void ImageColorBrightness(Image *image, int brightness) +{ + // Security check to avoid program crash + if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; + + if (brightness < -255) brightness = -255; + if (brightness > 255) brightness = 255; + + Color *pixels = GetImageData(*image); + + for (int y = 0; y < image->height; y++) + { + for (int x = 0; x < image->width; x++) + { + int cR = pixels[y*image->width + x].r + brightness; + int cG = pixels[y*image->width + x].g + brightness; + int cB = pixels[y*image->width + x].b + brightness; + + if (cR < 0) cR = 1; + if (cR > 255) cR = 255; + + if (cG < 0) cG = 1; + if (cG > 255) cG = 255; + + if (cB < 0) cB = 1; + if (cB > 255) cB = 255; + + pixels[y*image->width + x].r = (unsigned char)cR; + pixels[y*image->width + x].g = (unsigned char)cG; + pixels[y*image->width + x].b = (unsigned char)cB; + } + } + + Image processed = LoadImageEx(pixels, image->width, image->height); + ImageFormat(&processed, image->format); + UnloadImage(*image); + RL_FREE(pixels); + + image->data = processed.data; +} + +// Modify image color: replace color +void ImageColorReplace(Image *image, Color color, Color replace) +{ + // Security check to avoid program crash + if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; + + Color *pixels = GetImageData(*image); + + for (int y = 0; y < image->height; y++) + { + for (int x = 0; x < image->width; x++) + { + if ((pixels[y*image->width + x].r == color.r) && + (pixels[y*image->width + x].g == color.g) && + (pixels[y*image->width + x].b == color.b) && + (pixels[y*image->width + x].a == color.a)) + { + pixels[y*image->width + x].r = replace.r; + pixels[y*image->width + x].g = replace.g; + pixels[y*image->width + x].b = replace.b; + pixels[y*image->width + x].a = replace.a; + } + } + } + + Image processed = LoadImageEx(pixels, image->width, image->height); + ImageFormat(&processed, image->format); + UnloadImage(*image); + RL_FREE(pixels); + + image->data = processed.data; +} +#endif // SUPPORT_IMAGE_MANIPULATION + +// Get pixel data from image in the form of Color struct array +Color *GetImageData(Image image) +{ + if ((image.width == 0) || (image.height == 0)) return NULL; + + Color *pixels = (Color *)RL_MALLOC(image.width*image.height*sizeof(Color)); + + 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"); + + 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]; + + 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; +} + +// 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; +} + +// Get pixel data from image as Vector4 array (float normalized) +Vector4 *GetImageDataNormalized(Image image) +{ + Vector4 *pixels = (Vector4 *)RL_MALLOC(image.width*image.height*sizeof(Vector4)); + + if (image.format >= COMPRESSED_DXT1_RGB) TRACELOG(LOG_WARNING, "IMAGE: Pixel data retrieval not supported for compressed image formats"); + else + { + for (int i = 0, k = 0; i < image.width*image.height; i++) + { + 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; + } + } + } + + return pixels; +} + +// Get image alpha border rectangle +Rectangle GetImageAlphaBorder(Image image, float threshold) +{ + Rectangle crop = { 0 }; + + Color *pixels = GetImageData(image); + + if (pixels != NULL) + { + 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; + } + } + } -// 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; + // Check for empty blank image + if ((xMin != 65536) && (xMax != 65536)) + { + crop = (Rectangle){ (float)xMin, (float)yMin, (float)((xMax + 1) - xMin), (float)((yMax + 1) - yMin) }; + } - Image imRec = GenImageColor((int)rec.width, (int)rec.height, color); - ImageDraw(dst, imRec, (Rectangle){ 0, 0, rec.width, rec.height }, rec, WHITE); - UnloadImage(imRec); -} + RL_FREE(pixels); + } -// 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); + return crop; } +//------------------------------------------------------------------------------------ +// Image drawing functions +//------------------------------------------------------------------------------------ +#if defined(SUPPORT_IMAGE_DRAWING) // Clear image background with given color void ImageClearBackground(Image *dst, Color color) { @@ -2043,710 +2202,528 @@ void ImageDrawPixel(Image *dst, int x, int y, Color color) // 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) -{ - // Security check to avoid program crash - if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; - - Color *srcPixels = GetImageData(*image); - Color *dstPixels = (Color *)RL_MALLOC(image->width*image->height*sizeof(Color)); - - for (int y = 0; y < image->height; y++) - { - for (int x = 0; x < image->width; x++) - { - dstPixels[y*image->width + x] = srcPixels[(image->height - 1 - y)*image->width + x]; - } - } - - Image processed = LoadImageEx(dstPixels, image->width, image->height); - ImageFormat(&processed, image->format); - UnloadImage(*image); - - RL_FREE(srcPixels); - RL_FREE(dstPixels); - - image->data = processed.data; -} - -// Flip image horizontally -void ImageFlipHorizontal(Image *image) -{ - // Security check to avoid program crash - if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; - - Color *srcPixels = GetImageData(*image); - Color *dstPixels = (Color *)RL_MALLOC(image->width*image->height*sizeof(Color)); - - for (int y = 0; y < image->height; y++) - { - for (int x = 0; x < image->width; x++) - { - dstPixels[y*image->width + x] = srcPixels[y*image->width + (image->width - 1 - x)]; - } - } - - Image processed = LoadImageEx(dstPixels, image->width, image->height); - ImageFormat(&processed, image->format); - UnloadImage(*image); - - RL_FREE(srcPixels); - RL_FREE(dstPixels); - - image->data = processed.data; -} - -// Rotate image clockwise 90deg -void ImageRotateCW(Image *image) -{ - // Security check to avoid program crash - if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; - - Color *srcPixels = GetImageData(*image); - Color *rotPixels = (Color *)RL_MALLOC(image->width*image->height*sizeof(Color)); + 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)); - for (int y = 0; y < image->height; y++) - { - for (int x = 0; x < image->width; x++) + ((unsigned short *)dst->data)[y*dst->width + x] = (unsigned short)r << 11 | (unsigned short)g << 5 | (unsigned short)b; + + } break; + case UNCOMPRESSED_R5G5B5A1: { - rotPixels[x*image->height + (image->height - y - 1)] = srcPixels[y*image->width + x]; - } - } + // 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 }; - Image processed = LoadImageEx(rotPixels, image->height, image->width); - ImageFormat(&processed, image->format); - UnloadImage(*image); + 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;; - RL_FREE(srcPixels); - RL_FREE(rotPixels); + ((unsigned short *)dst->data)[y*dst->width + x] = (unsigned short)r << 11 | (unsigned short)g << 6 | (unsigned short)b << 1 | (unsigned short)a; - image->data = processed.data; - image->width = processed.width; - image->height = processed.height; -} + } 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 }; -// Rotate image counter-clockwise 90deg -void ImageRotateCCW(Image *image) -{ - // Security check to avoid program crash - if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; + 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)); - Color *srcPixels = GetImageData(*image); - Color *rotPixels = (Color *)RL_MALLOC(image->width*image->height*sizeof(Color)); + ((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; - for (int y = 0; y < image->height; y++) - { - for (int x = 0; x < image->width; x++) + } break; + case UNCOMPRESSED_R32: { - rotPixels[x*image->height + y] = srcPixels[y*image->width + (image->width - x - 1)]; - } - } + // 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 }; - Image processed = LoadImageEx(rotPixels, image->height, image->width); - ImageFormat(&processed, image->format); - UnloadImage(*image); + ((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 }; - RL_FREE(srcPixels); - RL_FREE(rotPixels); + ((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 }; - image->data = processed.data; - image->width = processed.width; - image->height = processed.height; + ((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; + } } -// Modify image color: tint -void ImageColorTint(Image *image, Color color) +// Draw pixel within an image (Vector version) +void ImageDrawPixelV(Image *dst, Vector2 position, Color color) { - // Security check to avoid program crash - if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; - - Color *pixels = GetImageData(*image); + ImageDrawPixel(dst, (int)position.x, (int)position.y, color); +} - float cR = (float)color.r/255; - float cG = (float)color.g/255; - float cB = (float)color.b/255; - float cA = (float)color.a/255; +// 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 y = 0; y < image->height; y++) + for (int x = startPosX, y = startPosY; x <= endPosX; x++) { - for (int x = 0; x < image->width; x++) - { - int index = y * image->width + x; - unsigned char r = (unsigned char)(((float)pixels[index].r/255*cR)*255.0f); - unsigned char g = (unsigned char)(((float)pixels[index].g/255*cG)*255.0f); - unsigned char b = (unsigned char)(((float)pixels[index].b/255*cB)*255.0f); - unsigned char a = (unsigned char)(((float)pixels[index].a/255*cA)*255.0f); + ImageDrawPixel(dst, x, y, color); + slopeError += m; - pixels[y*image->width + x].r = r; - pixels[y*image->width + x].g = g; - pixels[y*image->width + x].b = b; - pixels[y*image->width + x].a = a; + if (slopeError >= 0) + { + y++; + slopeError -= 2*(endPosX - startPosX); } } - - Image processed = LoadImageEx(pixels, image->width, image->height); - ImageFormat(&processed, image->format); - UnloadImage(*image); - RL_FREE(pixels); - - image->data = processed.data; } -// Modify image color: invert -void ImageColorInvert(Image *image) +// Draw line within an image (Vector version) +void ImageDrawLineV(Image *dst, Vector2 start, Vector2 end, Color color) { - // Security check to avoid program crash - if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; + ImageDrawLine(dst, (int)start.x, (int)start.y, (int)end.x, (int)end.y, color); +} - Color *pixels = GetImageData(*image); +// 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; - for (int y = 0; y < image->height; y++) + while (y >= x) { - for (int x = 0; x < image->width; 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) { - pixels[y*image->width + x].r = 255 - pixels[y*image->width + x].r; - pixels[y*image->width + x].g = 255 - pixels[y*image->width + x].g; - pixels[y*image->width + x].b = 255 - pixels[y*image->width + x].b; + y--; + decesionParameter = decesionParameter + 4*(x - y) + 10; } + else decesionParameter = decesionParameter + 4*x + 6; } +} - Image processed = LoadImageEx(pixels, image->width, image->height); - ImageFormat(&processed, image->format); - UnloadImage(*image); - 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); +} - image->data = processed.data; +// 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); } -// Modify image color: grayscale -void ImageColorGrayscale(Image *image) +// Draw rectangle within an image (Vector version) +void ImageDrawRectangleV(Image *dst, Vector2 position, Vector2 size, Color color) { - ImageFormat(image, UNCOMPRESSED_GRAYSCALE); + ImageDrawRectangle(dst, (int)position.x, (int)position.y, (int)size.x, (int)size.y, color); } -// Modify image color: contrast -// NOTE: Contrast values between -100 and 100 -void ImageColorContrast(Image *image, float contrast) +// Draw rectangle within an image +void ImageDrawRectangleRec(Image *dst, Rectangle rec, Color color) { // Security check to avoid program crash - if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; - - if (contrast < -100) contrast = -100; - if (contrast > 100) contrast = 100; - - contrast = (100.0f + contrast)/100.0f; - contrast *= contrast; - - Color *pixels = GetImageData(*image); - - for (int y = 0; y < image->height; y++) - { - for (int x = 0; x < image->width; x++) - { - float pR = (float)pixels[y*image->width + x].r/255.0f; - pR -= 0.5; - pR *= contrast; - pR += 0.5; - pR *= 255; - if (pR < 0) pR = 0; - if (pR > 255) pR = 255; - - float pG = (float)pixels[y*image->width + x].g/255.0f; - pG -= 0.5; - pG *= contrast; - pG += 0.5; - pG *= 255; - if (pG < 0) pG = 0; - if (pG > 255) pG = 255; - - float pB = (float)pixels[y*image->width + x].b/255.0f; - pB -= 0.5; - pB *= contrast; - pB += 0.5; - pB *= 255; - if (pB < 0) pB = 0; - if (pB > 255) pB = 255; - - pixels[y*image->width + x].r = (unsigned char)pR; - pixels[y*image->width + x].g = (unsigned char)pG; - pixels[y*image->width + x].b = (unsigned char)pB; - } - } + if ((dst->data == NULL) || (dst->width == 0) || (dst->height == 0)) return; - Image processed = LoadImageEx(pixels, image->width, image->height); - ImageFormat(&processed, image->format); - UnloadImage(*image); - RL_FREE(pixels); + Image imRec = GenImageColor((int)rec.width, (int)rec.height, color); + ImageDraw(dst, imRec, (Rectangle){ 0, 0, rec.width, rec.height }, rec, WHITE); + UnloadImage(imRec); +} - image->data = processed.data; +// 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); } -// Modify image color: brightness -// NOTE: Brightness values between -255 and 255 -void ImageColorBrightness(Image *image, int brightness) +// 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 ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; - - if (brightness < -255) brightness = -255; - if (brightness > 255) brightness = 255; + if ((dst->data == NULL) || (dst->width == 0) || (dst->height == 0) || + (src.data == NULL) || (src.width == 0) || (src.height == 0)) return; - Color *pixels = GetImageData(*image); + // 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; - for (int y = 0; y < image->height; y++) + if ((srcRec.x + srcRec.width) > src.width) { - for (int x = 0; x < image->width; x++) - { - int cR = pixels[y*image->width + x].r + brightness; - int cG = pixels[y*image->width + x].g + brightness; - int cB = pixels[y*image->width + x].b + brightness; + srcRec.width = src.width - srcRec.x; + TRACELOG(LOG_WARNING, "IMAGE: Source rectangle width out of bounds, rescaled width: %i", srcRec.width); + } - if (cR < 0) cR = 1; - if (cR > 255) cR = 255; + 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); + } - if (cG < 0) cG = 1; - if (cG > 255) cG = 255; + Image srcCopy = ImageCopy(src); // Make a copy of source image to work with it - if (cB < 0) cB = 1; - if (cB > 255) cB = 255; + // Crop source image to desired source rectangle (if required) + if ((src.width != (int)srcRec.width) && (src.height != (int)srcRec.height)) ImageCrop(&srcCopy, srcRec); - pixels[y*image->width + x].r = (unsigned char)cR; - pixels[y*image->width + x].g = (unsigned char)cG; - pixels[y*image->width + x].b = (unsigned char)cB; - } + // 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); } - Image processed = LoadImageEx(pixels, image->width, image->height); - ImageFormat(&processed, image->format); - UnloadImage(*image); - RL_FREE(pixels); - - image->data = processed.data; -} + // 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; + } -// Modify image color: replace color -void ImageColorReplace(Image *image, Color color, Color replace) -{ - // Security check to avoid program crash - if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; + if ((dstRec.x + dstRec.width) > dst->width) + { + ImageCrop(&srcCopy, (Rectangle) { 0, 0, dst->width - dstRec.x, dstRec.height }); + dstRec.width = dst->width - dstRec.x; + } - Color *pixels = GetImageData(*image); + 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; + } - for (int y = 0; y < image->height; y++) + if ((dstRec.y + dstRec.height) > dst->height) { - for (int x = 0; x < image->width; x++) - { - if ((pixels[y*image->width + x].r == color.r) && - (pixels[y*image->width + x].g == color.g) && - (pixels[y*image->width + x].b == color.b) && - (pixels[y*image->width + x].a == color.a)) - { - pixels[y*image->width + x].r = replace.r; - pixels[y*image->width + x].g = replace.g; - pixels[y*image->width + x].b = replace.b; - pixels[y*image->width + x].a = replace.a; - } - } + ImageCrop(&srcCopy, (Rectangle) { 0, 0, dstRec.width, dst->height - dstRec.y }); + dstRec.height = dst->height - dstRec.y; } - Image processed = LoadImageEx(pixels, image->width, image->height); - ImageFormat(&processed, image->format); - UnloadImage(*image); - RL_FREE(pixels); + // Get image data as Color pixels array to work with it + Color *dstPixels = GetImageData(*dst); + Color *srcPixels = GetImageData(srcCopy); - image->data = processed.data; -} -#endif // SUPPORT_IMAGE_MANIPULATION + UnloadImage(srcCopy); // Source copy not required any more -// Generate image: plain color -Image GenImageColor(int width, int height, Color color) -{ - Color *pixels = (Color *)RL_CALLOC(width*height, sizeof(Color)); + Vector4 fsrc, fdst, fout; // Normalized pixel data (ready for operation) + Vector4 ftint = ColorNormalize(tint); // Normalized color tint - for (int i = 0; i < width*height; i++) pixels[i] = color; + // 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) - Image image = LoadImageEx(pixels, width, height); + fdst = ColorNormalize(dstPixels[j*(int)dst->width + i]); + fsrc = ColorNormalize(srcPixels[(j - (int)dstRec.y)*(int)dstRec.width + (i - (int)dstRec.x)]); - RL_FREE(pixels); + // Apply color tint to source image + fsrc.x *= ftint.x; fsrc.y *= ftint.y; fsrc.z *= ftint.z; fsrc.w *= ftint.w; - return image; -} + fout.w = fsrc.w + fdst.w*(1.0f - fsrc.w); -#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)); + 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; + } - 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)); + 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: horizontal gradient -Image GenImageGradientH(int width, int height, Color left, Color right) +// 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)); - - for (int i = 0; i < width; i++) - { - float factor = (float)i/(float)width; - for (int j = 0; j < height; j++) - { - 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)); - } - } - - Image image = LoadImageEx(pixels, width, height); - RL_FREE(pixels); + Vector2 position = { (float)posX, (float)posY }; - return image; + // 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); } -// Generate image: radial gradient -Image GenImageGradientRadial(int width, int height, float density, Color inner, Color outer) +// 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) { - Color *pixels = (Color *)RL_MALLOC(width*height*sizeof(Color)); - float radius = (width < height)? (float)width/2.0f : (float)height/2.0f; - - float centerX = (float)width/2.0f; - float centerY = (float)height/2.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)); - - factor = (float)fmax(factor, 0.f); - factor = (float)fmin(factor, 1.f); // dist can be bigger than radius so we have to check + Image imText = ImageTextEx(font, text, fontSize, spacing, tint); - 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)); - } - } + Rectangle srcRec = { 0.0f, 0.0f, (float)imText.width, (float)imText.height }; + Rectangle dstRec = { position.x, position.y, (float)imText.width, (float)imText.height }; - Image image = LoadImageEx(pixels, width, height); - RL_FREE(pixels); + ImageDraw(dst, imText, srcRec, dstRec, WHITE); - return image; + UnloadImage(imText); } +#endif // SUPPORT_IMAGE_DRAWING -// Generate image: checked -Image GenImageChecked(int width, int height, int checksX, int checksY, Color col1, Color col2) +//------------------------------------------------------------------------------------ +// Texture loading functions +//------------------------------------------------------------------------------------ +// Load texture from file into GPU memory (VRAM) +Texture2D LoadTexture(const char *fileName) { - Color *pixels = (Color *)RL_MALLOC(width*height*sizeof(Color)); + Texture2D texture = { 0 }; - for (int y = 0; y < height; y++) + Image image = LoadImage(fileName); + + if (image.data != NULL) { - for (int x = 0; x < width; x++) - { - if ((x/checksX + y/checksY)%2 == 0) pixels[y*width + x] = col1; - else pixels[y*width + x] = col2; - } + texture = LoadTextureFromImage(image); + UnloadImage(image); } - Image image = LoadImageEx(pixels, width, height); - RL_FREE(pixels); - - return image; + return texture; } -// Generate image: white noise -Image GenImageWhiteNoise(int width, int height, float factor) +// Load a texture from image data +// NOTE: image is not unloaded, it must be done manually +Texture2D LoadTextureFromImage(Image image) { - Color *pixels = (Color *)RL_MALLOC(width*height*sizeof(Color)); + Texture2D texture = { 0 }; - for (int i = 0; i < width*height; i++) + if ((image.data != NULL) && (image.width != 0) && (image.height != 0)) { - if (GetRandomValue(0, 99) < (int)(factor*100.0f)) pixels[i] = WHITE; - else pixels[i] = BLACK; + 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"); - Image image = LoadImageEx(pixels, width, height); - RL_FREE(pixels); + texture.width = image.width; + texture.height = image.height; + texture.mipmaps = image.mipmaps; + texture.format = image.format; - return image; + return texture; } -// Generate image: perlin noise -Image GenImagePerlinNoise(int width, int height, int offsetX, int offsetY, float scale) +// Load cubemap from image, multiple image cubemap layouts supported +TextureCubemap LoadTextureCubemap(Image image, int layoutType) { - Color *pixels = (Color *)RL_MALLOC(width*height*sizeof(Color)); + TextureCubemap cubemap = { 0 }; - for (int y = 0; y < height; y++) + if (layoutType == CUBEMAP_AUTO_DETECT) // Try to automatically guess layout type { - for (int x = 0; x < width; x++) + // Check image width/height to determine the type of cubemap provided + if (image.width > image.height) { - float nx = (float)(x + offsetX)*scale/(float)width; - float ny = (float)(y + offsetY)*scale/(float)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; } + } - // 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 + cubemap.height = cubemap.width; + } - // 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; + if (layoutType != CUBEMAP_AUTO_DETECT) + { + int size = cubemap.width; - int intensity = (int)(p*255.0f); - pixels[y*width + x] = (Color){intensity, intensity, intensity, 255}; - } - } + 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 }; - Image image = LoadImageEx(pixels, width, height); - RL_FREE(pixels); + 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) + { + 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; + } - return image; -} + // 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); -// Generate image: cellular algorithm. Bigger tileSize means bigger cells -Image GenImageCellular(int width, int height, int tileSize) -{ - Color *pixels = (Color *)RL_MALLOC(width*height*sizeof(Color)); + // TODO: Image formating does not work with compressed textures! + } - int seedsPerRow = width/tileSize; - int seedsPerCol = height/tileSize; - int seedsCount = seedsPerRow * seedsPerCol; + //for (int i = 0; i < 6; i++) ImageDraw(&faces, image, faceRecs[i], (Rectangle){ 0, (float)size*i, (float)size, (float)size }, WHITE); - Vector2 *seeds = (Vector2 *)RL_MALLOC(seedsCount*sizeof(Vector2)); + cubemap.id = rlLoadTextureCubemap(faces.data, size, faces.format); + if (cubemap.id == 0) TRACELOG(LOG_WARNING, "IMAGE: Failed to load cubemap image"); - for (int i = 0; i < seedsCount; i++) - { - 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}; + UnloadImage(faces); } + else TRACELOG(LOG_WARNING, "IMAGE: Failed to detect cubemap image layout"); - for (int y = 0; y < height; y++) - { - int tileY = y/tileSize; + return cubemap; +} - for (int x = 0; x < width; x++) - { - int tileX = x/tileSize; +// 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); - float minDistance = (float)strtod("Inf", NULL); + return target; +} - // Check all adjacent tiles - for (int i = -1; i < 2; i++) - { - if ((tileX + i < 0) || (tileX + i >= seedsPerRow)) continue; +// Unload texture from GPU memory (VRAM) +void UnloadTexture(Texture2D texture) +{ + if (texture.id > 0) + { + rlDeleteTextures(texture.id); - for (int j = -1; j < 2; j++) - { - if ((tileY + j < 0) || (tileY + j >= seedsPerCol)) continue; + TRACELOG(LOG_INFO, "TEXTURE: [ID %i] Unloaded texture data from VRAM (GPU)", texture.id); + } +} - Vector2 neighborSeed = seeds[(tileY + j)*seedsPerRow + tileX + i]; +// Unload render texture from GPU memory (VRAM) +void UnloadRenderTexture(RenderTexture2D target) +{ + if (target.id > 0) rlDeleteRenderTextures(target); +} - float dist = (float)hypot(x - (int)neighborSeed.x, y - (int)neighborSeed.y); - minDistance = (float)fmin(minDistance, dist); - } - } +// 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); +} - // 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; +// Get pixel data from GPU texture and return an Image +// NOTE: Compressed texture formats not supported +Image GetTextureData(Texture2D texture) +{ + Image image = { 0 }; - pixels[y*width + x] = (Color){ intensity, intensity, intensity, 255 }; + 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 //---------------------------------------------------------------------------------- -- cgit v1.2.3