Henry Segal
Henry Segal

Reputation: 3

Why do taller letters appear deformed when rendering text with FreeType in OpenGL?

I have managed to draw text with FreeType in OpenGL 4, but the taller letters (e.g. g, d, f, etc.) are somehow being drawn too tall. This is what it looks like. This is what it is supposed to look like. The tall letters are too tall, while the "normal height" letters are just fine.

struct FontChar {
    float tx; // texcoord x position
    float tw; // texcoord x width
    glm::ivec2 size; // face->glyph->bitmap.width, face->glyph->bitmap.rows
    glm::ivec2 bearing; // face->glyph->bitmap_left, face->glyph->bitmap_top
    glm::ivec2 advance; // face->glyph->advance.x, face->glyph->advance.y
} fontChars[128]; // this is populated properly with FreeType

std::vector<float> vertices;

const float sx = 2.0f / 1920.0f;
const float sy = 2.0f / 1080.0f;

float x = 0.0f;
float y = 0.0f;
for (char c : text) {
    const float vx = x + fontChars[c].bearing.x * sx;
    const float vy = y + fontChars[c].bearing.y * sy;
    const float w = fontChars[c].size.x * sx;
    const float h = fontChars[c].size.y * sy;
    float tx = fontChars[c].tx;
    float tw = fontChars[c].tw;
    std::vector<float> quad = { // pos_x, pos_y, tex_x, tex_y
        vx, vy, tx, 0.0f,
        vx + w, vy, tx + tw, 0.0f,
        vx + w, vy - h, tx + tw, 1.0f,
        vx + w, vy - h, tx + tw, 1.0f,
        vx, vy - h, tx, 1.0f,
        vx, vy, tx, 0.0f
    };
    vertices.insert(vertices.begin(), quad.begin(), quad.end());
    x += float(fontChars[c].advance.x >> 6) * sx;
    y += float(fontChars[c].advance.y >> 6) * sy;
}

I then buffer the vertices into a vertex buffer, and then I draw it. The only code that could affect the height is const float h = fontChars[c].size.y * sy, but the size is taken straight from FreeType, and the sy works for the "normal height" letters. This leads me to believe that it could be due to the glyph textures being put into a texture atlas.

FT_Set_Pixel_Sizes(face, 0, size);

glPixelStorei(GL_UNPACK_ALIGNMENT, 1);

std::array<FontChar, 128> characters{};

unsigned int w = 0;
unsigned int h = 0;
for (unsigned char c = 0; c < 128; c++) {
    if (FT_Load_Char(face, c, FT_LOAD_BITMAP_METRICS_ONLY)) {
        throw std::runtime_error("Failed to load glyph");
    }
    w += face->glyph->bitmap.width;
    h = std::max(face->glyph->bitmap.rows, h); // maybe this is the issue???
}

GLuint texture;
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, w, h, 0, GL_RED, GL_UNSIGNED_BYTE, nullptr);
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);

unsigned int x = 0;
for (unsigned char c = 0; c < 128; c++) {
    if (FT_Load_Char(face, c, FT_LOAD_RENDER)) {
        throw std::runtime_error("Failed to load glyph");
    }
    glTexSubImage2D(GL_TEXTURE_2D, 0, x, 0, face->glyph->bitmap.width, face->glyph->bitmap.rows, GL_RED, GL_UNSIGNED_BYTE, face->glyph->bitmap.buffer);

    FontChar character = {
        (float)x / (float)w,
        (float)face->glyph->bitmap.width / (float)w,
        glm::ivec2(face->glyph->bitmap.width, face->glyph->bitmap.rows),
        glm::ivec2(face->glyph->bitmap_left, face->glyph->bitmap_top),
        glm::ivec2(face->glyph->advance.x, face->glyph->advance.y)
    };
    characters[c] = character;

    x += face->glyph->bitmap.width;
}

The only other place where I do anything that could influence this vertical stretching behavior is when I find the max height of the characters. I do this so I can find the proper dimensions of the texture atlas, which is just 1 character tall by n characters wide. I'm still not sure how this could cause the behavior though.

Upvotes: 0

Views: 185

Answers (1)

Henry Segal
Henry Segal

Reputation: 3

I have found the issue. My instincts were correct; the issue was related to the height of the texture atlas. I was not plugging the heights of the glyph bitmaps into the actual vertices, I was instead using the entire height of the texture. All I had to do was pass the heights of the characters into the FontChar struct when populating the fontChars array, and then I made my vertices go from 0.0f to the height instead of 0.0f to 1.0f. This worked except now all of my text was too tall. Then I realized that I am using an orthographic matrix which extends the x coordinates from [-1, 1] to [-width/height, width/height], and since I was using separate scale factors (sx and sy), my scaling was incorrect. To fix, I just got rid of sy and replaced every sy with sx. I also added 2 pixels between each texture in the atlas so I don't get any smearing between textures. Here is the final result.

Upvotes: 0

Related Questions