diff options
| author | David Reid <[email protected]> | 2018-04-21 17:26:40 +1000 |
|---|---|---|
| committer | David Reid <[email protected]> | 2018-04-21 17:26:40 +1000 |
| commit | f5ebbfb6bc80e5d5555e84ee505ff794c2bc64b6 (patch) | |
| tree | 800aeb61be9c2018d1a048da54d1f6ab746f11f1 /src/textures.c | |
| parent | 950f31e620a9239dc91230ad92bb243f149e6f2c (diff) | |
| parent | 847bdaf68287f70fbeb5599361257b6f982e48c5 (diff) | |
| download | raylib-f5ebbfb6bc80e5d5555e84ee505ff794c2bc64b6.tar.gz raylib-f5ebbfb6bc80e5d5555e84ee505ff794c2bc64b6.zip | |
Merge branch 'master' of https://github.com/raysan5/raylib into dr/mini_al
Diffstat (limited to 'src/textures.c')
| -rw-r--r-- | src/textures.c | 891 |
1 files changed, 569 insertions, 322 deletions
diff --git a/src/textures.c b/src/textures.c index b9fc5c19..43453f73 100644 --- a/src/textures.c +++ b/src/textures.c @@ -16,7 +16,7 @@ * #define SUPPORT_FILEFORMAT_KTX * #define SUPPORT_FILEFORMAT_PVR * #define SUPPORT_FILEFORMAT_ASTC -* Selecte desired fileformats to be supported for image data loading. Some of those formats are +* Selecte desired fileformats to be supported for image data loading. Some of those formats are * supported by default, to remove support, just comment unrequired #define in this module * * #define SUPPORT_IMAGE_MANIPULATION @@ -34,7 +34,7 @@ * * LICENSE: zlib/libpng * -* Copyright (c) 2014-2017 Ramon Santamaria (@raysan5) +* Copyright (c) 2014-2018 Ramon Santamaria (@raysan5) * * This software is provided "as-is", without any express or implied warranty. In no event * will the authors be held liable for any damages arising from the use of this software. @@ -52,15 +52,7 @@ * 3. This notice may not be removed or altered from any source distribution. * **********************************************************************************************/ - -// Default configuration flags (supported features) -//------------------------------------------------- -#define SUPPORT_FILEFORMAT_PNG -#define SUPPORT_FILEFORMAT_DDS -#define SUPPORT_FILEFORMAT_HDR -#define SUPPORT_IMAGE_MANIPULATION -#define SUPPORT_IMAGE_GENERATION -//------------------------------------------------- +#include "config.h" #include "raylib.h" @@ -110,7 +102,7 @@ #include "external/stb_image.h" // Required for: stbi_load_from_file() // NOTE: Used to read image data (multiple formats support) #endif - + #if defined(SUPPORT_IMAGE_MANIPULATION) #define STB_IMAGE_RESIZE_IMPLEMENTATION #include "external/stb_image_resize.h" // Required for: stbir_resize_uint8() @@ -165,18 +157,7 @@ Image LoadImage(const char *fileName) { Image image = { 0 }; - if (IsFileExtension(fileName, ".rres")) - { - RRES rres = LoadResource(fileName, 0); - - // NOTE: Parameters for RRES_TYPE_IMAGE are: width, height, format, mipmaps - - if (rres[0].type == RRES_TYPE_IMAGE) image = LoadImagePro(rres[0].data, rres[0].param1, rres[0].param2, rres[0].param3); - else TraceLog(LOG_WARNING, "[%s] Resource file does not contain image data", fileName); - - UnloadResource(rres); - } - else if ((IsFileExtension(fileName, ".png")) + if ((IsFileExtension(fileName, ".png")) #if defined(SUPPORT_FILEFORMAT_BMP) || (IsFileExtension(fileName, ".bmp")) #endif @@ -197,14 +178,14 @@ Image LoadImage(const char *fileName) int imgWidth = 0; int imgHeight = 0; int imgBpp = 0; - + FILE *imFile = fopen(fileName, "rb"); - + if (imFile != NULL) { // NOTE: Using stb_image to load images (Supports: BMP, TGA, PNG, JPG, ...) image.data = stbi_load_from_file(imFile, &imgWidth, &imgHeight, &imgBpp, 0); - + fclose(imFile); image.width = imgWidth; @@ -221,25 +202,26 @@ Image LoadImage(const char *fileName) else if (IsFileExtension(fileName, ".hdr")) { int imgBpp = 0; - + FILE *imFile = fopen(fileName, "rb"); - + stbi_set_flip_vertically_on_load(true); - - // Load 32 bit per channel floats data + + // Load 32 bit per channel floats data image.data = stbi_loadf_from_file(imFile, &image.width, &image.height, &imgBpp, 0); - + stbi_set_flip_vertically_on_load(false); fclose(imFile); - + image.mipmaps = 1; - - if (imgBpp == 3) image.format = UNCOMPRESSED_R32G32B32; - else + + if (imgBpp == 1) image.format = UNCOMPRESSED_R32; + else if (imgBpp == 3) image.format = UNCOMPRESSED_R32G32B32; + else if (imgBpp == 4) image.format = UNCOMPRESSED_R32G32B32A32; + else { - // TODO: Support different number of channels at 32 bit float - TraceLog(LOG_WARNING, "[%s] Image fileformat not supported (only 3 channel 32 bit floats)", fileName); + TraceLog(LOG_WARNING, "[%s] Image fileformat not supported", fileName); UnloadImage(image); } } @@ -326,22 +308,11 @@ Image LoadImageRaw(const char *fileName, int width, int height, int format, int { if (headerSize > 0) fseek(rawFile, headerSize, SEEK_SET); - unsigned int size = width*height; + unsigned int size = GetPixelDataSize(width, height, format); - switch (format) - { - case UNCOMPRESSED_GRAYSCALE: image.data = (unsigned char *)malloc(size); break; // 8 bit per pixel (no alpha) - case UNCOMPRESSED_GRAY_ALPHA: image.data = (unsigned char *)malloc(size*2); size *= 2; break; // 16 bpp (2 channels) - case UNCOMPRESSED_R5G6B5: image.data = (unsigned short *)malloc(size); break; // 16 bpp - case UNCOMPRESSED_R8G8B8: image.data = (unsigned char *)malloc(size*3); size *= 3; break; // 24 bpp - case UNCOMPRESSED_R5G5B5A1: image.data = (unsigned short *)malloc(size); break; // 16 bpp (1 bit alpha) - case UNCOMPRESSED_R4G4B4A4: image.data = (unsigned short *)malloc(size); break; // 16 bpp (4 bit alpha) - case UNCOMPRESSED_R8G8B8A8: image.data = (unsigned char *)malloc(size*4); size *= 4; break; // 32 bpp - case UNCOMPRESSED_R32G32B32: image.data = (float *)malloc(size*12); size *= 12; break; // 4 byte per channel (12 byte) - default: TraceLog(LOG_WARNING, "Image format not suported"); break; - } + image.data = malloc(size); // Allocate required memory in bytes - // NOTE: fread() returns num read elements instead of bytes, + // NOTE: fread() returns num read elements instead of bytes, // to get bytes we need to read (1 byte size, elements) instead of (x byte size, 1 element) int bytes = fread(image.data, 1, size, rawFile); @@ -395,8 +366,6 @@ Texture2D LoadTextureFromImage(Image image) texture.height = image.height; texture.mipmaps = image.mipmaps; texture.format = image.format; - - TraceLog(LOG_DEBUG, "[TEX ID %i] Parameters: %ix%i, %i mips, format %i", texture.id, texture.width, texture.height, texture.mipmaps, texture.format); return texture; } @@ -436,24 +405,22 @@ void UnloadRenderTexture(RenderTexture2D target) } // Get pixel data from image in the form of Color struct array +// TODO: Support float pixel data retrieval Color *GetImageData(Image image) { Color *pixels = (Color *)malloc(image.width*image.height*sizeof(Color)); - int k = 0; - - for (int i = 0; i < image.width*image.height; i++) + 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)[k]; - pixels[i].g = ((unsigned char *)image.data)[k]; - pixels[i].b = ((unsigned char *)image.data)[k]; + 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; - k++; } break; case UNCOMPRESSED_GRAY_ALPHA: { @@ -466,36 +433,33 @@ Color *GetImageData(Image image) } break; case UNCOMPRESSED_R5G5B5A1: { - unsigned short pixel = ((unsigned short *)image.data)[k]; + 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); - k++; } break; case UNCOMPRESSED_R5G6B5: { - unsigned short pixel = ((unsigned short *)image.data)[k]; + 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; - k++; } break; case UNCOMPRESSED_R4G4B4A4: { - unsigned short pixel = ((unsigned short *)image.data)[k]; + 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)); - k++; } break; case UNCOMPRESSED_R8G8B8A8: { @@ -522,12 +486,50 @@ Color *GetImageData(Image image) return pixels; } +// 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); @@ -536,14 +538,13 @@ Image GetTextureData(Texture2D texture) { image.width = texture.width; image.height = texture.height; + image.format = texture.format; image.mipmaps = 1; - - if (rlGetVersion() == OPENGL_ES_20) - { - // NOTE: Data retrieved on OpenGL ES 2.0 comes as RGBA (from framebuffer) - image.format = UNCOMPRESSED_R8G8B8A8; - } - else image.format = texture.format; + + // NOTE: Data retrieved on OpenGL ES 2.0 should be RGBA + // coming from FBO color buffer, but it seems original + // texture format is retrieved on RPI... weird... + //image.format = UNCOMPRESSED_R8G8B8A8; TraceLog(LOG_INFO, "Texture pixel data obtained successfully"); } @@ -561,16 +562,95 @@ void UpdateTexture(Texture2D texture, const void *pixels) rlUpdateTexture(texture.id, texture.width, texture.height, texture.format, pixels); } -// Save image to a PNG file -void SaveImageAs(const char* fileName, Image image) +// Export image as a PNG file +void ExportImage(const char *fileName, Image image) { -#if defined(PLATFORM_DESKTOP) || defined(PLATFORM_RPI) - unsigned char* imgData = (unsigned char*)GetImageData(image); // this works since Color is just a container for the RGBA values + // NOTE: Getting Color array as RGBA unsigned char values + unsigned char *imgData = (unsigned char *)GetImageData(image); SavePNG(fileName, imgData, image.width, image.height, 4); free(imgData); +} - TraceLog(LOG_INFO, "Image saved: %s", fileName); -#endif +// 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 = malloc(size); + + if (newImage.data != NULL) + { + // NOTE: Size must be provided in bytes + memcpy(newImage.data, image.data, size); + + newImage.width = image.width; + newImage.height = image.height; + newImage.mipmaps = image.mipmaps; + newImage.format = image.format; + } + + return newImage; +} + +// 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) +{ + Color *pixels = GetImageData(*image); // Get pixels data + + // 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 *pixelsPOT = NULL; + + // Generate POT array from NPOT data + pixelsPOT = (Color *)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; + } + } + + TraceLog(LOG_WARNING, "Image converted to POT: (%ix%i) -> (%ix%i)", image->width, image->height, potWidth, potHeight); + + free(pixels); // Free pixels data + free(image->data); // Free old image data + + int format = image->format; // Store image data format to reconvert later + + // TODO: Image width and height changes... do we want to store new values or keep the old ones? + // NOTE: Issues when using image.width and image.height for sprite animations... + *image = LoadImageEx(pixelsPOT, potWidth, potHeight); + + free(pixelsPOT); // Free POT pixels data + + ImageFormat(image, format); // Reconvert image to previous format + } } // Convert image data to desired format @@ -582,8 +662,8 @@ void ImageFormat(Image *image, int newFormat) { Color *pixels = GetImageData(*image); - free(image->data); - + free(image->data); // WARNING! We loose mipmaps data --> Regenerated at the end... + image->data = NULL; image->format = newFormat; int k = 0; @@ -604,11 +684,10 @@ void ImageFormat(Image *image, int newFormat) { image->data = (unsigned char *)malloc(image->width*image->height*2*sizeof(unsigned char)); - for (int i = 0; i < image->width*image->height*2; i += 2) + for (int i = 0; i < image->width*image->height*2; i += 2, k++) { ((unsigned char *)image->data)[i] = (unsigned char)((float)pixels[k].r*0.299f + (float)pixels[k].g*0.587f + (float)pixels[k].b*0.114f); ((unsigned char *)image->data)[i + 1] = pixels[k].a; - k++; } } break; @@ -622,9 +701,9 @@ void ImageFormat(Image *image, int newFormat) for (int i = 0; i < image->width*image->height; i++) { - r = (unsigned char)(round((float)pixels[k].r*31/255)); - g = (unsigned char)(round((float)pixels[k].g*63/255)); - b = (unsigned char)(round((float)pixels[k].b*31/255)); + r = (unsigned char)(round((float)pixels[i].r*31.0f/255)); + g = (unsigned char)(round((float)pixels[i].g*63.0f/255)); + b = (unsigned char)(round((float)pixels[i].b*31.0f/255)); ((unsigned short *)image->data)[i] = (unsigned short)r << 11 | (unsigned short)g << 5 | (unsigned short)b; } @@ -634,12 +713,11 @@ void ImageFormat(Image *image, int newFormat) { image->data = (unsigned char *)malloc(image->width*image->height*3*sizeof(unsigned char)); - for (int i = 0; i < image->width*image->height*3; i += 3) + for (int i = 0; i < image->width*image->height*3; i += 3, k++) { ((unsigned char *)image->data)[i] = pixels[k].r; ((unsigned char *)image->data)[i + 1] = pixels[k].g; ((unsigned char *)image->data)[i + 2] = pixels[k].b; - k++; } } break; case UNCOMPRESSED_R5G5B5A1: @@ -655,9 +733,9 @@ void ImageFormat(Image *image, int newFormat) for (int i = 0; i < image->width*image->height; i++) { - r = (unsigned char)(round((float)pixels[i].r*31/255)); - g = (unsigned char)(round((float)pixels[i].g*31/255)); - b = (unsigned char)(round((float)pixels[i].b*31/255)); + r = (unsigned char)(round((float)pixels[i].r*31.0f/255)); + g = (unsigned char)(round((float)pixels[i].g*31.0f/255)); + b = (unsigned char)(round((float)pixels[i].b*31.0f/255)); a = (pixels[i].a > ALPHA_THRESHOLD) ? 1 : 0; ((unsigned short *)image->data)[i] = (unsigned short)r << 11 | (unsigned short)g << 6 | (unsigned short)b << 1 | (unsigned short)a; @@ -675,12 +753,12 @@ void ImageFormat(Image *image, int newFormat) for (int i = 0; i < image->width*image->height; i++) { - r = (unsigned char)(round((float)pixels[i].r*15/255)); - g = (unsigned char)(round((float)pixels[i].g*15/255)); - b = (unsigned char)(round((float)pixels[i].b*15/255)); - a = (unsigned char)(round((float)pixels[i].a*15/255)); + r = (unsigned char)(round((float)pixels[i].r*15.0f/255)); + g = (unsigned char)(round((float)pixels[i].g*15.0f/255)); + b = (unsigned char)(round((float)pixels[i].b*15.0f/255)); + a = (unsigned char)(round((float)pixels[i].a*15.0f/255)); - ((unsigned short *)image->data)[i] = (unsigned short)r << 12 | (unsigned short)g << 8| (unsigned short)b << 4| (unsigned short)a; + ((unsigned short *)image->data)[i] = (unsigned short)r << 12 | (unsigned short)g << 8 | (unsigned short)b << 4 | (unsigned short)a; } } break; @@ -688,19 +766,59 @@ void ImageFormat(Image *image, int newFormat) { image->data = (unsigned char *)malloc(image->width*image->height*4*sizeof(unsigned char)); - for (int i = 0; i < image->width*image->height*4; i += 4) + for (int i = 0; i < image->width*image->height*3; i += 3, k++) { ((unsigned char *)image->data)[i] = pixels[k].r; ((unsigned char *)image->data)[i + 1] = pixels[k].g; ((unsigned char *)image->data)[i + 2] = pixels[k].b; ((unsigned char *)image->data)[i + 3] = pixels[k].a; - k++; + } + } break; + case UNCOMPRESSED_R32: + { + image->data = (float *)malloc(image->width*image->height*sizeof(float)); + + for (int i = 0; i < image->width*image->height; i++) + { + ((float *)image->data)[i] = (float)((float)pixels[i].r*0.299f/255.0f + (float)pixels[i].g*0.587f/255.0f + (float)pixels[i].b*0.114f/255.0f); + } + } break; + case UNCOMPRESSED_R32G32B32: + { + image->data = (float *)malloc(image->width*image->height*3*sizeof(float)); + + for (int i = 0; i < image->width*image->height*3; i += 3, k++) + { + ((float *)image->data)[i] = (float)pixels[k].r/255.0f; + ((float *)image->data)[i + 1] = (float)pixels[k].g/255.0f; + ((float *)image->data)[i + 2] = (float)pixels[k].b/255.0f; + } + } break; + case UNCOMPRESSED_R32G32B32A32: + { + image->data = (float *)malloc(image->width*image->height*4*sizeof(float)); + + for (int i = 0; i < image->width*image->height*4; i += 4, k++) + { + ((float *)image->data)[i] = (float)pixels[k].r/255.0f; + ((float *)image->data)[i + 1] = (float)pixels[k].g/255.0f; + ((float *)image->data)[i + 2] = (float)pixels[k].b/255.0f; + ((float *)image->data)[i + 3] = (float)pixels[k].a/255.0f; } } break; default: break; } free(pixels); + pixels = NULL; + // In case original image had mipmaps, generate mipmaps for formated image + // NOTE: Original mipmaps are replaced by new ones, if custom mipmaps were used, they are lost + if (image->mipmaps > 1) + { + image->mipmaps = 1; + assert(image->data != NULL); + ImageMipmaps(image); + } } else TraceLog(LOG_WARNING, "Image data format is compressed, can not be converted"); } @@ -752,99 +870,92 @@ 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) +// 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) { - Color *pixels = GetImageData(*image); // Get pixels data - - // 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))); + 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; - // Check if POT texture generation is required (if texture is not already POT) - if ((potWidth != image->width) || (potHeight != image->height)) - { - Color *pixelsPOT = NULL; + UnloadImage(*image); + + int prevFormat = image->format; + *image = LoadImageEx(pixels, image->width, image->height); + + ImageFormat(image, prevFormat); +} - // Generate POT array from NPOT data - pixelsPOT = (Color *)malloc(potWidth*potHeight*sizeof(Color)); +// Crop image depending on alpha value +void ImageAlphaCrop(Image *image, float threshold) +{ + Rectangle crop = { 0 }; + + Color *pixels = GetImageData(*image); + + int minx = 0; + int miny = 0; - for (int j = 0; j < potHeight; j++) + for (int i = 0; i < image->width*image->height; i++) + { + if (pixels[i].a > (unsigned char)(threshold*255.0f)) { - 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; - } - } + minx = i%image->width; + miny = -(-((i/image->width) + 1) + 1); - TraceLog(LOG_WARNING, "Image converted to POT: (%ix%i) -> (%ix%i)", image->width, image->height, potWidth, potHeight); - - free(pixels); // Free pixels data - free(image->data); // Free old image data + if (crop.y == 0) crop.y = miny; - int format = image->format; // Store image data format to reconvert later - - // TODO: Image width and height changes... do we want to store new values or keep the old ones? - // NOTE: Issues when using image.width and image.height for sprite animations... - *image = LoadImageEx(pixelsPOT, potWidth, potHeight); + if (crop.x == 0) crop.x = minx; + else if (minx < crop.x) crop.x = minx; - free(pixelsPOT); // Free POT pixels data + if (crop.width == 0) crop.width = minx; + else if (crop.width < minx) crop.width = minx; - ImageFormat(image, format); // Reconvert image to previous format + if (crop.height == 0) crop.height = miny; + else if (crop.height < miny) crop.height = miny; + } } + + crop.width -= (crop.x - 1); + crop.height -= (crop.y - 1); + + TraceLog(LOG_INFO, "Crop rectangle: (%i, %i, %i, %i)", crop.x, crop.y, crop.width, crop.height); + + free(pixels); + + // NOTE: Added this weird check to avoid additional 1px crop to + // image data that has already been cropped... + if ((crop.x != 1) && + (crop.y != 1) && + (crop.width != image->width - 1) && + (crop.height != image->height - 1)) ImageCrop(image, crop); } -#if defined(SUPPORT_IMAGE_MANIPULATION) -// Copy an image to a new image -Image ImageCopy(Image image) +// Premultiply alpha channel +void ImageAlphaPremultiply(Image *image) { - Image newImage; - - int byteSize = image.width*image.height; - - switch (image.format) + float alpha = 0.0f; + Color *pixels = GetImageData(*image); + + for (int i = 0; i < image->width*image->height; i++) { - case UNCOMPRESSED_GRAYSCALE: break; // 8 bpp (1 byte) - case UNCOMPRESSED_GRAY_ALPHA: // 16 bpp - case UNCOMPRESSED_R5G6B5: // 16 bpp - case UNCOMPRESSED_R5G5B5A1: // 16 bpp - case UNCOMPRESSED_R4G4B4A4: byteSize *= 2; break; // 16 bpp (2 bytes) - case UNCOMPRESSED_R8G8B8: byteSize *= 3; break; // 24 bpp (3 bytes) - case UNCOMPRESSED_R8G8B8A8: byteSize *= 4; break; // 32 bpp (4 bytes) - case UNCOMPRESSED_R32G32B32: byteSize *= 12; break; // 4 byte per channel (12 bytes) - case COMPRESSED_DXT3_RGBA: - case COMPRESSED_DXT5_RGBA: - case COMPRESSED_ETC2_EAC_RGBA: - case COMPRESSED_ASTC_4x4_RGBA: break; // 8 bpp (1 byte) - case COMPRESSED_DXT1_RGB: - case COMPRESSED_DXT1_RGBA: - case COMPRESSED_ETC1_RGB: - case COMPRESSED_ETC2_RGB: - case COMPRESSED_PVRT_RGB: - case COMPRESSED_PVRT_RGBA: byteSize /= 2; break; // 4 bpp - case COMPRESSED_ASTC_8x8_RGBA: byteSize /= 4; break;// 2 bpp - default: TraceLog(LOG_WARNING, "Image format not recognized"); break; + 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); } - newImage.data = malloc(byteSize); - - if (newImage.data != NULL) - { - // NOTE: Size must be provided in bytes - memcpy(newImage.data, image.data, byteSize); + UnloadImage(*image); + + int prevFormat = image->format; + *image = LoadImageEx(pixels, image->width, image->height); + + ImageFormat(image, prevFormat); +} - newImage.width = image.width; - newImage.height = image.height; - newImage.mipmaps = image.mipmaps; - newImage.format = image.format; - } - return newImage; -} +#if defined(SUPPORT_IMAGE_MANIPULATION) // 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) @@ -952,6 +1063,190 @@ void ImageResizeNN(Image *image,int newWidth,int newHeight) free(pixels); } +// Generate all mipmap levels for a provided image +// NOTE 1: Supports POT and NPOT images +// NOTE 2: image.data is scaled to include mipmap levels +// NOTE 3: Mipmaps format is the same as base image +void ImageMipmaps(Image *image) +{ + int mipCount = 1; // Required mipmap levels count (including base level) + int mipWidth = image->width; // Base image width + int mipHeight = image->height; // Base image height + int mipSize = GetPixelDataSize(mipWidth, mipHeight, image->format); // Image data size (in bytes) + + // Count mipmap levels required + while ((mipWidth != 1) || (mipHeight != 1)) + { + if (mipWidth != 1) mipWidth /= 2; + if (mipHeight != 1) mipHeight /= 2; + + // Security check for NPOT textures + if (mipWidth < 1) mipWidth = 1; + if (mipHeight < 1) mipHeight = 1; + + TraceLog(LOG_DEBUG, "Next mipmap level: %i x %i - current size %i", mipWidth, mipHeight, mipSize); + + mipCount++; + mipSize += GetPixelDataSize(mipWidth, mipHeight, image->format); // Add mipmap size (in bytes) + } + + TraceLog(LOG_DEBUG, "Mipmaps available: %i - Mipmaps required: %i", image->mipmaps, mipCount); + TraceLog(LOG_DEBUG, "Mipmaps total size required: %i", mipSize); + TraceLog(LOG_DEBUG, "Image data memory start address: 0x%x", image->data); + + if (image->mipmaps < mipCount) + { + void *temp = realloc(image->data, mipSize); + + if (temp != NULL) + { + image->data = temp; // Assign new pointer (new size) to store mipmaps data + TraceLog(LOG_DEBUG, "Image data memory point reallocated: 0x%x", temp); + } + else TraceLog(LOG_WARNING, "Mipmaps required memory could not be allocated"); + + // Pointer to allocated memory point where store next mipmap level data + unsigned char *nextmip = (unsigned char *)image->data + GetPixelDataSize(image->width, image->height, image->format); + + mipWidth = image->width/2; + mipHeight = image->height/2; + mipSize = GetPixelDataSize(mipWidth, mipHeight, image->format); + Image imCopy = ImageCopy(*image); + + for (int i = 1; i < mipCount; i++) + { + TraceLog(LOG_DEBUG, "Gen mipmap level: %i (%i x %i) - size: %i - offset: 0x%x", i, mipWidth, mipHeight, mipSize, nextmip); + + ImageResize(&imCopy, mipWidth, mipHeight); // Uses internally Mitchell cubic downscale filter + + memcpy(nextmip, imCopy.data, mipSize); + nextmip += mipSize; + image->mipmaps++; + + mipWidth /= 2; + mipHeight /= 2; + + // Security check for NPOT textures + if (mipWidth < 1) mipWidth = 1; + if (mipHeight < 1) mipHeight = 1; + + mipSize = GetPixelDataSize(mipWidth, mipHeight, image->format); + } + + UnloadImage(imCopy); + } + else TraceLog(LOG_WARNING, "Image mipmaps already available"); +} + +// Dither image data to 16bpp or lower (Floyd-Steinberg dithering) +// NOTE: In case selected bpp do not represent an known 16bit format, +// dithered data is stored in the LSB part of the unsigned short +void ImageDither(Image *image, int rBpp, int gBpp, int bBpp, int aBpp) +{ + if (image->format >= COMPRESSED_DXT1_RGB) + { + TraceLog(LOG_WARNING, "Compressed data formats can not be dithered"); + return; + } + + if ((rBpp+gBpp+bBpp+aBpp) > 16) + { + TraceLog(LOG_WARNING, "Unsupported dithering bpps (%ibpp), only 16bpp or lower modes supported", (rBpp+gBpp+bBpp+aBpp)); + } + else + { + Color *pixels = GetImageData(*image); + + free(image->data); // free old image data + + if ((image->format != UNCOMPRESSED_R8G8B8) && (image->format != UNCOMPRESSED_R8G8B8A8)) + { + TraceLog(LOG_WARNING, "Image format is already 16bpp or lower, dithering could have no effect"); + } + + // Define new image format, check if desired bpp match internal known format + if ((rBpp == 5) && (gBpp == 6) && (bBpp == 5) && (aBpp == 0)) image->format = UNCOMPRESSED_R5G6B5; + else if ((rBpp == 5) && (gBpp == 5) && (bBpp == 5) && (aBpp == 1)) image->format = UNCOMPRESSED_R5G5B5A1; + else if ((rBpp == 4) && (gBpp == 4) && (bBpp == 4) && (aBpp == 4)) image->format = UNCOMPRESSED_R4G4B4A4; + else + { + image->format = 0; + TraceLog(LOG_WARNING, "Unsupported dithered OpenGL internal format: %ibpp (R%iG%iB%iA%i)", (rBpp+gBpp+bBpp+aBpp), rBpp, gBpp, bBpp, aBpp); + } + + // NOTE: We will store the dithered data as unsigned short (16bpp) + image->data = (unsigned short *)malloc(image->width*image->height*sizeof(unsigned short)); + + Color oldPixel = WHITE; + Color newPixel = WHITE; + + int rError, gError, bError; + unsigned short rPixel, gPixel, bPixel, aPixel; // Used for 16bit pixel composition + + #define MIN(a,b) (((a)<(b))?(a):(b)) + + for (int y = 0; y < image->height; y++) + { + for (int x = 0; x < image->width; x++) + { + oldPixel = pixels[y*image->width + x]; + + // NOTE: New pixel obtained by bits truncate, it would be better to round values (check ImageFormat()) + newPixel.r = oldPixel.r >> (8 - rBpp); // R bits + newPixel.g = oldPixel.g >> (8 - gBpp); // G bits + newPixel.b = oldPixel.b >> (8 - bBpp); // B bits + newPixel.a = oldPixel.a >> (8 - aBpp); // A bits (not used on dithering) + + // NOTE: Error must be computed between new and old pixel but using same number of bits! + // We want to know how much color precision we have lost... + rError = (int)oldPixel.r - (int)(newPixel.r << (8 - rBpp)); + gError = (int)oldPixel.g - (int)(newPixel.g << (8 - gBpp)); + bError = (int)oldPixel.b - (int)(newPixel.b << (8 - bBpp)); + + pixels[y*image->width + x] = newPixel; + + // NOTE: Some cases are out of the array and should be ignored + if (x < (image->width - 1)) + { + pixels[y*image->width + x+1].r = MIN((int)pixels[y*image->width + x+1].r + (int)((float)rError*7.0f/16), 0xff); + pixels[y*image->width + x+1].g = MIN((int)pixels[y*image->width + x+1].g + (int)((float)gError*7.0f/16), 0xff); + pixels[y*image->width + x+1].b = MIN((int)pixels[y*image->width + x+1].b + (int)((float)bError*7.0f/16), 0xff); + } + + if ((x > 0) && (y < (image->height - 1))) + { + pixels[(y+1)*image->width + x-1].r = MIN((int)pixels[(y+1)*image->width + x-1].r + (int)((float)rError*3.0f/16), 0xff); + pixels[(y+1)*image->width + x-1].g = MIN((int)pixels[(y+1)*image->width + x-1].g + (int)((float)gError*3.0f/16), 0xff); + pixels[(y+1)*image->width + x-1].b = MIN((int)pixels[(y+1)*image->width + x-1].b + (int)((float)bError*3.0f/16), 0xff); + } + + if (y < (image->height - 1)) + { + pixels[(y+1)*image->width + x].r = MIN((int)pixels[(y+1)*image->width + x].r + (int)((float)rError*5.0f/16), 0xff); + pixels[(y+1)*image->width + x].g = MIN((int)pixels[(y+1)*image->width + x].g + (int)((float)gError*5.0f/16), 0xff); + pixels[(y+1)*image->width + x].b = MIN((int)pixels[(y+1)*image->width + x].b + (int)((float)bError*5.0f/16), 0xff); + } + + if ((x < (image->width - 1)) && (y < (image->height - 1))) + { + pixels[(y+1)*image->width + x+1].r = MIN((int)pixels[(y+1)*image->width + x+1].r + (int)((float)rError*1.0f/16), 0xff); + pixels[(y+1)*image->width + x+1].g = MIN((int)pixels[(y+1)*image->width + x+1].g + (int)((float)gError*1.0f/16), 0xff); + pixels[(y+1)*image->width + x+1].b = MIN((int)pixels[(y+1)*image->width + x+1].b + (int)((float)bError*1.0f/16), 0xff); + } + + rPixel = (unsigned short)newPixel.r; + gPixel = (unsigned short)newPixel.g; + bPixel = (unsigned short)newPixel.b; + aPixel = (unsigned short)newPixel.a; + + ((unsigned short *)image->data)[y*image->width + x] = (rPixel << (gBpp + bBpp + aBpp)) | (gPixel << (bBpp + aBpp)) | (bPixel << aBpp) | aPixel; + } + } + + free(pixels); + } +} + // Draw an image (source) within an image (destination) // TODO: Feel this function could be simplified... void ImageDraw(Image *dst, Image src, Rectangle srcRec, Rectangle dstRec) @@ -1028,10 +1323,31 @@ void ImageDraw(Image *dst, Image src, Rectangle srcRec, Rectangle dstRec) // Alpha blending implementation dstCol = dstPixels[j*dst->width + i]; srcCol = srcPixels[(j - dstRec.y)*dstRec.width + (i - dstRec.x)]; + + /* + // Pre-multiply alpha + Vector3 dstColf = { (float)dstCol.r/255.0f, (float)dstCol.g/255.0f, (float)dstCol.b/255.0f }; + dstColf = Vector3Multiply(dstColf, (float)dstCol.a/255.0f); + Vector3 srcColf = { (float)srcCol.r/255.0f, (float)srcCol.g/255.0f, (float)srcCol.b/255.0f }; + srcColf = Vector3Multiply(srcColf, (float)srcCol.a/255.0f); + + dstColf = Vector3Add(dstColf, srcColf); + + if (dstColf.x > 1.0f) dstColf.x = 1.0f; + if (dstColf.y > 1.0f) dstColf.y = 1.0f; + if (dstColf.z > 1.0f) dstColf.z = 1.0f; + + dstCol.r = (unsigned char)(dstColf.x*255.0f); + dstCol.g = (unsigned char)(dstColf.y*255.0f); + dstCol.b = (unsigned char)(dstColf.z*255.0f); + dstCol.a = srcCol.a; + */ dstCol.r = ((srcCol.a*(srcCol.r - dstCol.r)) >> 8) + dstCol.r; dstCol.g = ((srcCol.a*(srcCol.g - dstCol.g)) >> 8) + dstCol.g; dstCol.b = ((srcCol.a*(srcCol.b - dstCol.b)) >> 8) + dstCol.b; + //dstCol.a = ((srcCol.a*(srcCol.a - dstCol.a)) >> 8) + dstCol.a; + dstCol.a = srcCol.a; dstPixels[j*dst->width + i] = dstCol; @@ -1065,57 +1381,89 @@ Image ImageTextEx(SpriteFont font, const char *text, float fontSize, int spacing { int length = strlen(text); int posX = 0; + int index; // Index position in sprite font + unsigned char character; // Current character + // TODO: ISSUE: Measured text size does not seem to be correct... issue on ImageDraw() Vector2 imSize = MeasureTextEx(font, text, font.baseSize, spacing); + + TraceLog(LOG_DEBUG, "Text Image size: %f, %f", imSize.x, imSize.y); // NOTE: glGetTexImage() not available in OpenGL ES + // TODO: This is horrible, retrieving font texture from GPU!!! + // Define ImageFont struct? or include Image spritefont in SpriteFont struct? Image imFont = GetTextureData(font.texture); - - ImageFormat(&imFont, UNCOMPRESSED_R8G8B8A8); // Convert to 32 bit for color tint - ImageColorTint(&imFont, tint); // Apply color tint to font - - Color *fontPixels = GetImageData(imFont); + + ImageColorTint(&imFont, tint); // Apply color tint to font // Create image to store text - // NOTE: Pixels are initialized to BLANK color (0, 0, 0, 0) - Color *pixels = (Color *)calloc((int)imSize.x*(int)imSize.y, sizeof(Color)); + Image imText = GenImageColor((int)imSize.x, (int)imSize.y, BLANK); for (int i = 0; i < length; i++) { - Rectangle letterRec = font.chars[(int)text[i] - 32].rec; - - for (int y = letterRec.y; y < (letterRec.y + letterRec.height); y++) + if ((unsigned char)text[i] == '\n') { - for (int x = posX; x < (posX + letterRec.width); x++) + // TODO: Support line break + } + else + { + if ((unsigned char)text[i] == 0xc2) // UTF-8 encoding identification HACK! { - pixels[(y - letterRec.y)*(int)imSize.x + x] = fontPixels[y*font.texture.width + (x - posX + letterRec.x)]; + // Support UTF-8 encoded values from [0xc2 0x80] -> [0xc2 0xbf](¿) + character = (unsigned char)text[i + 1]; + index = GetGlyphIndex(font, (int)character); + i++; + } + else if ((unsigned char)text[i] == 0xc3) // UTF-8 encoding identification HACK! + { + // Support UTF-8 encoded values from [0xc3 0x80](À) -> [0xc3 0xbf](ÿ) + character = (unsigned char)text[i + 1]; + index = GetGlyphIndex(font, (int)character + 64); + i++; + } + else index = GetGlyphIndex(font, (unsigned char)text[i]); + + CharInfo letter = font.chars[index]; + + if ((unsigned char)text[i] != ' ') + { + ImageDraw(&imText, imFont, letter.rec, (Rectangle){ posX + letter.offsetX, + letter.offsetY, letter.rec.width, letter.rec.height }); } - } - posX += letterRec.width + spacing; + if (letter.advanceX == 0) posX += letter.rec.width + spacing; + else posX += letter.advanceX + spacing; + } } UnloadImage(imFont); - Image imText = LoadImageEx(pixels, (int)imSize.x, (int)imSize.y); - // Scale image depending on text size if (fontSize > imSize.y) { float scaleFactor = fontSize/imSize.y; - TraceLog(LOG_INFO, "Scalefactor: %f", scaleFactor); + TraceLog(LOG_INFO, "Image text scaled by factor: %f", scaleFactor); // Using nearest-neighbor scaling algorithm for default font if (font.texture.id == GetDefaultFont().texture.id) ImageResizeNN(&imText, (int)(imSize.x*scaleFactor), (int)(imSize.y*scaleFactor)); else ImageResize(&imText, (int)(imSize.x*scaleFactor), (int)(imSize.y*scaleFactor)); } - free(pixels); - free(fontPixels); - return imText; } +// Draw rectangle within an image +void ImageDrawRectangle(Image *dst, Vector2 position, Rectangle rec, Color color) +{ + Image imRec = GenImageColor(rec.width, rec.height, color); + + Rectangle dstRec = { (int)position.x, (int)position.y, imRec.width, imRec.height }; + + ImageDraw(dst, imRec, rec, dstRec); + + UnloadImage(imRec); +} + // Draw text (default font) within an image (destination) void ImageDrawText(Image *dst, Vector2 position, const char *text, int fontSize, Color color) { @@ -1184,115 +1532,6 @@ void ImageFlipHorizontal(Image *image) image->data = processed.data; } -// Dither image data to 16bpp or lower (Floyd-Steinberg dithering) -// NOTE: In case selected bpp do not represent an known 16bit format, -// dithered data is stored in the LSB part of the unsigned short -void ImageDither(Image *image, int rBpp, int gBpp, int bBpp, int aBpp) -{ - if (image->format >= COMPRESSED_DXT1_RGB) - { - TraceLog(LOG_WARNING, "Compressed data formats can not be dithered"); - return; - } - - if ((rBpp+gBpp+bBpp+aBpp) > 16) - { - TraceLog(LOG_WARNING, "Unsupported dithering bpps (%ibpp), only 16bpp or lower modes supported", (rBpp+gBpp+bBpp+aBpp)); - } - else - { - Color *pixels = GetImageData(*image); - - free(image->data); // free old image data - - if ((image->format != UNCOMPRESSED_R8G8B8) && (image->format != UNCOMPRESSED_R8G8B8A8)) - { - TraceLog(LOG_WARNING, "Image format is already 16bpp or lower, dithering could have no effect"); - } - - // Define new image format, check if desired bpp match internal known format - if ((rBpp == 5) && (gBpp == 6) && (bBpp == 5) && (aBpp == 0)) image->format = UNCOMPRESSED_R5G6B5; - else if ((rBpp == 5) && (gBpp == 5) && (bBpp == 5) && (aBpp == 1)) image->format = UNCOMPRESSED_R5G5B5A1; - else if ((rBpp == 4) && (gBpp == 4) && (bBpp == 4) && (aBpp == 4)) image->format = UNCOMPRESSED_R4G4B4A4; - else - { - image->format = 0; - TraceLog(LOG_WARNING, "Unsupported dithered OpenGL internal format: %ibpp (R%iG%iB%iA%i)", (rBpp+gBpp+bBpp+aBpp), rBpp, gBpp, bBpp, aBpp); - } - - // NOTE: We will store the dithered data as unsigned short (16bpp) - image->data = (unsigned short *)malloc(image->width*image->height*sizeof(unsigned short)); - - Color oldPixel = WHITE; - Color newPixel = WHITE; - - int rError, gError, bError; - unsigned short rPixel, gPixel, bPixel, aPixel; // Used for 16bit pixel composition - - #define MIN(a,b) (((a)<(b))?(a):(b)) - - for (int y = 0; y < image->height; y++) - { - for (int x = 0; x < image->width; x++) - { - oldPixel = pixels[y*image->width + x]; - - // NOTE: New pixel obtained by bits truncate, it would be better to round values (check ImageFormat()) - newPixel.r = oldPixel.r >> (8 - rBpp); // R bits - newPixel.g = oldPixel.g >> (8 - gBpp); // G bits - newPixel.b = oldPixel.b >> (8 - bBpp); // B bits - newPixel.a = oldPixel.a >> (8 - aBpp); // A bits (not used on dithering) - - // NOTE: Error must be computed between new and old pixel but using same number of bits! - // We want to know how much color precision we have lost... - rError = (int)oldPixel.r - (int)(newPixel.r << (8 - rBpp)); - gError = (int)oldPixel.g - (int)(newPixel.g << (8 - gBpp)); - bError = (int)oldPixel.b - (int)(newPixel.b << (8 - bBpp)); - - pixels[y*image->width + x] = newPixel; - - // NOTE: Some cases are out of the array and should be ignored - if (x < (image->width - 1)) - { - pixels[y*image->width + x+1].r = MIN((int)pixels[y*image->width + x+1].r + (int)((float)rError*7.0f/16), 0xff); - pixels[y*image->width + x+1].g = MIN((int)pixels[y*image->width + x+1].g + (int)((float)gError*7.0f/16), 0xff); - pixels[y*image->width + x+1].b = MIN((int)pixels[y*image->width + x+1].b + (int)((float)bError*7.0f/16), 0xff); - } - - if ((x > 0) && (y < (image->height - 1))) - { - pixels[(y+1)*image->width + x-1].r = MIN((int)pixels[(y+1)*image->width + x-1].r + (int)((float)rError*3.0f/16), 0xff); - pixels[(y+1)*image->width + x-1].g = MIN((int)pixels[(y+1)*image->width + x-1].g + (int)((float)gError*3.0f/16), 0xff); - pixels[(y+1)*image->width + x-1].b = MIN((int)pixels[(y+1)*image->width + x-1].b + (int)((float)bError*3.0f/16), 0xff); - } - - if (y < (image->height - 1)) - { - pixels[(y+1)*image->width + x].r = MIN((int)pixels[(y+1)*image->width + x].r + (int)((float)rError*5.0f/16), 0xff); - pixels[(y+1)*image->width + x].g = MIN((int)pixels[(y+1)*image->width + x].g + (int)((float)gError*5.0f/16), 0xff); - pixels[(y+1)*image->width + x].b = MIN((int)pixels[(y+1)*image->width + x].b + (int)((float)bError*5.0f/16), 0xff); - } - - if ((x < (image->width - 1)) && (y < (image->height - 1))) - { - pixels[(y+1)*image->width + x+1].r = MIN((int)pixels[(y+1)*image->width + x+1].r + (int)((float)rError*1.0f/16), 0xff); - pixels[(y+1)*image->width + x+1].g = MIN((int)pixels[(y+1)*image->width + x+1].g + (int)((float)gError*1.0f/16), 0xff); - pixels[(y+1)*image->width + x+1].b = MIN((int)pixels[(y+1)*image->width + x+1].b + (int)((float)bError*1.0f/16), 0xff); - } - - rPixel = (unsigned short)newPixel.r; - gPixel = (unsigned short)newPixel.g; - bPixel = (unsigned short)newPixel.b; - aPixel = (unsigned short)newPixel.a; - - ((unsigned short *)image->data)[y*image->width + x] = (rPixel << (gBpp + bBpp + aBpp)) | (gPixel << (bBpp + aBpp)) | (bPixel << aBpp) | aPixel; - } - } - - free(pixels); - } -} - // Modify image color: tint void ImageColorTint(Image *image, Color color) { @@ -1452,6 +1691,20 @@ void ImageColorBrightness(Image *image, int brightness) #endif // SUPPORT_IMAGE_MANIPULATION #if defined(SUPPORT_IMAGE_GENERATION) +// Generate image: plain color +Image GenImageColor(int width, int height, Color color) +{ + Color *pixels = (Color *)calloc(width*height, sizeof(Color)); + + for (int i = 0; i < width*height; i++) pixels[i] = color; + + Image image = LoadImageEx(pixels, width, height); + + free(pixels); + + return image; +} + // Generate image: vertical gradient Image GenImageGradientV(int width, int height, Color top, Color bottom) { @@ -1568,7 +1821,7 @@ Image GenImageWhiteNoise(int width, int height, float factor) } // Generate image: perlin noise -Image GenImagePerlinNoise(int width, int height, float scale) +Image GenImagePerlinNoise(int width, int height, int offsetX, int offsetY, float scale) { Color *pixels = (Color *)malloc(width*height*sizeof(Color)); @@ -1576,13 +1829,18 @@ Image GenImagePerlinNoise(int width, int height, float scale) { for (int x = 0; x < width; x++) { - float nx = (float)x*scale/(float)width; - float ny = (float)y*scale/(float)height; + float nx = (float)(x + offsetX)*scale/(float)width; + float ny = (float)(y + offsetY)*scale/(float)height; - // 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, 0, 0, 0) + 1.0f) / 2.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 + + // 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, 0, 0, 0) + 1.0f)/2.0f; - int intensity = (int)(p * 255.0f); + int intensity = (int)(p*255.0f); pixels[y*width + x] = (Color){intensity, intensity, intensity, 255}; } } @@ -1657,20 +1915,9 @@ Image GenImageCellular(int width, int height, int tileSize) // Generate GPU mipmaps for a texture void GenTextureMipmaps(Texture2D *texture) { -#if PLATFORM_WEB - // Calculate next power-of-two values - int potWidth = (int)powf(2, ceilf(logf((float)texture->width)/logf(2))); - int potHeight = (int)powf(2, ceilf(logf((float)texture->height)/logf(2))); - - // Check if texture is POT - if ((potWidth != texture->width) || (potHeight != texture->height)) - { - TraceLog(LOG_WARNING, "Limited NPOT support, no mipmaps available for NPOT textures"); - } - else rlGenerateMipmaps(texture); -#else + // NOTE: NPOT textures support check inside function + // On WebGL (OpenGL ES 2.0) NPOT textures support is limited rlGenerateMipmaps(texture); -#endif } // Set texture scaling filter mode |
