简体   繁体   中英

C++ freetype add border to text with color and lenght

I've made an OpenGL c++ program that can display some text into the screen. I've followed some tutorials and got help from this question . With that, I've managed to write a function called add_border but I didn't quite understand how this all works and couldn't manage to display border on the screen.

Here is what I have so far:

text.fs

#version 300 es
precision mediump float;

in vec2 vUV;
uniform sampler2D u_texture;
uniform vec3 textColor;
out vec4 fragColor;
void main()
{
    vec2 uv = vUV.xy;
    float text = texture(u_texture, uv).r;
    fragColor = vec4(textColor.rgb*text, text);
}

text.vs

#version 300 es
precision mediump float;
layout (location = 0) in vec4 in_attr;
out vec2 vUV;
uniform mat4 projection;
uniform mat4 model;

void main()
{
    vUV         = in_attr.zw;
        gl_Position = projection * model * vec4(in_attr.xy, 1.0f, 1.0f);
}

Text.cpp

void Text::render_text(std::string text, float x, float y, float z, std::string hex_color, float angle_rad, bool has_bg)
{   
    static const int scale = 1;

    y /= 2;

    shader.use();
    glUniform3f(glGetUniformLocation(shader.ID, "textColor"), 0.9, 0.9, 0.9);
    color.get_color_float(Utils::BLUE));
    glActiveTexture(GL_TEXTURE0);
    glBindVertexArray(VAO);

    GLfloat vertices[6][4] = {
        { 0.0,  1.0,   0.0, 0.0 },
        { 0.0,  0.0,   0.0, 1.0 },
        { 1.0,  0.0,   1.0, 1.0 },

        { 0.0,  1.0,   0.0, 0.0 },
        { 1.0,  0.0,   1.0, 1.0 },
        { 1.0,  1.0,   1.0, 0.0 }
    };

    glBindBuffer(GL_ARRAY_BUFFER, VBO);
    glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices), vertices); // Be sure to use glBufferSubData and not glBufferData
    glBindBuffer(GL_ARRAY_BUFFER, 0);


    glm::mat4 rotateM = glm::rotate(glm::mat4(1.0f), glm::radians(angle_rad), glm::vec3(0.0f, 0.0f, 1.0f));
    glm::mat4 transOriginM = glm::translate(glm::mat4(1.0f), glm::vec3(x, y, z));

    std::string::const_iterator c;
    GLfloat char_x = 0.0f;

    for (c = text.begin(); c != text.end(); c++)
    {
        Character ch = Characters[*c];

        GLfloat w = ch.Size.x * scale;
        GLfloat h = ch.Size.y * scale;
        GLfloat xrel = char_x + ch.Bearing.x * scale;
        GLfloat yrel = y - (ch.Size.y - ch.Bearing.y) * scale;

        char_x += (ch.Advance >> 6) * scale; )

        glm::mat4 scaleM = glm::scale(glm::mat4(1.0f), glm::vec3(w, h, 1.0f));
        glm::mat4 transRelM = glm::translate(glm::mat4(1.0f), glm::vec3(xrel, yrel, z));

        glm::mat4 modelM = transOriginM * rotateM * transRelM * scaleM;

        GLint model_loc = glGetUniformLocation(shader.ID, "model");
        glUniformMatrix4fv(model_loc, 1, GL_FALSE, glm::value_ptr(modelM));


        glBindTexture(GL_TEXTURE_2D, ch.TextureID);

        glDrawArrays(GL_TRIANGLES, 0, 6);
    }
    glBindVertexArray(0);
    glBindTexture(GL_TEXTURE_2D, 0);
}

void Text::add_border(){
    if(FT_Outline_New(ft_lib, 10000, 1000, &border) != 0){
        VI_ERROR("Error");
    }

    FT_Stroker_New(ft_lib, &stroker);
    FT_Stroker_Set(stroker, 2 * 64, FT_STROKER_LINECAP_ROUND, FT_STROKER_LINEJOIN_ROUND, 0);
    FT_Get_Glyph(face->glyph, &glyph);
    FT_Glyph_Stroke(&glyph, stroker, false);

    if (glyph->format == FT_GLYPH_FORMAT_OUTLINE)
    {
        FT_Raster_Params params;
        params.flags = FT_RASTER_FLAG_AA | FT_RASTER_FLAG_DIRECT;
        //params.gray_spans = outlineRenderCallback;
        //params.user = user_data;
        FT_Outline_Render(ft_lib, &border, &params);
    }
}



Text::Text(text_attributes&& atrib, int gl_width, int gl_height) 
    :SCR_WIDTH(gl_width), SCR_HEIGHT(gl_height)
{
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

    shader = Shader("./src/opengl/shaders/text.vs", "./src/opengl/shaders/text.fs");

    glm::mat4 projection = glm::ortho(0.0f, static_cast<float>(SCR_WIDTH), 0.0f, static_cast<float>(SCR_HEIGHT));
    shader.use();
    glUniformMatrix4fv(glGetUniformLocation(shader.ID, "projection"), 1, GL_FALSE, glm::value_ptr(projection));

    if (FT_Init_FreeType(&ft_lib))
    {
        VI_ERROR("ERROR::FREETYPE: Could not init FreeType Library");
        exit(0);
    }

    std::string font_name = "./src/opengl/fonts/" + *atrib.__font_name +  ".ttf";
    
    if (FT_New_Face(ft_lib, font_name.c_str(), 0, &face)) {
        VI_ERROR("ERROR::FREETYPE: Failed to load font");
        exit(0) ;
    } else {
        FT_Set_Pixel_Sizes(face, 0, atrib.__font_size);
        glPixelStorei(GL_UNPACK_ALIGNMENT, 1);

        for (unsigned char c = 0; c < 128; c++)
        {
            if (FT_Load_Char(face, c, FT_LOAD_RENDER))
            {
                VI_ERROR("ERROR::FREETYTPE: Failed to load Glyph");
                continue;
            }

            add_border();


            GLuint texture;
            glGenTextures(1, &texture);
            glBindTexture(GL_TEXTURE_2D, texture);
            glTexImage2D(
                GL_TEXTURE_2D,
                0,
                GL_RED,
                face->glyph->bitmap.width,
                face->glyph->bitmap.rows,
                0,
                GL_RED,
                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,
                glm::ivec2(face->glyph->bitmap.width, face->glyph->bitmap.rows),
                glm::ivec2(face->glyph->bitmap_left, face->glyph->bitmap_top),
                static_cast<unsigned int>(face->glyph->advance.x)
            };
            Characters.insert(std::pair<char, Character>(c, character));
        }
        glBindTexture(GL_TEXTURE_2D, 0);
    }
    FT_Done_Face(face);
    FT_Outline_Done(ft_lib, &border);
    FT_Done_FreeType(ft_lib);
    
    glGenVertexArrays(1, &VAO);
    glGenBuffers(1, &VBO);
    glBindVertexArray(VAO);
    glBindBuffer(GL_ARRAY_BUFFER, VBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(float) * 6 * 4, NULL, GL_DYNAMIC_DRAW);
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 4 * sizeof(float), 0);
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glBindVertexArray(0);
}

I've read somewhere that changing FT_LOAD_RENDER to FT_LOAD_DEFAULT in FT_Load_Char() causes the text to disappear completely

Right now this code compiles and runs just fine, however there is no border around the text. I want to add some parameters to the add_border function to have it display different border's with the given parameters such as border thickness and border color. What am I doing wrong? How can I have it display border like I want it to?

Based on the comments above:

The below example is based on my own lib (although very shortened), where i am collecting the coverage into an span array and render it later. It is also possible to render the coverage(s)/scanline directly onto a bitmap.

//structure to set the bounds
//and to hold a list of the coverage(s)
//basically the 'raw' data to produce a single glyph image
struct my_spans {
    int x_min, y_min;
    int x_max, y_max;
    FT_Span *span_array; //needed later to reproduce the (glyph) image
};

struct my_spans spans; //initialize properly

//set the raster params accordingly
FT_Raster_Params params;
memset(&params, 0, sizeof(params));
params.flags = FT_RASTER_FLAG_AA | FT_RASTER_FLAG_DIRECT; //direct rendering
params.gray_spans = outlineRenderCallback; //our callback below
params.user = &spans; //userdata will be our struct

//will be called for each scanline, thus the single y coordinate
void outlineRenderCallback(
    int y,
    int count,
    const FT_Span *ft_spans,
    void *user
){
    struct my_spans *spans = (struct my_spans*) user;
    FT_Span span;
    
    //increase array capacity by count

    //foreach span
    for (int i=0; i < count; ++i, ++ft_spans) {
        
        //horizontal boundary
        spans->x_min = min(spans->x_min, ft_spans->x);
        spans->x_max = max(spans->x_max, ft_spans->x + ft_spans->len - 1);

        //span copy, although possible to write into array directly
        span.x = ft_spans->x;
        span.y = y;
        span.len = ft_spans->len;
        span.coverage = ft_spans->coverage;

        //push span to array, if not written directly
    }

    //vertical boundary
    spans->y_min = min(spans->y_min, y);
    spans->y_max = max(spans->y_max, y);
}

Reference:

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM