From 75ba5aca551d2b16fa75ba31e616b009ac5dde6a Mon Sep 17 00:00:00 2001 From: Ray Date: Wed, 20 Jun 2018 00:52:14 +0200 Subject: [PATCH] Improved font generation and SDF Added: data to CharInfo struct Added: LoadFontData() Added: GenImageFontAtlas() Removed: LoadFontEx() Removed: LoadTTF() [internal] Some code tweaks --- release/include/raylib.h | 27 +- release/libs/win32/mingw32/libraylib.a | Bin 1103438 -> 1116752 bytes src/raylib.h | 27 +- src/text.c | 356 +++++++++++++++---------- src/textures.c | 4 +- 5 files changed, 243 insertions(+), 171 deletions(-) diff --git a/release/include/raylib.h b/release/include/raylib.h index e0cfaa6de..49434d529 100644 --- a/release/include/raylib.h +++ b/release/include/raylib.h @@ -389,6 +389,7 @@ typedef struct CharInfo { int offsetX; // Character offset X when drawing int offsetY; // Character offset Y when drawing int advanceX; // Character advance position X + unsigned char *data; // Character pixel data (grayscale) } CharInfo; // Font type, includes texture and charSet array data @@ -955,29 +956,29 @@ RLAPI void DrawTextureV(Texture2D texture, Vector2 position, Color tint); RLAPI void DrawTextureEx(Texture2D texture, Vector2 position, float rotation, float scale, Color tint); // Draw a Texture2D with extended parameters RLAPI void DrawTextureRec(Texture2D texture, Rectangle sourceRec, Vector2 position, Color tint); // Draw a part of a texture defined by a rectangle RLAPI void DrawTexturePro(Texture2D texture, Rectangle sourceRec, Rectangle destRec, Vector2 origin, float rotation, Color tint); // Draw a part of a texture defined by a rectangle with 'pro' parameters - //------------------------------------------------------------------------------------ // Font Loading and Text Drawing Functions (Module: text) //------------------------------------------------------------------------------------ // Font loading/unloading functions -RLAPI Font GetDefaultFont(void); // Get the default Font -RLAPI Font LoadFont(const char *fileName); // Load Font from file into GPU memory (VRAM) -RLAPI Font LoadFontEx(const char *fileName, int fontSize, int charsCount, int *fontChars); // Load Font from file with extended parameters -RLAPI void UnloadFont(Font font); // Unload Font from GPU memory (VRAM) +RLAPI Font GetDefaultFont(void); // Get the default Font +RLAPI Font LoadFont(const char *fileName); // Load font from file into GPU memory (VRAM) +RLAPI CharInfo *LoadFontData(const char *fileName, int fontSize, int *fontChars, int charsCount, bool sdf); // Load font data for further use +RLAPI Image GenImageFontAtlas(CharInfo *chars, int fontSize, int charsCount, int packing); // Generate image font atlas using chars info +RLAPI void UnloadFont(Font font); // Unload Font from GPU memory (VRAM) // Text drawing functions -RLAPI void DrawFPS(int posX, int posY); // Shows current FPS -RLAPI void DrawText(const char *text, int posX, int posY, int fontSize, Color color); // Draw text (using default font) -RLAPI void DrawTextEx(Font font, const char* text, Vector2 position, float fontSize, float spacing, Color tint); // Draw text using Font and additional parameters +RLAPI void DrawFPS(int posX, int posY); // Shows current FPS +RLAPI void DrawText(const char *text, int posX, int posY, int fontSize, Color color); // Draw text (using default font) +RLAPI void DrawTextEx(Font font, const char* text, Vector2 position, float fontSize, float spacing, Color tint); // Draw text using font and additional parameters // Text misc. functions -RLAPI int MeasureText(const char *text, int fontSize); // Measure string width for default font -RLAPI Vector2 MeasureTextEx(Font font, const char *text, float fontSize, float spacing); // Measure string size for Font -RLAPI const char *FormatText(const char *text, ...); // Formatting of text with variables to 'embed' -RLAPI const char *SubText(const char *text, int position, int length); // Get a piece of a text string -RLAPI int GetGlyphIndex(Font font, int character); // Returns index position for a unicode character on sprite font +RLAPI int MeasureText(const char *text, int fontSize); // Measure string width for default font +RLAPI Vector2 MeasureTextEx(Font font, const char *text, float fontSize, float spacing); // Measure string size for Font +RLAPI const char *FormatText(const char *text, ...); // Formatting of text with variables to 'embed' +RLAPI const char *SubText(const char *text, int position, int length); // Get a piece of a text string +RLAPI int GetGlyphIndex(Font font, int character); // Get index position for a unicode character on sprite font //------------------------------------------------------------------------------------ // Basic 3d Shapes Drawing Functions (Module: models) diff --git a/release/libs/win32/mingw32/libraylib.a b/release/libs/win32/mingw32/libraylib.a index 2701869a717bc927f721e61df2ce922757782435..3425c71aed98130df9eb86722b3fbe3d7b4f72b3 100644 GIT binary patch delta 69547 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 Gi~kp87x#Gp delta 56536 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 NPMDoe`MVCU{2yc+s=xpM diff --git a/src/raylib.h b/src/raylib.h index e0cfaa6de..49434d529 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -389,6 +389,7 @@ typedef struct CharInfo { int offsetX; // Character offset X when drawing int offsetY; // Character offset Y when drawing int advanceX; // Character advance position X + unsigned char *data; // Character pixel data (grayscale) } CharInfo; // Font type, includes texture and charSet array data @@ -955,29 +956,29 @@ RLAPI void DrawTextureV(Texture2D texture, Vector2 position, Color tint); RLAPI void DrawTextureEx(Texture2D texture, Vector2 position, float rotation, float scale, Color tint); // Draw a Texture2D with extended parameters RLAPI void DrawTextureRec(Texture2D texture, Rectangle sourceRec, Vector2 position, Color tint); // Draw a part of a texture defined by a rectangle RLAPI void DrawTexturePro(Texture2D texture, Rectangle sourceRec, Rectangle destRec, Vector2 origin, float rotation, Color tint); // Draw a part of a texture defined by a rectangle with 'pro' parameters - //------------------------------------------------------------------------------------ // Font Loading and Text Drawing Functions (Module: text) //------------------------------------------------------------------------------------ // Font loading/unloading functions -RLAPI Font GetDefaultFont(void); // Get the default Font -RLAPI Font LoadFont(const char *fileName); // Load Font from file into GPU memory (VRAM) -RLAPI Font LoadFontEx(const char *fileName, int fontSize, int charsCount, int *fontChars); // Load Font from file with extended parameters -RLAPI void UnloadFont(Font font); // Unload Font from GPU memory (VRAM) +RLAPI Font GetDefaultFont(void); // Get the default Font +RLAPI Font LoadFont(const char *fileName); // Load font from file into GPU memory (VRAM) +RLAPI CharInfo *LoadFontData(const char *fileName, int fontSize, int *fontChars, int charsCount, bool sdf); // Load font data for further use +RLAPI Image GenImageFontAtlas(CharInfo *chars, int fontSize, int charsCount, int packing); // Generate image font atlas using chars info +RLAPI void UnloadFont(Font font); // Unload Font from GPU memory (VRAM) // Text drawing functions -RLAPI void DrawFPS(int posX, int posY); // Shows current FPS -RLAPI void DrawText(const char *text, int posX, int posY, int fontSize, Color color); // Draw text (using default font) -RLAPI void DrawTextEx(Font font, const char* text, Vector2 position, float fontSize, float spacing, Color tint); // Draw text using Font and additional parameters +RLAPI void DrawFPS(int posX, int posY); // Shows current FPS +RLAPI void DrawText(const char *text, int posX, int posY, int fontSize, Color color); // Draw text (using default font) +RLAPI void DrawTextEx(Font font, const char* text, Vector2 position, float fontSize, float spacing, Color tint); // Draw text using font and additional parameters // Text misc. functions -RLAPI int MeasureText(const char *text, int fontSize); // Measure string width for default font -RLAPI Vector2 MeasureTextEx(Font font, const char *text, float fontSize, float spacing); // Measure string size for Font -RLAPI const char *FormatText(const char *text, ...); // Formatting of text with variables to 'embed' -RLAPI const char *SubText(const char *text, int position, int length); // Get a piece of a text string -RLAPI int GetGlyphIndex(Font font, int character); // Returns index position for a unicode character on sprite font +RLAPI int MeasureText(const char *text, int fontSize); // Measure string width for default font +RLAPI Vector2 MeasureTextEx(Font font, const char *text, float fontSize, float spacing); // Measure string size for Font +RLAPI const char *FormatText(const char *text, ...); // Formatting of text with variables to 'embed' +RLAPI const char *SubText(const char *text, int position, int length); // Get a piece of a text string +RLAPI int GetGlyphIndex(Font font, int character); // Get index position for a unicode character on sprite font //------------------------------------------------------------------------------------ // Basic 3d Shapes Drawing Functions (Module: models) diff --git a/src/text.c b/src/text.c index 9755ff8b5..3c3ff7513 100644 --- a/src/text.c +++ b/src/text.c @@ -47,16 +47,14 @@ #include "utils.h" // Required for: fopen() Android mapping #if defined(SUPPORT_FILEFORMAT_TTF) - // Following libs are used on LoadTTF() - #define STBTT_STATIC // Define stb_truetype functions static to this module + #define STB_RECT_PACK_IMPLEMENTATION + #include "external/stb_rect_pack.h" // Required for: ttf font rectangles packaging + + #define STBTT_STATIC #define STB_TRUETYPE_IMPLEMENTATION - #include "external/stb_truetype.h" // Required for: stbtt_BakeFontBitmap() + #include "external/stb_truetype.h" // Required for: ttf font data reading #endif -// Rectangle packing functions (not used at the moment) -//#define STB_RECT_PACK_IMPLEMENTATION -//#include "stb_rect_pack.h" - //---------------------------------------------------------------------------------- // Defines and Macros //---------------------------------------------------------------------------------- @@ -89,7 +87,7 @@ static Font LoadImageFont(Image image, Color key, int firstChar); // Load a Imag static Font LoadBMFont(const char *fileName); // Load a BMFont file (AngelCode font file) #endif #if defined(SUPPORT_FILEFORMAT_TTF) -static Font LoadTTF(const char *fileName, int fontSize, int charsCount, int *fontChars); // Load spritefont from TTF data +//static Font LoadTTF(const char *fileName, int fontSize, int charsCount, int *fontChars); // Load spritefont from TTF data #endif #if defined(SUPPORT_DEFAULT_FONT) @@ -276,68 +274,244 @@ Font LoadFont(const char *fileName) // Default hardcoded values for ttf file loading #define DEFAULT_TTF_FONTSIZE 32 // Font first character (32 - space) #define DEFAULT_TTF_NUMCHARS 95 // ASCII 32..126 is 95 glyphs - #define DEFAULT_FIRST_CHAR 32 // Expected first char for image spritefont + #define DEFAULT_FIRST_CHAR 32 // Expected first char for image sprite font - Font spriteFont = { 0 }; + Font font = { 0 }; #if defined(SUPPORT_FILEFORMAT_TTF) - if (IsFileExtension(fileName, ".ttf")) spriteFont = LoadFontEx(fileName, DEFAULT_TTF_FONTSIZE, 0, NULL); + if (IsFileExtension(fileName, ".ttf")) + { + font.baseSize = DEFAULT_TTF_FONTSIZE; + font.charsCount = DEFAULT_TTF_NUMCHARS; + font.chars = LoadFontData(fileName, font.baseSize, NULL, font.charsCount, false); + Image atlas = GenImageFontAtlas(font.chars, font.charsCount, font.baseSize, 0); + font.texture = LoadTextureFromImage(atlas); + UnloadImage(atlas); + } else #endif #if defined(SUPPORT_FILEFORMAT_FNT) - if (IsFileExtension(fileName, ".fnt")) spriteFont = LoadBMFont(fileName); + if (IsFileExtension(fileName, ".fnt")) font = LoadBMFont(fileName); else #endif { Image image = LoadImage(fileName); - if (image.data != NULL) spriteFont = LoadImageFont(image, MAGENTA, DEFAULT_FIRST_CHAR); + if (image.data != NULL) font = LoadImageFont(image, MAGENTA, DEFAULT_FIRST_CHAR); UnloadImage(image); } - if (spriteFont.texture.id == 0) + if (font.texture.id == 0) { TraceLog(LOG_WARNING, "[%s] Font could not be loaded, using default font", fileName); - spriteFont = GetDefaultFont(); + font = GetDefaultFont(); } - else SetTextureFilter(spriteFont.texture, FILTER_POINT); // By default we set point filter (best performance) + else SetTextureFilter(font.texture, FILTER_POINT); // By default we set point filter (best performance) - return spriteFont; + return font; } -// Load Font from TTF font file with generation parameters -// NOTE: You can pass an array with desired characters, those characters should be available in the font -// if array is NULL, default char set is selected 32..126 -Font LoadFontEx(const char *fileName, int fontSize, int charsCount, int *fontChars) +// Load font data for further use +// NOTE: Requires TTF font and can generate SDF data +CharInfo *LoadFontData(const char *fileName, int fontSize, int *fontChars, int charsCount, bool sdf) { - Font spriteFont = { 0 }; - int totalChars = 95; // Default charset [32..126] - bool fontCharsLoaded = false; + // NOTE: Using some SDF generation default values, + // trades off precision with ability to handle *smaller* sizes + #define SDF_CHAR_PADDING 4 + #define SDF_ON_EDGE_VALUE 128 + #define SDF_PIXEL_DIST_SCALE 64.0f + + CharInfo *chars = (CharInfo *)malloc(charsCount*sizeof(CharInfo)); + + // Load font data (including pixel data) from TTF file + // NOTE: Loaded information should be enough to generate font image atlas, + // using any packaging method + FILE *fontFile = fopen(fileName, "rb"); // Load font file + + fseek(fontFile, 0, SEEK_END); + long size = ftell(fontFile); // Get file size + fseek(fontFile, 0, SEEK_SET); // Reset file pointer + + unsigned char *fontBuffer = (unsigned char *)malloc(size); + + fread(fontBuffer, size, 1, fontFile); + fclose(fontFile); + + // Init font for data reading + stbtt_fontinfo fontInfo; + if (!stbtt_InitFont(&fontInfo, fontBuffer, 0)) TraceLog(LOG_WARNING, "Failed to init font!"); -#if defined(SUPPORT_FILEFORMAT_TTF) - if (IsFileExtension(fileName, ".ttf")) + // Calculate font scale factor + float scaleFactor = stbtt_ScaleForPixelHeight(&fontInfo, fontSize); + + // Calculate font basic metrics + // NOTE: ascent is equivalent to font baseline + int ascent, descent, lineGap; + stbtt_GetFontVMetrics(&fontInfo, &ascent, &descent, &lineGap); + ascent *= scaleFactor; + descent *= scaleFactor; + + // Fill fontChars in case not provided externally + // NOTE: By default we fill charsCount consecutevely, starting at 32 (Space) + int genFontChars = false; + if (fontChars == NULL) genFontChars = true; + if (genFontChars) + { + fontChars = (int *)malloc(charsCount*sizeof(int)); + for (int i = 0; i < charsCount; i++) fontChars[i] = i + 32; + } + + // NOTE: Using simple packaging, one char after another + for (int i = 0; i < charsCount; i++) + { + int chw = 0, chh = 0; // Character width and height (on generation) + int ch = fontChars[i]; // Character value to get info for + chars[i].value = ch; + + // Render a unicode codepoint to a bitmap + // stbtt_GetCodepointBitmap() -- allocates and returns a bitmap + // stbtt_GetCodepointBitmapBox() -- how big the bitmap must be + // stbtt_MakeCodepointBitmap() -- renders into bitmap you provide + + if (!sdf) chars[i].data = stbtt_GetCodepointBitmap(&fontInfo, scaleFactor, scaleFactor, ch, &chw, &chh, &chars[i].offsetX, &chars[i].offsetY); + else if (ch != 32) chars[i].data = stbtt_GetCodepointSDF(&fontInfo, scaleFactor, ch, SDF_CHAR_PADDING, SDF_ON_EDGE_VALUE, SDF_PIXEL_DIST_SCALE, &chw, &chh, &chars[i].offsetX, &chars[i].offsetY); + + chars[i].rec.width = (float)chw; + chars[i].rec.height = (float)chh; + chars[i].offsetY += ascent; + + // Get bounding box for character (may be offset to account for chars that dip above or below the line) + int chX1, chY1, chX2, chY2; + stbtt_GetCodepointBitmapBox(&fontInfo, ch, scaleFactor, scaleFactor, &chX1, &chY1, &chX2, &chY2); + + TraceLog(LOG_DEBUG, "Character box measures: %i, %i, %i, %i", chX1, chY1, chX2 - chX1, chY2 - chY1); + TraceLog(LOG_DEBUG, "Character offsetY: %i", ascent + chY1); + + stbtt_GetCodepointHMetrics(&fontInfo, ch, &chars[i].advanceX, NULL); + chars[i].advanceX *= scaleFactor; + } + + free(fontBuffer); + if (genFontChars) free(fontChars); + + return chars; +} + +// Generate image font atlas using chars info +// NOTE: Packing method: 0-Default, 1-Skyline +Image GenImageFontAtlas(CharInfo *chars, int charsCount, int fontSize, int packing) +{ + Image atlas = { 0 }; + + int padding = 10; + + // Calculate atlas texture size based on fontSize + // NOTE: Font texture size is predicted (being as much conservative as possible) + // Predictive method consist of supposing same number of chars by line-column (sqrtf) + // and a maximum character width of 3/4 of fontSize... it worked ok with all my tests... + //float guessSize = ceilf((float)fontSize*3/4)*ceilf(sqrtf((float)charsCount)); + //int textureSize = (int)powf(2, ceilf(logf((float)guessSize)/logf(2))); // Calculate next POT + + // TODO: TEXTURE SIZE NOT GOOD ENOUGH! -> Calculate chars area -> guess texture size? + float requiredArea = 0; + for (int i = 0; i < charsCount; i++) requiredArea += ((chars[i].rec.width + 2*padding)*(chars[i].rec.height + 2*padding)); + float guessSize = sqrtf(requiredArea)*1.25f; + int textureSize = (int)powf(2, ceilf(logf((float)guessSize)/logf(2))); // Calculate next POT + + atlas.width = textureSize; // Atlas bitmap width + atlas.height = textureSize; // Atlas bitmap height + atlas.data = (unsigned char *)calloc(1, atlas.width*atlas.height); // Create a bitmap to store characters (8 bpp) + atlas.format = UNCOMPRESSED_GRAYSCALE; + atlas.mipmaps = 1; + + if (packing == 0) // Use basic packing algorythm { - if (charsCount != 0) totalChars = charsCount; + int offsetX = padding; + int offsetY = padding; - if (fontChars == NULL) + // NOTE: Using simple packaging, one char after another + for (int i = 0; i < charsCount; i++) { - fontChars = (int *)malloc(totalChars*sizeof(int)); - for (int i = 0; i < totalChars; i++) fontChars[i] = i + 32; // Default first character: SPACE[32] - fontCharsLoaded = true; + // Copy pixel data from fc.data to atlas + for (int y = 0; y < (int)chars[i].rec.height; y++) + { + for (int x = 0; x < (int)chars[i].rec.width; x++) + { + ((unsigned char *)atlas.data)[(offsetY + y)*atlas.width + (offsetX + x)] = chars[i].data[y*(int)chars[i].rec.width + x]; + } + } + + chars[i].rec.x = offsetX; + chars[i].rec.y = offsetY; + + // Move atlas position X for next character drawing + offsetX += ((int)chars[i].advanceX + 2*padding); + + if (offsetX >= (atlas.width - (int)chars[i].rec.width - padding)) + { + offsetX = padding; + offsetY += (fontSize + 2*padding); + + if (offsetY > (atlas.height - fontSize - padding)) break; + } } + } + else if (packing == 1) // Use Skyline rect packing algorythm + { + stbrp_context *context = (stbrp_context *)malloc(sizeof(*context)); + stbrp_node *nodes = (stbrp_node *)malloc(charsCount*sizeof(*nodes)); + + stbrp_init_target(context, atlas.width, atlas.height, nodes, charsCount); + stbrp_rect *rects = (stbrp_rect *)malloc(charsCount*sizeof(stbrp_rect)); - spriteFont = LoadTTF(fileName, fontSize, totalChars, fontChars); + // Fill rectangles for packaging + for (int i = 0; i < charsCount; i++) + { + rects[i].id = i; + rects[i].w = (int)chars[i].rec.width + 2*padding; + rects[i].h = (int)chars[i].rec.height + 2*padding; + } + + // Package rectangles into atlas + stbrp_pack_rects(context, rects, charsCount); - if (fontCharsLoaded) free(fontChars); + for (int i = 0; i < charsCount; i++) + { + chars[i].rec.x = rects[i].x + padding; + chars[i].rec.y = rects[i].y + padding; + + if (rects[i].was_packed) + { + // Copy pixel data from fc.data to atlas + for (int y = 0; y < (int)chars[i].rec.height; y++) + { + for (int x = 0; x < (int)chars[i].rec.width; x++) + { + ((unsigned char *)atlas.data)[(rects[i].y + padding + y)*atlas.width + (rects[i].x + padding + x)] = chars[i].data[y*(int)chars[i].rec.width + x]; + } + } + } + else TraceLog(LOG_WARNING, "Character could not be packed: %i", i); + } + + free(nodes); + free(context); } -#endif + + // Convert image data from GRAYSCALE to GRAY_ALPHA + //ImageAlphaMask(&atlas, atlas); // WARNING: Not working in this case, requires manual operation + unsigned char *dataGrayAlpha = (unsigned char *)malloc(textureSize*textureSize*sizeof(unsigned char)*2); // Two channels - if (spriteFont.texture.id == 0) + for (int i = 0, k = 0; i < atlas.width*atlas.height; i++, k += 2) { - TraceLog(LOG_WARNING, "[%s] Font could not be generated, using default font", fileName); - spriteFont = GetDefaultFont(); + dataGrayAlpha[k] = 255; + dataGrayAlpha[k + 1] = ((unsigned char *)atlas.data)[i]; } - return spriteFont; + free(atlas.data); + atlas.data = dataGrayAlpha; + atlas.format = UNCOMPRESSED_GRAY_ALPHA; + + return atlas; } // Unload Font from GPU memory (VRAM) @@ -811,108 +985,4 @@ static Font LoadBMFont(const char *fileName) return font; } -#endif - -#if defined(SUPPORT_FILEFORMAT_TTF) -// Generate a sprite font from TTF file data (font size required) -// TODO: Review texture packing method and generation (use oversampling) -static Font LoadTTF(const char *fileName, int fontSize, int charsCount, int *fontChars) -{ - #define MAX_TTF_SIZE 16 // Maximum ttf file size in MB - - // NOTE: Font texture size is predicted (being as much conservative as possible) - // Predictive method consist of supposing same number of chars by line-column (sqrtf) - // and a maximum character width of 3/4 of fontSize... it worked ok with all my tests... - - // Calculate next power-of-two value - float guessSize = ceilf((float)fontSize*3/4)*ceilf(sqrtf((float)charsCount)); - int textureSize = (int)powf(2, ceilf(logf((float)guessSize)/logf(2))); // Calculate next POT - - TraceLog(LOG_INFO, "TTF spritefont loading: Predicted texture size: %ix%i", textureSize, textureSize); - - unsigned char *ttfBuffer = (unsigned char *)malloc(MAX_TTF_SIZE*1024*1024); - unsigned char *dataBitmap = (unsigned char *)malloc(textureSize*textureSize*sizeof(unsigned char)); // One channel bitmap returned! - stbtt_bakedchar *charData = (stbtt_bakedchar *)malloc(sizeof(stbtt_bakedchar)*charsCount); - - Font font = { 0 }; - - FILE *ttfFile = fopen(fileName, "rb"); - - if (ttfFile == NULL) - { - TraceLog(LOG_WARNING, "[%s] TTF file could not be opened", fileName); - return font; - } - - // NOTE: We try reading up to 16 MB of elements of 1 byte - fread(ttfBuffer, 1, MAX_TTF_SIZE*1024*1024, ttfFile); - - // Find font baseline (vertical origin of the font) - // NOTE: This value is required because y-offset depends on it! - stbtt_fontinfo fontInfo; - int ascent, baseline; - float scale; - - stbtt_InitFont(&fontInfo, ttfBuffer, 0); - scale = stbtt_ScaleForPixelHeight(&fontInfo, fontSize); - stbtt_GetFontVMetrics(&fontInfo, &ascent, 0, 0); - baseline = (int)(ascent*scale); - - if (fontChars[0] != 32) TraceLog(LOG_WARNING, "TTF spritefont loading: first character is not SPACE(32) character"); - - // NOTE: Using stb_truetype crappy packing method, no guarantee the font fits the image... - // TODO: Replace this function by a proper packing method and support random chars order, - // we already receive a list (fontChars) with the ordered expected characters - int result = stbtt_BakeFontBitmap(ttfBuffer, 0, fontSize, dataBitmap, textureSize, textureSize, fontChars[0], charsCount, charData); - - //if (result > 0) TraceLog(LOG_INFO, "TTF spritefont loading: first unused row of generated bitmap: %i", result); - if (result < 0) TraceLog(LOG_WARNING, "TTF spritefont loading: Not all the characters fit in the font"); - - free(ttfBuffer); - - // Convert image data from grayscale to to UNCOMPRESSED_GRAY_ALPHA - unsigned char *dataGrayAlpha = (unsigned char *)malloc(textureSize*textureSize*sizeof(unsigned char)*2); // Two channels - - for (int i = 0, k = 0; i < textureSize*textureSize; i++, k += 2) - { - dataGrayAlpha[k] = 0xff; - dataGrayAlpha[k + 1] = dataBitmap[i]; - } - - free(dataBitmap); - - // Sprite font generation from TTF extracted data - Image image; - image.width = textureSize; - image.height = textureSize; - image.mipmaps = 1; - image.format = UNCOMPRESSED_GRAY_ALPHA; - image.data = dataGrayAlpha; - font.texture = LoadTextureFromImage(image); // Load image into texture - UnloadImage(image); // Unloads image data (dataGrayAlpha) - - - // Fill font characters info data - font.baseSize = fontSize; - font.charsCount = charsCount; - font.chars = (CharInfo *)malloc(font.charsCount*sizeof(CharInfo)); - - for (int i = 0; i < font.charsCount; i++) - { - font.chars[i].value = fontChars[i]; - - font.chars[i].rec.x = (int)charData[i].x0; - font.chars[i].rec.y = (int)charData[i].y0; - font.chars[i].rec.width = (int)charData[i].x1 - (int)charData[i].x0; - font.chars[i].rec.height = (int)charData[i].y1 - (int)charData[i].y0; - - font.chars[i].offsetX = charData[i].xoff; - font.chars[i].offsetY = baseline + charData[i].yoff; - font.chars[i].advanceX = (int)charData[i].xadvance; - } - - free(charData); - - return font; -} -#endif +#endif \ No newline at end of file diff --git a/src/textures.c b/src/textures.c index eeaf7ffdf..29316a7af 100644 --- a/src/textures.c +++ b/src/textures.c @@ -828,9 +828,9 @@ void ImageFormat(Image *image, int newFormat) } break; case UNCOMPRESSED_GRAY_ALPHA: { - image->data = (unsigned char *)malloc(image->width*image->height*2*sizeof(unsigned char)); + 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, k++) + for (int i = 0; i < image->width*image->height*2; i += 2, k++) { ((unsigned char *)image->data)[i] = (unsigned char)((pixels[k].x*0.299f + (float)pixels[k].y*0.587f + (float)pixels[k].z*0.114f)*255.0f); ((unsigned char *)image->data)[i + 1] = (unsigned char)(pixels[k].w*255.0f);