Implementing visualization of remote fields

So embarrassing that I've never gotten a reliable font rendering system in my application before. So I decided to put an end to this.

While I was looking for examples of font rendering, I happened to go into this one that everyone compliments.

However, I lack the experience and knowledge to do this.

Here's a quick rundown of what I did:

First, using the Freetype2 library, I loaded the character ('A') into memory as a bitmap.

unsigned char u8Character = 'A';
int32_t u32Character = 0;
utf8::utf8to32(&u8Character, &u8Character + 1, &u32Character);
FT_Set_Char_Size(face, 0, 64 * 64, 300, 300);    
FT_Load_Char(face, u32Character, FT_LOAD_RENDER);

      

Second, I calculated the distance field as the Chebyshev distance from each pixel of the bitmap to create a floating point map.

Note. I honestly don't know how to do this. The algorithm below is full guessing.

template <typename input_type, typename output_type>
output_type chebyshev_distance( input_type from_x
                              , input_type from_y
                              , input_type to_x
                              , input_type to_y )
{
    input_type dx = std::abs(to_x - from_x);
    input_type dy = std::abs(to_y - from_y);
    return static_cast<output_type>(dx > dy ? dx : dy);
}
void GenerateSigendDistanceFieldFrom( const unsigned char* inputBuffer
                                    , int width
                                    , int height
                                    , float* outputBuffer
                                    , bool normalize = false)
{
    for (int iy = 0; iy < height; ++iy)
    {
        for (int ix = 0; ix < width; ++ix)
        {
            int index = iy*width + ix;
            unsigned char value = inputBuffer[index];
            int indexMax = width*height;
            int indexMin = 0;
            int far = width > height ? width : height;
            bool found = false;
            for (int distance = 1; distance < far; ++distance)
            {
                int xmin = (ix - distance) >= 0 ? ix - distance : 0;
                int ymin = (iy - distance) >= 0 ? iy - distance : 0;
                int xmax = (ix + distance) < width ? ix + distance+1 : width;
                int ymax = (iy + distance) < height ? iy + distance+1 : height;
                int x = xmin;
                int y = ymin;
                auto fCompareAndFill = [&]() -> bool
                {
                    if (value != inputBuffer[y*width + x])
                    {
                        outputBuffer[index] = chebyshev_distance<int, float>(ix, iy, x, y);
                        if (value < 0xff/2) outputBuffer[index] *= -1;
                        //outputBuffer[index] = distance;
                        return true;
                    }
                    return false;
                };
                while (x < xmax)
                {
                    if (fCompareAndFill())
                    {
                        found = true;
                        break;
                    }
                    ++x;
                }
                --x;
                if (found == true){ break; }
                while (y < ymax)
                {
                    if (fCompareAndFill())
                    {
                        found = true;
                        break;
                    }
                    ++y;
                }
                --y;
                if (found == true){ break; }
                while (x >= xmin)
                {
                    if (fCompareAndFill())
                    {
                        found = true;
                        break;
                    }
                    --x;
                }
                ++x;
                if (found == true){ break; }
                while (y >= ymin)
                {
                    if (fCompareAndFill())
                    {
                        found = true;
                        break;
                    }
                    --y;
                }
                if (found == true){ break; }

            } // for( int distance = 1; distance < far; ++distance )

        } // for( int ix = 0; ix < width; ++ix )

    } // for( int iy = 0; iy < height; ++iy )

    if( normalize )
    {
        float min = outputBuffer[0];
        float max = outputBuffer[0];
        for( int i = 0; i < width*height; ++i )
        {
            if( outputBuffer[i] < min )
                min = outputBuffer[i];
            if( outputBuffer[i] > max )
                max = outputBuffer[i];
        }
        float denominator = (max - min);
        float newMin = min / denominator;
        for( int i = 0; i < width*height; ++i )
        {
            outputBuffer[i] /= denominator;
            outputBuffer[i] -= newMin;
        }
    }

} // GenerateSigendDistanceFieldFrom

      

Third, as a test, I mapped this character texture to full 800x600 screen to see how they expand. The texture "GL_LINEAR" was selected. The result was terrible.

! 1! Render Alpha as red value.

const GLchar fssource[] =
    "#version 440 \n"
    "out vec4 v_color;"
    "in vec2 v_uv;"
    "uniform sampler2D u_texture;"
    "void main()"
    "{"
    "   float mask = texture(u_texture, v_uv).a;"
    "   v_color = vec4(mask,0,0,1);"
    "}"
;

      

enter image description here

! 2! Render text. the threshold for alpha is 0.5

const GLchar fssource[] =
    "#version 440 \n"
    "out vec4 v_color;"
    "in vec2 v_uv;"
    "uniform sampler2D u_texture;"
    "void main()"
    "{"
    "   vec4 result = vec4(1,1,1,1);"
    "   float mask = texture(u_texture, v_uv).a;"
    "   if( mask >= 0.5 ) { result.a = 1; }\n"
    "   else { result.a = 0; }\n"
    "   v_color = result;"
    "}"

      

enter image description here

! 3! Render text. the threshold for alpha is 0.7

const GLchar fssource[] =
    "#version 440 \n"
    "out vec4 v_color;"
    "in vec2 v_uv;"
    "uniform sampler2D u_texture;"
    "void main()"
    "{"
    "   vec4 result = vec4(1,1,1,1);"
    "   float mask = texture(u_texture, v_uv).a;"
    "   if( mask >= 0.7 ) { result.a = 1; }\n"
    "   else { result.a = 0; }\n"
    "   v_color = result;"
    "}"

      

enter image description here

The font looks bumpy and apparently the distance field is too bright. it is assumed that the algorithm operates with a threshold of 0.5. Not only is the result wrong, it takes too long to generate the distance field. Thus, I could not use a high resolution image as input.

Here I am doing something clearly wrong, but it looks like I figured out myself how to generate the correct result.

But please help me if you know what I am doing wrong.

Below is the entire source file:

#include <iostream>
#include <iomanip>
#include <algorithm>
#include <fstream>
#include <vector>
#include <cstdint>
#include <climits>
#include "ft2build.h"
#include FT_FREETYPE_H
#include "../utf8_v2_3_4/Source/utf8.h"
#include <SDL.h>
#include "../Glew/glew.h"
#include <gl/GL.h>
#undef main
template <typename input_type, typename output_type>
output_type chebyshev_distance( input_type from_x
                              , input_type from_y
                              , input_type to_x
                              , input_type to_y )
{
    input_type dx = std::abs(to_x - from_x);
    input_type dy = std::abs(to_y - from_y);
    return static_cast<output_type>(dx > dy ? dx : dy);
}
void GenerateSigendDistanceFieldFrom( const unsigned char* inputBuffer
                                    , int width
                                    , int height
                                    , float* outputBuffer
                                    , bool normalize = false)
{
    for (int iy = 0; iy < height; ++iy)
    {
        for (int ix = 0; ix < width; ++ix)
        {
            int index = iy*width + ix;
            unsigned char value = inputBuffer[index];
            int indexMax = width*height;
            int indexMin = 0;
            int far = width > height ? width : height;
            bool found = false;
            for (int distance = 1; distance < far; ++distance)
            {
                int xmin = (ix - distance) >= 0 ? ix - distance : 0;
                int ymin = (iy - distance) >= 0 ? iy - distance : 0;
                int xmax = (ix + distance) < width ? ix + distance+1 : width;
                int ymax = (iy + distance) < height ? iy + distance+1 : height;
                int x = xmin;
                int y = ymin;
                auto fCompareAndFill = [&]() -> bool
                {
                    if (value != inputBuffer[y*width + x])
                    {
                        outputBuffer[index] = chebyshev_distance<int, float>(ix, iy, x, y);
                        if (value < 0xff/2) outputBuffer[index] *= -1;
                        //outputBuffer[index] = distance;
                        return true;
                    }
                    return false;
                };
                while (x < xmax)
                {
                    if (fCompareAndFill())
                    {
                        found = true;
                        break;
                    }
                    ++x;
                }
                --x;
                if (found == true){ break; }
                while (y < ymax)
                {
                    if (fCompareAndFill())
                    {
                        found = true;
                        break;
                    }
                    ++y;
                }
                --y;
                if (found == true){ break; }
                while (x >= xmin)
                {
                    if (fCompareAndFill())
                    {
                        found = true;
                        break;
                    }
                    --x;
                }
                ++x;
                if (found == true){ break; }
                while (y >= ymin)
                {
                    if (fCompareAndFill())
                    {
                        found = true;
                        break;
                    }
                    --y;
                }
                if (found == true){ break; }

            } // for( int distance = 1; distance < far; ++distance )

        } // for( int ix = 0; ix < width; ++ix )

    } // for( int iy = 0; iy < height; ++iy )

    if( normalize )
    {
        float min = outputBuffer[0];
        float max = outputBuffer[0];
        for( int i = 0; i < width*height; ++i )
        {
            if( outputBuffer[i] < min )
                min = outputBuffer[i];
            if( outputBuffer[i] > max )
                max = outputBuffer[i];
        }
        float denominator = (max - min);
        float newMin = min / denominator;
        for( int i = 0; i < width*height; ++i )
        {
            outputBuffer[i] /= denominator;
            outputBuffer[i] -= newMin;
        }
    }

} // GenerateSigendDistanceFieldFrom


namespace
{
    SDL_Window* window = NULL;
    SDL_Surface* screenSurface = NULL;
    FT_Library freetype;
    FT_Face face;
    SDL_GLContext glContext;
    GLuint glProgram = 0;
    GLuint vbo = 0;
    GLuint vao = 0;
    GLuint glTexture = 0;
}

GLuint MakeShader( GLenum shaderType, const char* source, int slen )
{
    auto shader = glCreateShader(shaderType);
    glShaderSource(shader, 1, (const GLchar**)&source, &slen);
    glCompileShader(shader);
    GLint success;
    glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
    if (success == GL_FALSE)
    {
        std::vector<GLchar> glInfoLogBuffer;
        int len;
        glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &len);
        glInfoLogBuffer.resize(len + 1);
        GLsizei outlen;
        glGetShaderInfoLog(shader, glInfoLogBuffer.size(), &outlen, glInfoLogBuffer.data());
        glInfoLogBuffer.back() = 0;
        std::cout << glInfoLogBuffer.data() << std::endl;
        return 0;
    }
    return shader;
}

GLuint MakeProgram( GLuint vshader, GLuint fshader )
{
    auto program = glCreateProgram();
    glAttachShader(program, vshader);
    glAttachShader(program, fshader);
    glLinkProgram(program);
    GLint success;
    glGetProgramiv(program, GL_LINK_STATUS, &success);
    if( success == GL_FALSE )
    {
        int len;
        glGetProgramiv(program, GL_INFO_LOG_LENGTH, &len);
        std::vector<GLchar> buffer;
        buffer.resize(len+1);
        buffer.back() = 0;
        glGetProgramInfoLog(program, buffer.size(), &len, buffer.data());
        std::cout << buffer.data() << std::endl;
        return 0;
    }
    return program;
}

int Initialize()
{
    if (SDL_Init(SDL_INIT_VIDEO) < 0)
    {
        std::cout << "SDL could not initialize!";
        return -1;
    }

    window = SDL_CreateWindow("My Window", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 800, 600, SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN);
    if (!window)
    {
        std::cout << "Window could not be created!";
        return -1;
    }
    screenSurface = SDL_GetWindowSurface(window);
    SDL_FillRect(screenSurface, 0, SDL_MapRGB(screenSurface->format, 0xFF, 0xFF, 0xFF));
    SDL_UpdateWindowSurface(window);


    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1);
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);

    glContext = SDL_GL_CreateContext(window);

    SDL_GL_SetSwapInterval(1);

    GLenum glError = glewInit();
    if( glError != GLEW_OK )
    {
        std::cout << "Failed to initialize glew" << std::endl;
        return -1;
    }

    // ----------------------
    // Vertex Shader
    const GLchar vssource[] =
        "#version 440 \n"
        "layout(location=0) in vec3 a_position;"
        "layout(location=1) in vec2 a_uv;"
        "out vec2 v_uv;"
        "void main()"
        "{"
        " gl_Position = vec4(a_position,1);"
        " v_uv = a_uv;"
        "}\n"
    ;
    auto vshader = MakeShader(GL_VERTEX_SHADER, vssource, _countof(vssource));
    // --------------------
    // Fragment Shader
    //const GLchar fssource[] =
    //    "#version 440 \n"
    //    "out vec4 v_color;"
    //    "in vec2 v_uv;"
    //    "uniform sampler2D u_texture;"
    //    "void main()"
    //    "{"
    //    "   float mask = texture(u_texture, v_uv).a;"
    //    "   v_color = vec4(mask,0,0,1);"
    //    "}"
    //;
    const GLchar fssource[] =
        "#version 440 \n"
        "out vec4 v_color;"
        "in vec2 v_uv;"
        "uniform sampler2D u_texture;"
        "void main()"
        "{"
        "   vec4 result = vec4(1,1,1,1);"
        "   float mask = texture(u_texture, v_uv).a;"
        "   if( mask >= 0.7 ) { result.a = 1; }\n"
        "   else { result.a = 0; }\n"
        "   v_color = result;"
        "}"
    ;
    auto fshader = MakeShader(GL_FRAGMENT_SHADER, fssource, _countof(fssource));

    // --------------------
    // Shader Program
    glProgram = MakeProgram( vshader, fshader );

    // --------------------
    // Vertex Buffer Object
    float vb[] =
    {
        -1, -1, 0,
        1,  -1, 0,
        -1, 1,  0,

        1,  -1, 0,
        1,  1,  0,
        -1, 1,  0,

        0, 0,
        1, 0,
        0, 1,

        1, 0,
        1, 1,
        0, 1,
    };
    glGenBuffers(1, &vbo);
    glBindBuffer(GL_ARRAY_BUFFER, vbo);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vb), vb, GL_STATIC_DRAW);
    glBindBuffer(GL_ARRAY_BUFFER, 0);

    // --------------------
    // Vertex Array Object
    glGenVertexArrays(1, &vao);
    glBindVertexArray(vao);
    glEnableVertexAttribArray(0);
    glEnableVertexAttribArray(1);
    glBindBuffer(GL_ARRAY_BUFFER, vbo);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);
    glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, (const GLvoid*)(sizeof(float)*18));
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glBindVertexArray(0);

    //
    // Freetype
    //

    FT_Error error = FT_Init_FreeType(&freetype);
    if (error)
    {
        std::cout << "FreeType: error occured with error code: " << error << std::endl;
    }
    error = FT_New_Face(freetype, "C:/Windows/Fonts/Arial.ttf", 0, &face);
    if (error)
    {
        std::cout << "FreeType: error occured with error code: " << error << std::endl;
    }
    error = FT_Set_Char_Size(face, 0, 64 * 64, 300, 300);
    if (error)
    {
        std::cout << "FreeType: error occured with error code: " << error << std::endl;
    }

    unsigned char u8Character = 'A';
    int32_t u32Character = 0;
    utf8::utf8to32(&u8Character, &u8Character + 1, &u32Character);
    error = FT_Load_Char(face, u32Character, FT_LOAD_RENDER);
    if (error)
    {
        std::cout << "FreeType: error occured with error code: " << error << std::endl;
    }

    auto bitmap = face->glyph->bitmap;
    const int width = bitmap.width;
    const int height = bitmap.rows;
    const int size = width*height;
    std::vector<float> outputBuffer;
    outputBuffer.resize(size);
    GenerateSigendDistanceFieldFrom(face->glyph->bitmap.buffer, width, height, outputBuffer.data(), true);

    std::ofstream ofs("testout.txt");
    for (int i = 0; i < height; ++i)
    {
        for (int j = 0; j < width; ++j)
        {
            ofs << bitmap.buffer[i*width + j] << ' ';
        }
        ofs << std::endl;
    }
    ofs << std::endl;
    for (int i = 0; i < height; ++i)
    {
        for (int j = 0; j < width; ++j)
        {
            ofs << std::setw(6) << std::setprecision(2) << std::fixed << outputBuffer[i*width + j];
        }
        ofs << std::endl;
    }

    // ----


    // --------------------
    // Texture
    std::vector<float> invertY;
    invertY.reserve(size);
    for( int i = height-1; i >= 0; --i )
    {
        for( int j = 0; j < width; ++j )
        {
            invertY.push_back(outputBuffer[i*width+j]);
        }
    }
    glGenTextures(1, &glTexture);
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, glTexture);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, width, height, 0, GL_ALPHA, GL_FLOAT, invertY.data());
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glGenerateMipmap(GL_TEXTURE_2D);


    glClearColor(0.5f,0.5f,1.0f,1.0f);

    return 0;
}
void Release()
{
    SDL_DestroyWindow(window);

    SDL_Quit();
}
int main(int argc, char* argv[])
{
    Initialize();
    bool quit = false;
    while( !quit )
    {
        SDL_Event e;
        if( SDL_PollEvent(&e) != 0 )
        {
            if (e.type == SDL_QUIT)
            {
                quit = true;
            }
        } // if( SDL_PollEvent(&e) != 0 )

        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);


        glEnable(GL_BLEND);
        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
        glUseProgram(glProgram);
        GLint loc = glGetUniformLocation(glProgram, "u_texture");
        glUniform1i(loc, 0);
        glBindVertexArray(vao);
        glDrawArrays(GL_TRIANGLES, 0, 6);
        glBindVertexArray(0);
        glUseProgram(0);

        SDL_GL_SwapWindow(window);
    } // while( !quit )
    Release();
    return 0;
}

      

+3


source to share


2 answers


If I understood correctly, you set the size of the char bitmap to 64 (= 64.64 to Freetype 26.6 fixed point type), but then you need to stretch the bitmap to a larger size, hence scaling.



I suggest you set the size of the char (with FT_Set_Char_Size) to a size equal to or greater than the final size. Then the rest of the SW should just keep the bitmap as it is, or eventually shrink it. This does not mean loss of quality, while scaling (making the x bitmap larger) causes the observed problems. Then, for zooming out, any interpolation scheme will give you decent results.

+1


source


To get a smooth edge you need to use a smooth edge blend, perhaps 0.5 alpha to 0.6. As for the field with the given distances taking too long, you shouldn't generate the texture at runtime, instead you should first create it and then just load it with all the required data.



0


source







All Articles