// Load a SpriteFont image into GPU memory SpriteFont LoadSpriteFont(const char* fileName) { SpriteFont spriteFont; Image image; // Check file extension if (strcmp(GetExtension(fileName),"rbmf") == 0) spriteFont = LoadRBMF(fileName); else { // Use stb_image to load image data! int imgWidth; int imgHeight; int imgBpp; byte *imgData = stbi_load(fileName, &imgWidth, &imgHeight, &imgBpp, 4); // Force loading to 4 components (RGBA) // Convert array to pixel array for working convenience Color *imgDataPixel = (Color *)malloc(imgWidth * imgHeight * sizeof(Color)); Color *imgDataPixelPOT = NULL; int pix = 0; for (int i = 0; i < (imgWidth * imgHeight * 4); i += 4) { imgDataPixel[pix].r = imgData[i]; imgDataPixel[pix].g = imgData[i+1]; imgDataPixel[pix].b = imgData[i+2]; imgDataPixel[pix].a = imgData[i+3]; pix++; } stbi_image_free(imgData); // At this point we have a pixel array with all the data... // Process bitmap Font pixel data to get measures (Character array) // spriteFont.charSet data is filled inside the function and memory is allocated! int numChars = ParseImageData(imgDataPixel, imgWidth, imgHeight, &spriteFont.charSet); TraceLog(INFO, "[%s] SpriteFont data parsed correctly", fileName); TraceLog(INFO, "[%s] SpriteFont num chars detected: %i", numChars); spriteFont.numChars = numChars; // Convert image font to POT image before conversion to texture // Just add the required amount of pixels at the right and bottom sides of image... int potWidth = GetNextPOT(imgWidth); int potHeight = GetNextPOT(imgHeight); // Check if POT texture generation is required (if texture is not already POT) if ((potWidth != imgWidth) || (potHeight != imgHeight)) { // Generate POT array from NPOT data imgDataPixelPOT = (Color *)malloc(potWidth * potHeight * sizeof(Color)); for (int j = 0; j < potHeight; j++) { for (int i = 0; i < potWidth; i++) { if ((j < imgHeight) && (i < imgWidth)) imgDataPixelPOT[j*potWidth + i] = imgDataPixel[j*imgWidth + i]; else imgDataPixelPOT[j*potWidth + i] = MAGENTA; } } TraceLog(WARNING, "SpriteFont texture converted to POT: %ix%i", potWidth, potHeight); } free(imgDataPixel); image.pixels = imgDataPixelPOT; image.width = potWidth; image.height = potHeight; spriteFont.texture = CreateTexture(image, false); // Convert loaded image to OpenGL texture UnloadImage(image); } return spriteFont; }
// Generate a sprite font from TTF file data (font size required) // TODO: Review texture packing method and generation (use oversampling) static SpriteFont LoadTTF(const char *fileName, int fontSize, int numChars, int *fontChars) { // 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... int textureSize = GetNextPOT(ceil((float)fontSize*3/4)*ceil(sqrtf((float)numChars))); TraceLog(INFO, "TTF spritefont loading: Predicted texture size: %ix%i", textureSize, textureSize); unsigned char *ttfBuffer = (unsigned char *)malloc(1 << 25); unsigned char *dataBitmap = (unsigned char *)malloc(textureSize*textureSize*sizeof(unsigned char)); // One channel bitmap returned! stbtt_bakedchar *charData = (stbtt_bakedchar *)malloc(sizeof(stbtt_bakedchar)*numChars); SpriteFont font = { 0 }; FILE *ttfFile = fopen(fileName, "rb"); if (ttfFile == NULL) { TraceLog(WARNING, "[%s] TTF file could not be opened", fileName); return font; } fread(ttfBuffer, 1, 1<<25, ttfFile); if (fontChars[0] != 32) TraceLog(WARNING, "TTF spritefont loading: first character is not SPACE(32) character"); // NOTE: Using stb_truetype crappy packing method, no guarante 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], numChars, charData); //if (result > 0) TraceLog(INFO, "TTF spritefont loading: first unused row of generated bitmap: %i", result); if (result < 0) TraceLog(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] = 255; 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); //WritePNG("generated_ttf_image.png", (unsigned char *)image.data, image.width, image.height, 2); UnloadImage(image); // Unloads dataGrayAlpha font.size = fontSize; font.numChars = numChars; font.charValues = (int *)malloc(font.numChars*sizeof(int)); font.charRecs = (Rectangle *)malloc(font.numChars*sizeof(Rectangle)); font.charOffsets = (Vector2 *)malloc(font.numChars*sizeof(Vector2)); font.charAdvanceX = (int *)malloc(font.numChars*sizeof(int)); for (int i = 0; i < font.numChars; i++) { font.charValues[i] = fontChars[i]; font.charRecs[i].x = (int)charData[i].x0; font.charRecs[i].y = (int)charData[i].y0; font.charRecs[i].width = (int)charData[i].x1 - (int)charData[i].x0; font.charRecs[i].height = (int)charData[i].y1 - (int)charData[i].y0; font.charOffsets[i] = (Vector2){ charData[i].xoff, charData[i].yoff }; font.charAdvanceX[i] = (int)charData[i].xadvance; } free(charData); return font; }