Aug 16, 2009

Draw Text with OpenGL and Cairo

The OpenGL just supports capability to draw primitive, not text. In order to draw text on screen we need support of other package such as Cairo or Pango. The following topic describe how to use Cairo with OpenGL.
1 First we need to create a Cairo surface for drawing (text)
2 Then we will load the surface into GL texture using glTextureXXX function
3 After all, we will apply texture mapping into our primitive (for example a rectangle for displaying text)


Create Cairo Context
inline cairo_t*
create_cairo_context (int width,
int height,
int channels,
cairo_surface_t** surf,
unsigned char** buffer)
{
cairo_t* cr;

/* create cairo-surface/context to act as OpenGL-texture source */
*buffer = (unsigned char*)calloc (channels * width * height, sizeof (unsigned char));
if (!*buffer)
{
printf ("create_cairo_context() - Couldn't allocate surface-buffer\n");
return NULL;
}

*surf = cairo_image_surface_create_for_data (*buffer,
CAIRO_FORMAT_ARGB32,
width,
height,
channels * width);
if (cairo_surface_status (*surf) != CAIRO_STATUS_SUCCESS)
{
free (*buffer);
printf ("create_cairo_context() - Couldn't create surface\n");
return NULL;
}

cr = cairo_create (*surf);
if (cairo_status (cr) != CAIRO_STATUS_SUCCESS)
{
free (*buffer);
printf ("create_cairo_context() - Couldn't create context\n");
return NULL;
}

return cr;
}

Draw text into the Cairo surface and then load it into GL Texture
inline int DrawText(int x, int y, int width, int height, char *string, COLOR &textColor, COLOR &background)
{
cairo_surface_t* surface = NULL;
cairo_t* cr;
unsigned char* surfData;
GLuint textureId;

/* create cairo-surface/context to act as OpenGL-texture source */
cr = create_cairo_context (256,
256,
4,
&surface,
&surfData);

/* clear background */
cairo_set_operator(cr, CAIRO_OPERATOR_OVER);
cairo_set_source_rgb(cr, background.red, background.green, background.blue);
cairo_paint (cr);

cairo_move_to(cr, 256/10, 256/2);
cairo_set_font_size(cr, 30);
cairo_select_font_face(cr, "sans", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
cairo_set_source_rgb(cr, textColor.red, textColor.green, textColor.blue);
cairo_show_text(cr, string);

glGenTextures(1, &textureId);
glBindTexture(GL_TEXTURE_2D, textureId);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexImage2D(GL_TEXTURE_2D,
0,
GL_RGBA,
256,
256,
0,
GL_RGBA,
GL_UNSIGNED_BYTE,
surfData);
TestEGLError("glTexImage2D");

free (surfData);
cairo_destroy (cr);

return textureId;

}

Texture Mapping
The texture mapping is so simple, we need to provide them the UV coordinate. The UV coordinate is range from 0 to 1 which 0 is the start point, and 1 is the end point. The following is an example usage


GLfloat textureCoord[] = {
f2vt(0.0f), f2vt(0.35f),
f2vt(1.0f), f2vt(0.35f),
f2vt(0.0f), f2vt(0.55f),
f2vt(1.0f), f2vt(0.55f)
};
COLOR clrText = {255,255,255};
glEnable(GL_TEXTURE_2D);
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
GLuint textureId = DrawText(clipRect[0], clipRect[1], clipRect[2], clipRect[3], data, clrText, this->m_clrBackground);

glVertexPointer(2, VERTTYPEENUM, 0, rect);
glTexCoordPointer(2, VERTTYPEENUM, 0, textureCoord);

glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

glDisableClientState(GL_VERTEX_ARRAY);
glDisableClientState(GL_TEXTURE_COORD_ARRAY);

glDeleteTextures(1, &textureId);

No comments: