copengl-estexturesraspberry-pi3freetype2

How to fix freetype incorrectly loading characters


The FreeType library opens and loads a font without throwing errors, but when I try and use the character textures they're repeated and upsidedown.

I'm creating a graphics-based program using OpenGL, to deal with fonts and text I'm using FreeType. When I load the texture and set it up, the correct sizes and glyph attributes (width, advance etc...) are provided, but when I use the bitmap to create a texture and use that texture it's incorrect (as described earlier).

here is the initialisation code:

FT_Init_FreeType(&ft);
FT_New_Face(ft, "Roboto.ttf", 0, &face);
FT_Set_Pixel_Sizes(face, 0, 100);
getChars();
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
FT_Done_Face(face);
FT_Done_FreeType(ft);

here is the code that then gets the character textures:

void getChars(){
  int index = 0;
  for (GLubyte c = 0; c < 128; c++){
    if (FT_Load_Char(face, c, FT_LOAD_RENDER)){
      printf("couldn't load character\n");
      continue;
    }
    GLuint texture;
    glGenTextures(1,&texture);
    glBindTexture(GL_TEXTURE_2D, texture);
    glTexImage2D(GL_TEXTURE_2D,0,GL_RGBA,face->glyph->bitmap.width,face->glyph->bitmap.rows,0,GL_RGBA,GL_UNSIGNED_BYTE,face->glyph->bitmap.buffer);
    //glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    //glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    //glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    Character character = {texture, {face->glyph->bitmap.width, face->glyph->bitmap.rows}, {face->glyph->bitmap_left, face->glyph->bitmap_top}, face->glyph->advance.x};
    chars[index++] = character;
  }
  printf("Got characters\n");
}

I've commented out the glTexParameteri s that didn't make any difference to the way the texture was displayed

Here are the structures used to store the textures:

typedef struct vec2 {
  float x;
  float y;
} vec2;

typedef struct Character {
  GLuint Texture;
  vec2 Size;
  vec2 Bearing;
  GLuint Advance;
} Character;

Character chars[128];

Finally here is the code that displays a string passed to it:

void drawText(char* inString, float x, float y, float scale, colour col) {
  for (char* string = inString; *string != '\0'; string++){
    Character ch = chars[(int) *string];
    glBindTexture( GL_TEXTURE_2D, ch.Texture);

    printf("character\n");

    float xpos = x + ch.Bearing.x * scale * 2 /glutGet(GLUT_WINDOW_WIDTH);
    float ypos = y - (ch.Size.y - ch.Bearing.y) * scale * 2 /glutGet(GLUT_WINDOW_HEIGHT);

    float w = ch.Size.x * scale * 2 /glutGet(GLUT_WINDOW_WIDTH);
    float h = ch.Size.y * scale * 2 /glutGet(GLUT_WINDOW_HEIGHT);

    //draws the textured QUAD
    glBegin(GL_QUADS);
    //set the colour of the quad the texutre blends with to white with the appropriate opacity
    glColor4f(col.R, col.G, col.B, col.A);
    glTexCoord2f(0.0,0.0);
    glVertex2f(xpos, ypos);
    glTexCoord2f(0.0,1.0);
    glVertex2f(xpos, ypos+h);
    glTexCoord2f(1.0,1.0);
    glVertex2f(xpos+w, ypos+h);
    glTexCoord2f(1.0,0.0);
    glVertex2f(xpos+w, ypos);
    glEnd();

    x += (ch.Advance >> 6) * scale * 2 /glutGet(GLUT_WINDOW_WIDTH);
    printf("w %1.0f\n",w);
    printf("h %1.0f\n",h);
    printf("x %1.0f\n",xpos);
    printf("y %1.0f\n",ypos);
  }
}

I expect the normal text to be loaded, but as described earlier, each character looks like its own texture map

this is the way the text looks (ignore the image behind it)


Solution

  • The buffer which is returned by FT_Load_Char contains one byte for each pixel of the glyph.
    If you would use modern OpenGL the you could use the format GL_RED for the bitmap format and the internal texture image format. the rest would do the Shader program.
    Since you use Legacy OpenGL you've to use a format that converts the byte value to an RGB value. Use GL_LUMINANCE for that. GL_LUMINANCE converts "into an RGBA element by replicating the luminance value three times for red, green, and blue and attaching 1 for alpha".

    glTexImage2D(
        GL_TEXTURE_2D,
        0,
        GL_LUMINANCE,
        face->glyph->bitmap.width,
        face->glyph->bitmap.rows,
        0,
        GL_LUMINANCE,
        GL_UNSIGNED_BYTE,
        face->glyph->bitmap.buffer
    );
    

    Note that two-dimensional texturing has to be enabled, see glEnable:

    glEnable(GL_TEXTURE_2D)
    

    If you want to draw the text with an transparent background, then you can use GL_ALPHA:

    glTexImage2D(
        GL_TEXTURE_2D,
        0,
        GL_ALPHA,
        face->glyph->bitmap.width,
        face->glyph->bitmap.rows,
        0,
        GL_ALPHA,
        GL_UNSIGNED_BYTE,
        face->glyph->bitmap.buffer
    );
    

    When you draw the text, then you've to enable Blending:

    void drawText(char* inString, float x, float y, float scale, colour col) {
    
        glEnable(GL_BLEND);
        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    
        glEnable(GL_TEXTURE_2D);
    
        // [...]
    
        glDisable(GL_TEXTURE_2D);
        glDisable(GL_BLEND);
    }
    

    Further glPixelStorei(GL_UNPACK_ALIGNMENT, 1); should be called before getChars();.