【问题标题】:SDL_TTF rendering garbage to textureSDL_TTF 将垃圾渲染到纹理
【发布时间】:2016-11-15 11:56:37
【问题描述】:

我正在尝试使用 OpenGL 和 SDL,使用 SDL_ttf 将文本渲染到纹理,但代码正在渲染垃圾。

我的“渲染到纹理代码”:

GLuint textToTexture(std::string & text, TTF_Font* font, glm::vec4 textColour, glm::vec4 bgColour)
{
    if (!TTF_WasInit())
    {
        if (TTF_Init() == -1)
            exit(6);
    }
    SDL_Color colour = { (Uint8)(textColour.r*255), (Uint8)(textColour.g*255), (Uint8)(textColour.b*255), (Uint8)(textColour.a*255) };
    SDL_Color bg = { (Uint8)(bgColour.r*255), (Uint8)(bgColour.g*255), (Uint8)(bgColour.b*255), (Uint8)(bgColour.a*255) };

    SDL_Surface *stringImage = NULL;
    stringImage = TTF_RenderText_Blended(font, text.c_str(), colour);

    if (stringImage == NULL)
    {
        exit(5);
    }

    GLuint trueH = powerofTwo(stringImage->h);
    GLuint trueW = powerofTwo(stringImage->w);
    unsigned char* pixels = NULL;
    GLuint w = stringImage->w;
    GLuint h = stringImage->h;
    GLuint colours = stringImage->format->BytesPerPixel;
    pixels = padTexture((unsigned char*)stringImage->pixels, w, h, pixels, trueW, trueH, colours);
    GLuint format, internalFormat;
    if (colours == 4) {  

        if (stringImage->format->Rmask == 0x000000ff)
            format = GL_RGBA;
        else
            format = GL_BGRA;
    }
    else {      

        // no alpha
        if (stringImage->format->Rmask == 0x000000ff)
            format = GL_RGB;
        else
            format = GL_BGR;
    }
    internalFormat = (colours == 4) ? GL_RGBA : GL_RGB;


    GLuint texId = 0;
    //GLuint texture;

    glGenTextures(1, &texId);

    glBindTexture(GL_TEXTURE_2D, texId);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, trueW, trueH, 0,format, GL_UNSIGNED_BYTE, pixels);

    // SDL surface was used to generate the texture but is no longer
    // required. Release it to free memory
    SDL_FreeSurface(stringImage);
    free(pixels)
    return texId;
}

计算填充正确尺寸的代码:

int powerofTwo(int num)
{
    if (num != 0)
    {
        num--;
        num |= num >> 1;   // Divide by 2^k for consecutive doublings of k up to 32,
        num |= num >> 2;   // and then or the results.
        num |= num >> 4;
        num |= num >> 8;
        num |= num >> 16;
        num++;
    }
    return num;
}

最后,将字节复制到正确尺寸纹理的代码:

unsigned char* padTexture(unsigned char * src, int srcW, int srcH, unsigned char * dest, int width, int height, int bpp)
{
    dest = (unsigned char*)calloc(1, width*height*bpp);
    for (int i = 0; i < srcH; i++)
    {
        memcpy(dest + (width*i*bpp),src + (srcW*i*bpp), srcW*bpp);
    }
    return dest;
}

这段代码的结果如下:[![Garled Texture][1]][1]

我已确认并错误检查了 SDL_TTF 已在代码库的其他地方正确初始化,并且字体也正在加载。 我用三种不同的ttf字体测试过,结果都一样。

另外,如果我使用任何其他 TTF_rendering 函数(Shaded、Solid 等),则会呈现一个实心四边形,并且 textToTexture 函数中的“colors”变量也以 1 结束。

补充:

如前所述,我用三种 ttf 字体进行了测试: MavenPro-Regular,

HelveticaNeueLTStd-Th

还有一个我在网上找到的。 我试图渲染字符串“Select Scenario”。

预填充的图像尺寸为 138x25 像素。

后填充图像尺寸为 256x32 像素。

更新 1:

修复 bpp 问题后,新纹理如下:

每次运行程序时,此图像都会发生变化。

更新 2: 通过填充图像修复额外的发现错误并将像素数据设置为纹理本身后,当我使用 TTF_RenderText_Blended 时,我得到的只是一个黑色四边形,而当我使用 TTF_RenderText_Shaded 时,我得到:

更新 3:

我在调用 GL 代码之前立即使用了SDL_SaveBMP,在调用SDL_RenderText_Blended 之后,结果是一个完全白色的图像,(给定文本颜色)。

当我使用TTF_RenderText_Solid 执行相同操作时,保存的图像是应有的,但由 opengl 呈现,就像您在上面看到的图像一样。

SDL_TTF 初始化很好,字体加载没有错误,文本渲染也没有返回错误,所以我想不出下一步该做什么。

更新 4:

我已经将所有 ttf 代码重构为一个函数并删除了填充代码(因为现代 opengl 似乎并不关心它)。然而,尽管所有项目设置和代码现在都与已知在相同硬件上工作的测试项目相同,但问题仍然存在。

GLuint textToTexture(const char * text, const char * font, glm::vec4 textColour, glm::vec4 bgColour, unsigned int & texID)
{
    if (!TTF_WasInit()) {
        if (TTF_Init() == -1)
            exit(6);
    }
    SDL_Color colour = { (Uint8)(textColour.r * 255), (Uint8)(textColour.g * 255), (Uint8)(textColour.b * 255),(Uint8)(textColour.a * 255) };
    SDL_Color bg = { (Uint8)(bgColour.r * 255), (Uint8)(bgColour.g * 255), (Uint8)(bgColour.b * 255),255 };
    TTF_Font* fontObj = TTF_OpenFont(font, 24);
    if (!fontObj)
    {
        SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR,
            "Texture Error",
            "Cannot load font to create texture.",
            NULL);
        return 0;
    }
    SDL_Surface *image = NULL;
    image = TTF_RenderText_Blended(fontObj, text, colour);
    if (image == NULL)
    {
        exit(5);
        //exitFatalError("String surface not created.");
        std::cout << "String surface not created." << std::endl;

    }
    unsigned char* pixels = NULL;
    GLuint w = image->w;
    GLuint h = image->h;
    GLuint colours = image->format->BytesPerPixel;
    GLuint externalFormat, internalFormat;
    SDL_PixelFormat *format = image->format;
    if (colours == 4) {

        if (image->format->Rmask == 0x000000ff)
            externalFormat = GL_RGBA;
        else
            externalFormat = GL_BGRA;
    }
    else {

        // no alpha
        if (image->format->Rmask == 0x000000ff)
            externalFormat = GL_RGB;
        else
            externalFormat = GL_BGR;
    }
    internalFormat = (colours == 4) ? GL_RGBA : GL_RGB;


    GLuint texId = 0;
    //GLuint texture;

    glGenTextures(1, &texID);

    glBindTexture(GL_TEXTURE_2D, texID);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, w, h, 0, externalFormat, GL_UNSIGNED_BYTE, image->pixels);
    //glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, trueW, trueH, 0, externalFormat, GL_UNSIGNED_BYTE, pixels);
    glGenerateMipmap(GL_TEXTURE_2D);

    //// SDL surface was used to generate the texture but is no longer
    //// required. Release it to free memory
    SDL_FreeSurface(image);
    TTF_CloseFont(fontObj);
    return texID;
}

我有一个解决方法,可以将图像保存为 bmp,然后重新加载并创建纹理,但仅限于我使用 TTF_RenderText_Shaded 时。如果我使用TTF_RenderText_Blended,我会得到一个与文本颜色相对应的单色图像。

【问题讨论】:

  • src + (srcW*i) - 你也需要乘以 bpp(同样适用于 dst)。此外,您永远不会释放像素缓冲区,但这与您当前的问题无关。
  • 好的,我修复了这个错误(顺便说一句,很好地发现了),但它没有改善生成的纹理,所以可能还有其他问题吗?
  • 从技术上讲,您需要对齐行,但在 bpp=4 时,它们已经对齐了。使用您所做的更改和获得的结果更新问题。了解原始表面的宽度和高度的实际值、使用的字体以及正在呈现的文本也可能会有所帮助。

标签: c++ opengl sdl-ttf


【解决方案1】:
glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, trueH, trueW, 0,format, GL_UNSIGNED_BYTE, pixels);

trueHtrueW 顺序颠倒了

memcpy(src + (srcW*i*bpp), dest + (width*i*bpp), srcW*bpp);

源和目标顺序颠倒了。

dest = (unsigned char*)calloc(0, width*height*bpp);

分配了 0 个大小为 width*height*bpp 的元素,即 0 个字节。应该是 1 而不是 0。

这是一个完整的例子:

#include <SDL2/SDL.h>
#include <GL/gl.h>
#include <SDL2/SDL_ttf.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

static unsigned char* padTexture(unsigned char * src, int srcW, int srcH, unsigned char * dest, int width, int height, int bpp, const SDL_Palette *palette)
{
    int dst_bpp = (bpp == 1) ? 4 : bpp;
    dest = (unsigned char*)calloc(1, width*height*dst_bpp);
    if(bpp != 1) {
        for (int i = 0; i < srcH; i++)
        {
            memcpy(dest + (width*i*bpp), src + (srcW*i*bpp), srcW*bpp);
        }
    } else {
        /* indexed - read colours from palette */
        for(int i = 0; i < srcH; i++) {
            for(int j = 0; j < srcW; j++) {
                memcpy(dest + (width*i+j)*dst_bpp,
                        &palette->colors[src[srcW*i+j]], sizeof(SDL_Color));
            }
        }
    }
    return dest;
}

static int powerofTwo(int num) {
    if (num != 0)
    {
        num--;
        num |= num >> 1;   // Divide by 2^k for consecutive doublings of k up to 32,
        num |= num >> 2;   // and then or the results.
        num |= num >> 4;
        num |= num >> 8;
        num |= num >> 16;
        num++;
    }
    return num;
}

static GLuint textToTexture(const char *text, TTF_Font* font) {
    if (!TTF_WasInit()) {
        if (TTF_Init() == -1)
            exit(6);
    }
    SDL_Color colour = { 255, 255, 255, 255 };
    SDL_Color bg = { 0, 0, 0, 255 };

    SDL_Surface *stringImage = NULL;
//    stringImage = TTF_RenderText_Blended(font, text, colour);
    stringImage = TTF_RenderText_Shaded(font, text, colour, bg);

    if (stringImage == NULL) {
        exit(5);
    }

    GLuint trueH = powerofTwo(stringImage->h);
    GLuint trueW = powerofTwo(stringImage->w);
    unsigned char* pixels = NULL;
    GLuint w = stringImage->w;
    GLuint h = stringImage->h;
    GLuint colours = stringImage->format->BytesPerPixel;
    pixels = padTexture((unsigned char*)stringImage->pixels, w, h, pixels, trueW, trueH,
            colours, stringImage->format->palette);
    GLuint format, internalFormat;

    /* If indexed, want resulting image to be 32bit */
    if(colours == 1) {
        colours = 4;
    }

    if (colours == 4) {  

        if (stringImage->format->Rmask == 0x000000ff)
            format = GL_RGBA;
        else
            format = GL_BGRA;
    }
    else {      

        // no alpha
        if (stringImage->format->Rmask == 0x000000ff)
            format = GL_RGB;
        else
            format = GL_BGR;
    }
    internalFormat = (colours == 4) ? GL_RGBA : GL_RGB;


    GLuint texId = 0;
    //GLuint texture;

    glGenTextures(1, &texId);

    glBindTexture(GL_TEXTURE_2D, texId);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, trueW, trueH, 0,format, GL_UNSIGNED_BYTE, pixels);

    // SDL surface was used to generate the texture but is no longer
    // required. Release it to free memory
    SDL_FreeSurface(stringImage);
    free(pixels);
    return texId;
}

int main(int argc, char* argv[])
{
    SDL_Init(SDL_INIT_VIDEO);
    TTF_Init();

    SDL_Window *window = SDL_CreateWindow("SDL2 Example", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 600, 400, SDL_WINDOW_OPENGL);
    SDL_GLContext gl_ctx = SDL_GL_CreateContext(window);

    TTF_Font *font = TTF_OpenFont(".fonts/tahoma.ttf", 16);
    if(font) {
        printf("font loaded\n");
        textToTexture("Select Scenario", font);
        TTF_CloseFont(font);
    }

    int quit = 0;
    while(!quit) {
        SDL_Event ev;
        while(SDL_PollEvent(&ev)) {
            if(ev.type == SDL_QUIT || ev.type == SDL_KEYUP) {
                quit = 1;
            }
        }

        glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        glLoadIdentity();
        glEnable(GL_BLEND);
        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
        glEnable(GL_TEXTURE_2D);
        glColor3f(1.0f, 1.0f, 1.0f);
        glBegin(GL_QUADS);
            glTexCoord2f(0, 1);
            glVertex2f(-0.5, -0.5);
            glTexCoord2f(0, 0);
            glVertex2f(-0.5, 0.5);
            glTexCoord2f(1, 0);
            glVertex2f(0.5, 0.5);
            glTexCoord2f(1, 1);
            glVertex2f(0.5, -0.5);
        glEnd();

        glFlush();
        SDL_GL_SwapWindow(window);
    }

    SDL_GL_DeleteContext(gl_ctx);
    SDL_DestroyWindow(window);

    TTF_Quit();
    SDL_Quit();

    return 0;
}

【讨论】:

  • 很好看。更正了这些错误,但没有明显的改进,所以我用新代码更新了问题。
  • 对我来说很好。那就做一个完整的例子吧。
  • 我检查了存储在 GPU ram 中的数据,并且图像在那里也出现了乱码,所以我猜测问题出在 SDL_TTF 本身,或者将数据复制到 GPU 上。您使用的是哪个版本的 SDL_TTF?
  • 有趣。我构建了你的代码,它运行良好。我想还有其他事情发生......
  • @IanYoung 这些函数文档说它们创建了 8 位调色板表面(顺便说一句,这是正确的)。 Palette 是一个颜色表,每个像素在该表中指定为索引,因此每个像素为 1 字节,但图像中的颜色总数限制为 256;您不能直接复制索引,因为它本身不是颜色。我已经用一种可能的方式来处理调色板更新了代码示例;另一种(可能更简单)的方法是为目标大小创建新的表面并将源块插入其中。
猜你喜欢
  • 2012-02-05
  • 1970-01-01
  • 2014-10-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-30
相关资源
最近更新 更多