Monday, December 16, 2019

sdl - Rendering multiline text with SDL_TTF


Can you use any function to render more than a line of text in a surface?


If you can't do this, what whould be the way to go? I am doing the following: Create a surface (not sure if should be a software or a hardware one) and then create as many surfaces as lines my text have, then blitting all the surfaces, and freeing all the surfaces. But this turns to be very slow. This is the code:


void Text::updateSurface() {
if (surface != NULL) {
SDL_FreeSurface(surface);
}


TTF_Font * font = TTF_OpenFont(fontPath.c_str(),size);
if (font == NULL) {
ERROR("Null font"< }

int maxWidth = 0;
int totalHeight = 0;

std::vector surfaces;
for (size_t i=0; i
SDL_Surface * partialSurface = TTF_RenderText_Solid(font, lines[i].c_str(), color);
if (partialSurface == NULL) {
ERROR("surface == NULL: "< }
surfaces.push_back(partialSurface);
totalHeight += partialSurface->h;
maxWidth = std::max(maxWidth, partialSurface->w);
}

TTF_CloseFont(font);


surface = SDL::allocateSurface(maxWidth,totalHeight);
if (backgroundColor != NULL) {
SDL_FillRect(surface, NULL, SDL_MapRGB(surface->format,backgroundColor->r, backgroundColor->g, backgroundColor->b));
}
totalHeight = 0;
for (size_t i = 0; i < surfaces.size(); i++) {
SDL_Rect dst = {0, totalHeight, 0, 0};
switch(aligment) {
case ALIGMENT_RIGHT:

dst.x = maxWidth - surfaces[i]->w;
break;
case ALIGMENT_CENTER:
dst.x = (maxWidth - surfaces[i]->w) / 2;
break;
case ALIGMENT_LEFT:
default:
break;
}
totalHeight += surfaces[i]->h;

SDL_BlitSurface(surfaces[i],NULL,surface,&dst);
SDL_FreeSurface(surfaces[i]);
}
}

This is the code for SDL::allocateSurface(...)


static SDL_Surface * allocateSurface(int width, int height, int bpp = -1, Uint32 flags = 0)

SDL_Surface * SDL::allocateSurface(int width, int height, int bpp, Uint32 flags) {
if (bpp == -1) {

bpp = SDL::bpp; //SDL_GetVideoInfo()->vfmt->BitsPerPixel;
}

if (flags == 0) {
flags = SDL_HWSURFACE;
}

Uint32 rmask, gmask, bmask, amask;

#if SDL_BYTEORDER == SDL_BIG_ENDIAN

rmask = 0xff000000;
gmask = 0x00ff0000;
bmask = 0x0000ff00;
amask = 0x000000ff;
#else
rmask = 0x000000ff;
gmask = 0x0000ff00;
bmask = 0x00ff0000;
amask = 0xff000000;
#endif


return SDL_CreateRGBSurface(flags, width, height, bpp, rmask, gmask, bmask, amask);
}

Answer



Don't render text directly with SDL_TTF. It's not efficient, at all. Use SDL_TTF to generate a glyph atlas, or use a tool like AngelCode BMFont. You can get glyph metrics (Width, Height, x advance, etc.) using TTF_LineSkip to find the vertical distance, and TTF_GlyphMetrics.


At this point, you can render text yourself. By putting the font glyphs into an atlas, and storing glyph metrics, you can easily render multiple lines, render each glyph in a different style or even font, do various effects, etc. By batching your glyph draws, you can be way more efficient than using SDL_TTF naively. There's no need to copy whole lines into temporary SDL buffers which are copied to your real output.


It's harder, but worth it. Easy toy render APIs are just that: toys.


No comments:

Post a Comment

Simple past, Present perfect Past perfect

Can you tell me which form of the following sentences is the correct one please? Imagine two friends discussing the gym... I was in a good s...