|
@@ -65,9 +65,11 @@ static SpriteFont defaultFont; // Default font provided by raylib
|
|
|
//----------------------------------------------------------------------------------
|
|
|
// Module specific Functions Declaration
|
|
|
//----------------------------------------------------------------------------------
|
|
|
-static bool PixelIsMagenta(Color p); // Check if a pixel is magenta
|
|
|
+static bool PixelIsMagenta(Color p); // Check if a pixel is magenta
|
|
|
static int ParseImageData(Color *imgDataPixel, int imgWidth, int imgHeight, Character **charSet); // Parse image pixel data to obtain character set measures
|
|
|
-static int GetNextPOT(int num); // Calculate next power-of-two value for a given value
|
|
|
+static int GetNextPOT(int num); // Calculate next power-of-two value for a given value
|
|
|
+static SpriteFont LoadRBMF(const char *fileName); // Load a rBMF font file (raylib BitMap Font)
|
|
|
+static const char *GetExtension(const char *fileName);
|
|
|
|
|
|
//----------------------------------------------------------------------------------
|
|
|
// Module Functions Definition
|
|
@@ -203,80 +205,90 @@ SpriteFont LoadSpriteFont(const char* fileName)
|
|
|
{
|
|
|
SpriteFont spriteFont;
|
|
|
|
|
|
- // 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);
|
|
|
-
|
|
|
- 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 != imgWidth))
|
|
|
- {
|
|
|
- // Generate POT array from NPOT data
|
|
|
- imgDataPixelPOT = (Color *)malloc(potWidth * potHeight * sizeof(Color));
|
|
|
+ // 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;
|
|
|
|
|
|
- for (int j = 0; j < potHeight; j++)
|
|
|
+ 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)
|
|
|
{
|
|
|
- for (int i = 0; i < potWidth; i++)
|
|
|
+ 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);
|
|
|
+
|
|
|
+ fprintf(stderr, "SpriteFont data parsed correctly!\n");
|
|
|
+ fprintf(stderr, "SpriteFont num chars: %i\n", 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++)
|
|
|
{
|
|
|
- if ((j < imgHeight) && (i < imgWidth)) imgDataPixelPOT[j*potWidth + i] = imgDataPixel[j*imgWidth + i];
|
|
|
- else imgDataPixelPOT[j*potWidth + i] = MAGENTA;
|
|
|
+ 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;
|
|
|
+ }
|
|
|
}
|
|
|
+
|
|
|
+ fprintf(stderr, "SpriteFont texture converted to POT: %i %i\n", potWidth, potHeight);
|
|
|
}
|
|
|
- }
|
|
|
-
|
|
|
- free(imgDataPixel);
|
|
|
+
|
|
|
+ free(imgDataPixel);
|
|
|
|
|
|
- // Convert loaded data to OpenGL texture
|
|
|
- //----------------------------------------
|
|
|
- GLuint id;
|
|
|
- glGenTextures(1, &id); // Generate pointer to the texture
|
|
|
-
|
|
|
- glBindTexture(GL_TEXTURE_2D, id);
|
|
|
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); // Filter for pixel-perfect drawing, alternative: GL_LINEAR
|
|
|
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); // Filter for pixel-perfect drawing, alternative: GL_LINEAR
|
|
|
- glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, potWidth, potHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, imgDataPixelPOT);
|
|
|
-
|
|
|
- // NOTE: Not using mipmappings (texture for 2D drawing)
|
|
|
- // At this point we have the image converted to texture and uploaded to GPU
|
|
|
-
|
|
|
- free(imgDataPixelPOT); // Now we can free loaded data from RAM memory
|
|
|
+ // Convert loaded data to OpenGL texture
|
|
|
+ //----------------------------------------
|
|
|
+ GLuint id;
|
|
|
+ glGenTextures(1, &id); // Generate pointer to the texture
|
|
|
+
|
|
|
+ glBindTexture(GL_TEXTURE_2D, id);
|
|
|
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); // Filter for pixel-perfect drawing, alternative: GL_LINEAR
|
|
|
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); // Filter for pixel-perfect drawing, alternative: GL_LINEAR
|
|
|
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, potWidth, potHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, imgDataPixelPOT);
|
|
|
+
|
|
|
+ // NOTE: Not using mipmappings (texture for 2D drawing)
|
|
|
+ // At this point we have the image converted to texture and uploaded to GPU
|
|
|
+
|
|
|
+ free(imgDataPixelPOT); // Now we can free loaded data from RAM memory
|
|
|
+
|
|
|
+ spriteFont.texture.glId = id;
|
|
|
+ spriteFont.texture.width = potWidth;
|
|
|
+ spriteFont.texture.height = potHeight;
|
|
|
+ }
|
|
|
|
|
|
- spriteFont.texture.glId = id;
|
|
|
- spriteFont.texture.width = potWidth;
|
|
|
- spriteFont.texture.height = potHeight;
|
|
|
-
|
|
|
return spriteFont;
|
|
|
}
|
|
|
|
|
@@ -289,31 +301,23 @@ void UnloadSpriteFont(SpriteFont spriteFont)
|
|
|
|
|
|
// Draw text (using default font)
|
|
|
// NOTE: fontSize work like in any drawing program but if fontSize is lower than font-base-size, then font-base-size is used
|
|
|
+// NOTE: chars spacing is proportional to fontSize
|
|
|
void DrawText(const char* text, int posX, int posY, int fontSize, Color color)
|
|
|
{
|
|
|
Vector2 position = { (float)posX, (float)posY };
|
|
|
+
|
|
|
+ int defaultFontSize = 10; // Default Font chars height in pixel
|
|
|
|
|
|
- DrawTextEx(defaultFont, text, position, fontSize, 1, color);
|
|
|
-}
|
|
|
-
|
|
|
-// Formatting of text with variables to 'embed'
|
|
|
-const char *FormatText(const char *text, ...)
|
|
|
-{
|
|
|
- int length = strlen(text);
|
|
|
- char *buffer = malloc(length + 20); // We add 20 extra characters, should be enough... :P
|
|
|
-
|
|
|
- va_list args;
|
|
|
- va_start(args, text);
|
|
|
- vsprintf(buffer, text, args); // NOTE: We use vsprintf() defined in <stdarg.h>
|
|
|
- va_end(args);
|
|
|
+ if (fontSize < defaultFontSize) fontSize = defaultFontSize;
|
|
|
|
|
|
- //strcat(buffer, "\0"); // We add a end-of-string mark at the end (not needed)
|
|
|
+ int spacing = fontSize / defaultFontSize;
|
|
|
|
|
|
- return buffer;
|
|
|
+ DrawTextEx(defaultFont, text, position, fontSize, spacing, color);
|
|
|
}
|
|
|
|
|
|
// Draw text using SpriteFont
|
|
|
// NOTE: If font size is lower than base size, base size is used
|
|
|
+// NOTE: chars spacing is NOT proportional to fontSize
|
|
|
void DrawTextEx(SpriteFont spriteFont, const char* text, Vector2 position, int fontSize, int spacing, Color tint)
|
|
|
{
|
|
|
int length = strlen(text);
|
|
@@ -345,7 +349,7 @@ void DrawTextEx(SpriteFont spriteFont, const char* text, Vector2 position, int f
|
|
|
glTexCoord2f((float)(c.x + c.w) / spriteFont.texture.width, (float)(c.y + c.h) / spriteFont.texture.height); glVertex2f(positionX + (c.w) * scaleFactor, position.y + (c.h) * scaleFactor);
|
|
|
glTexCoord2f((float)(c.x + c.w) / spriteFont.texture.width, (float)c.y / spriteFont.texture.height); glVertex2f(positionX + (c.w) * scaleFactor, position.y);
|
|
|
|
|
|
- positionX += (spriteFont.charSet[(int)text[i] - FIRST_CHAR].w + spacing) * scaleFactor;
|
|
|
+ positionX += ((spriteFont.charSet[(int)text[i] - FIRST_CHAR].w) * scaleFactor + spacing);
|
|
|
}
|
|
|
glEnd();
|
|
|
|
|
@@ -354,6 +358,22 @@ void DrawTextEx(SpriteFont spriteFont, const char* text, Vector2 position, int f
|
|
|
glDisable(GL_TEXTURE_2D);
|
|
|
}
|
|
|
|
|
|
+// Formatting of text with variables to 'embed'
|
|
|
+const char *FormatText(const char *text, ...)
|
|
|
+{
|
|
|
+ int length = strlen(text);
|
|
|
+ char *buffer = malloc(length + 20); // We add 20 extra characters, should be enough... :P
|
|
|
+
|
|
|
+ va_list args;
|
|
|
+ va_start(args, text);
|
|
|
+ vsprintf(buffer, text, args); // NOTE: We use vsprintf() defined in <stdarg.h>
|
|
|
+ va_end(args);
|
|
|
+
|
|
|
+ //strcat(buffer, "\0"); // We add a end-of-string mark at the end (not needed)
|
|
|
+
|
|
|
+ return buffer;
|
|
|
+}
|
|
|
+
|
|
|
// Measure string width for default font
|
|
|
int MeasureText(const char *text, int fontSize)
|
|
|
{
|
|
@@ -517,4 +537,132 @@ static int GetNextPOT(int num)
|
|
|
}
|
|
|
|
|
|
return num;
|
|
|
+}
|
|
|
+
|
|
|
+// Load a rBMF font file (raylib BitMap Font)
|
|
|
+static SpriteFont LoadRBMF(const char *fileName)
|
|
|
+{
|
|
|
+ // rBMF Info Header (16 bytes)
|
|
|
+ typedef struct {
|
|
|
+ char id[4]; // rBMF file identifier
|
|
|
+ char version; // rBMF file version
|
|
|
+ // 4 MSB --> main version
|
|
|
+ // 4 LSB --> subversion
|
|
|
+ char firstChar; // First character in the font
|
|
|
+ // NOTE: Depending on charDataType, it could be useless
|
|
|
+ short imgWidth; // Image width - always POT (power-of-two)
|
|
|
+ short imgHeight; // Image height - always POT (power-of-two)
|
|
|
+ short numChars; // Number of characters contained
|
|
|
+ short charHeight; // Characters height - the same for all characters
|
|
|
+ char compType; // Compression type:
|
|
|
+ // 4 MSB --> image data compression
|
|
|
+ // 4 LSB --> chars data compression
|
|
|
+ char charsDataType; // Char data type provided
|
|
|
+ } rbmfInfoHeader;
|
|
|
+
|
|
|
+ SpriteFont spriteFont;
|
|
|
+ Image image;
|
|
|
+
|
|
|
+ rbmfInfoHeader rbmfHeader;
|
|
|
+ unsigned int *rbmfFileData;
|
|
|
+ unsigned char *rbmfCharWidthData;
|
|
|
+
|
|
|
+ int charsDivisor = 1; // Every char is separated from the consecutive by a 1 pixel divisor, horizontally and vertically
|
|
|
+
|
|
|
+ FILE *rbmfFile = fopen(fileName, "rb"); // Define a pointer to bitmap file and open it in read-binary mode
|
|
|
+
|
|
|
+ fread(&rbmfHeader, sizeof(rbmfInfoHeader), 1, rbmfFile);
|
|
|
+
|
|
|
+ //printf("rBMF info: %i %i %i %i\n", rbmfHeader.imgWidth, rbmfHeader.imgHeight, rbmfHeader.numChars, rbmfHeader.charHeight);
|
|
|
+
|
|
|
+ spriteFont.numChars = (int)rbmfHeader.numChars;
|
|
|
+
|
|
|
+ image.width = (int)rbmfHeader.imgWidth;
|
|
|
+ image.height = (int)rbmfHeader.imgHeight;
|
|
|
+
|
|
|
+ int numPixelBits = rbmfHeader.imgWidth * rbmfHeader.imgHeight / 32;
|
|
|
+
|
|
|
+ rbmfFileData = (unsigned int *)malloc(numPixelBits * sizeof(unsigned int));
|
|
|
+
|
|
|
+ for(int i = 0; i < numPixelBits; i++) fread(&rbmfFileData[i], sizeof(unsigned int), 1, rbmfFile);
|
|
|
+
|
|
|
+ rbmfCharWidthData = (unsigned char *)malloc(spriteFont.numChars * sizeof(unsigned char));
|
|
|
+
|
|
|
+ for(int i = 0; i < spriteFont.numChars; i++) fread(&rbmfCharWidthData[i], sizeof(unsigned char), 1, rbmfFile);
|
|
|
+
|
|
|
+ printf("Just read image data and width data... Starting image reconstruction...");
|
|
|
+
|
|
|
+ // Re-construct image from rbmfFileData
|
|
|
+ //-----------------------------------------
|
|
|
+ image.pixels = (Color *)malloc(image.width * image.height * sizeof(Color));
|
|
|
+
|
|
|
+ for (int i = 0; i < image.width * image.height; i++) image.pixels[i] = BLANK; // Initialize array
|
|
|
+
|
|
|
+ int counter = 0; // Font data elements counter
|
|
|
+
|
|
|
+ // Fill image data (convert from bit to pixel!)
|
|
|
+ for (int i = 0; i < image.width * image.height; i += 32)
|
|
|
+ {
|
|
|
+ for (int j = 31; j >= 0; j--)
|
|
|
+ {
|
|
|
+ if (BIT_CHECK(rbmfFileData[counter], j)) image.pixels[i+j] = WHITE;
|
|
|
+ }
|
|
|
+
|
|
|
+ counter++;
|
|
|
+ }
|
|
|
+
|
|
|
+ printf("Image reconstructed correctly... now converting it to texture...");
|
|
|
+
|
|
|
+ spriteFont.texture = CreateTexture2D(image);
|
|
|
+
|
|
|
+ UnloadImage(image); // Unload image data
|
|
|
+
|
|
|
+ printf("Starting charSet reconstruction...\n");
|
|
|
+
|
|
|
+ // Reconstruct charSet using rbmfCharWidthData, rbmfHeader.charHeight, charsDivisor, rbmfHeader.numChars
|
|
|
+ spriteFont.charSet = (Character *)malloc(spriteFont.numChars * sizeof(Character)); // Allocate space for our character data
|
|
|
+
|
|
|
+ int currentLine = 0;
|
|
|
+ int currentPosX = charsDivisor;
|
|
|
+ int testPosX = charsDivisor;
|
|
|
+
|
|
|
+ for (int i = 0; i < spriteFont.numChars; i++)
|
|
|
+ {
|
|
|
+ spriteFont.charSet[i].value = (int)rbmfHeader.firstChar + i;
|
|
|
+ spriteFont.charSet[i].x = currentPosX;
|
|
|
+ spriteFont.charSet[i].y = charsDivisor + currentLine * ((int)rbmfHeader.charHeight + charsDivisor);
|
|
|
+ spriteFont.charSet[i].w = (int)rbmfCharWidthData[i];
|
|
|
+ spriteFont.charSet[i].h = (int)rbmfHeader.charHeight;
|
|
|
+
|
|
|
+ testPosX += (spriteFont.charSet[i].w + charsDivisor);
|
|
|
+
|
|
|
+ if (testPosX > spriteFont.texture.width)
|
|
|
+ {
|
|
|
+ currentLine++;
|
|
|
+ currentPosX = 2 * charsDivisor + (int)rbmfCharWidthData[i];
|
|
|
+ testPosX = currentPosX;
|
|
|
+
|
|
|
+ spriteFont.charSet[i].x = charsDivisor;
|
|
|
+ spriteFont.charSet[i].y = charsDivisor + currentLine * (rbmfHeader.charHeight + charsDivisor);
|
|
|
+ }
|
|
|
+ else currentPosX = testPosX;
|
|
|
+
|
|
|
+ //printf("Char %i data: %i %i %i %i\n", spriteFont.charSet[i].value, spriteFont.charSet[i].x, spriteFont.charSet[i].y, spriteFont.charSet[i].w, spriteFont.charSet[i].h);
|
|
|
+ }
|
|
|
+
|
|
|
+ printf("CharSet reconstructed correctly... Data should be ready...\n");
|
|
|
+
|
|
|
+ fclose(rbmfFile);
|
|
|
+
|
|
|
+ free(rbmfFileData); // Now we can free loaded data from RAM memory
|
|
|
+ free(rbmfCharWidthData);
|
|
|
+
|
|
|
+ return spriteFont;
|
|
|
+}
|
|
|
+
|
|
|
+static const char *GetExtension(const char *fileName)
|
|
|
+{
|
|
|
+ const char *dot = strrchr(fileName, '.');
|
|
|
+ if(!dot || dot == fileName) return "";
|
|
|
+ return dot + 1;
|
|
|
}
|