summaryrefslogtreecommitdiffhomepage
path: root/src/textures.c
diff options
context:
space:
mode:
authorDavid Reid <[email protected]>2018-04-21 17:26:40 +1000
committerDavid Reid <[email protected]>2018-04-21 17:26:40 +1000
commitf5ebbfb6bc80e5d5555e84ee505ff794c2bc64b6 (patch)
tree800aeb61be9c2018d1a048da54d1f6ab746f11f1 /src/textures.c
parent950f31e620a9239dc91230ad92bb243f149e6f2c (diff)
parent847bdaf68287f70fbeb5599361257b6f982e48c5 (diff)
downloadraylib-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.c891
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