Mircea Ispas
Mircea Ispas

Reputation: 20780

How to render text with QOpenGLWidget

In older version of Qt there was QGLWidget, with a nice function called renderText. Now I'm using QOpenGLWidget class and the functionality for rendering text is missing.

Is there a easy way to render text using QOpenGLWidget? I won't like to build the whole text rendering with OpenGL from scratch...

Upvotes: 16

Views: 18422

Answers (6)

user2124108
user2124108

Reputation: 1

I had this issue when I needed to draw axis and corresponding ticks. In Qt4, I can use the following code:

for (int i = _Ymin; i <= _Ymax; i = i + _yGrid)
{
  double yPos = (_Ymax-i)*_yscaleF*_yscalingF;
  double xPos = -_xmin*_xscaleF-_shiftX;
  if(yPos < (_Ymax-_Ymin)*_yscaleF-_shiftY+1)
  {
      glBegin(GL_LINES);
      glVertex2f(xPos-2, yPos);
      glVertex2f(xPos+2, yPos);
      glEnd();
     renderText (xPos - offset,yPos+5 , 0, QString::number(i/10.0));
  }
}
glScalef(_xscalingF,_yscalingF,0);

However, when I try to migrate my code to Qt6, there's no renderText anymore. After searching on the web and investigating the source code of renderText, finally I found the solution, one needs to save/load gl status:

glBegin(GL_LINES);
glVertex2f(xPos-4, yPos);
glVertex2f(xPos+4, yPos);
glEnd();
qt_save_gl_state();
renderText(xPos - offset,yPos+5 , QString::number(i/10.0));
qt_restore_gl_state();

Here are the def of qt_save_gl_state, renderText, qt_restore_gl_state:

static void qt_save_gl_state()
{
    glPushClientAttrib(GL_CLIENT_ALL_ATTRIB_BITS);
    glPushAttrib(GL_ALL_ATTRIB_BITS);
    glMatrixMode(GL_TEXTURE);
    glPushMatrix();
    glLoadIdentity();
    glMatrixMode(GL_PROJECTION);
    glPushMatrix();
    glMatrixMode(GL_MODELVIEW);
    glPushMatrix();

    glShadeModel(GL_FLAT);
    glDisable(GL_CULL_FACE);
    glDisable(GL_LIGHTING);
    glDisable(GL_STENCIL_TEST);
    glDisable(GL_DEPTH_TEST);
    glEnable(GL_BLEND);
    glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
}
static void qt_restore_gl_state()
{
    glMatrixMode(GL_TEXTURE);
    glPopMatrix();
    glMatrixMode(GL_PROJECTION);
    glPopMatrix();
    glMatrixMode(GL_MODELVIEW);
    glPopMatrix();
    glPopAttrib();
    glPopClientAttrib();
}
void GLWidgetnew::renderText(double x, double y, const QString text)
{
    GLdouble textPosX = x, textPosY = y;
    // Retrieve last OpenGL color to use as a font color
    GLdouble glColor[4];
    glGetDoublev(GL_CURRENT_COLOR, glColor);
    QColor fontColor = QColor(glColor[0]*255, glColor[1]*255, 
    glColor[2]*255, glColor[3]*255);
    // Render text
    QPainter painter(this);
    painter.translate(float(_shiftX),float(_shiftY)); //This is for my own mouse event (scaling) 
                                
    painter.setPen(fontColor);
    QFont f;
    f.setPixelSize(10);
    painter.setFont(f);
    painter.drawText(textPosX, textPosY, text);
    painter.end();
}

src code of the project can be found here QtSignalProcessing

Upvotes: 0

codeling
codeling

Reputation: 11369

For future reference for anybody coming here with a similar issue - I had a similar issue as described in Urs's answer: When showing text on the OpenGL widget with drawText, some letters, sometimes whole words, were missing. Yet the mentioned glPixelStorei call did not change anything.

What I noticed when reading the QOpenGLWidget painting documentation was the QOpenGLFunction related stuff - and indeed the affected widget in our case did not use QOpenGLFunction yet, but apparently called OpenGL functions directly - and this lead to the weird text display.

Deriving from QOpenGLFunction and calling initializeOpenGLFunctions in initializeGL seems to have fixed the problems.

Edit: No luck - My problem seems to be an intermittent one that is only appearing occasionally (it's not reproducible every time I run the program). I guess I'll look into Qt OpenGL context debugging next.

Upvotes: 0

Urs
Urs

Reputation: 1

QPainter::drawText on a QOpenGLWidget relies on GL_UNPACK_ALIGNMENT being set to 4, otherwise characters will look corrupt/scrambled.

If you have this problem in your application, make sure to call

glPixelStorei(GL_UNPACK_ALIGNMENT, 4);

before drawing the text. (see https://bugreports.qt.io/browse/QTBUG-65496 for more info)

Upvotes: 0

jaba
jaba

Reputation: 775

You can implement this functionality by yourself based on the old Qt source code.

In your OpenGL widget class inherited from QOpenGLWidget (in this example it's GLBox) you have to implement the following methods:

renderText:

void GLBox::renderText(D3DVECTOR &textPosWorld, QString text)
{
    int width = this->width();
    int height = this->height();

    GLdouble model[4][4], proj[4][4];
    GLint view[4];
    glGetDoublev(GL_MODELVIEW_MATRIX, &model[0][0]);
    glGetDoublev(GL_PROJECTION_MATRIX, &proj[0][0]);
    glGetIntegerv(GL_VIEWPORT, &view[0]);
    GLdouble textPosX = 0, textPosY = 0, textPosZ = 0;

    project(textPosWorld.x, textPosWorld.y, textPosWorld.z, 
                &model[0][0], &proj[0][0], &view[0],
                &textPosX, &textPosY, &textPosZ);

    textPosY = height - textPosY; // y is inverted

    QPainter painter(this);
    painter.setPen(Qt::yellow);
    painter.setFont(QFont("Helvetica", 8));
    painter.setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing);
    painter.drawText(textPosX, textPosY, text); // z = pointT4.z + distOverOp / 4
    painter.end();
}

project:

inline GLint GLBox::project(GLdouble objx, GLdouble objy, GLdouble objz,
    const GLdouble model[16], const GLdouble proj[16],
    const GLint viewport[4],
    GLdouble * winx, GLdouble * winy, GLdouble * winz)
{
    GLdouble in[4], out[4];

    in[0] = objx;
    in[1] = objy;
    in[2] = objz;
    in[3] = 1.0;
    transformPoint(out, model, in);
    transformPoint(in, proj, out);

    if (in[3] == 0.0)
        return GL_FALSE;

    in[0] /= in[3];
    in[1] /= in[3];
    in[2] /= in[3];

    *winx = viewport[0] + (1 + in[0]) * viewport[2] / 2;
    *winy = viewport[1] + (1 + in[1]) * viewport[3] / 2;

    *winz = (1 + in[2]) / 2;
    return GL_TRUE;
}

and finally transformPoint:

inline void GLBox::transformPoint(GLdouble out[4], const GLdouble m[16], const GLdouble in[4])
{
#define M(row,col)  m[col*4+row]
    out[0] =
        M(0, 0) * in[0] + M(0, 1) * in[1] + M(0, 2) * in[2] + M(0, 3) * in[3];
    out[1] =
        M(1, 0) * in[0] + M(1, 1) * in[1] + M(1, 2) * in[2] + M(1, 3) * in[3];
    out[2] =
        M(2, 0) * in[0] + M(2, 1) * in[1] + M(2, 2) * in[2] + M(2, 3) * in[3];
    out[3] =
        M(3, 0) * in[0] + M(3, 1) * in[1] + M(3, 2) * in[2] + M(3, 3) * in[3];
#undef M
}

If you need renderText() because you have to port your Qt4 application to Qt5 just make sure to change the signature of the function provided here to

void GLBox::renderText(double x, double y, double z, const QString & str, const QFont & font = QFont(), int listBase = 2000)

and you don't have to worry about this anymore.

Upvotes: 5

Nils Schimmelmann
Nils Schimmelmann

Reputation: 51

I ended up doing a solution similar to what @jaba wrote. I also noticed some graphical corruption unless I called painter.end() at the end of the method.

void MapCanvas::renderText(double x, double y, double z, const QString &str, const QFont & font = QFont()) {
    // Identify x and y locations to render text within widget
    int height = this->height();
    GLdouble textPosX = 0, textPosY = 0, textPosZ = 0;
    project(x, y, 0f, &textPosX, &textPosY, &textPosZ);
    textPosY = height - textPosY; // y is inverted

    // Retrieve last OpenGL color to use as a font color
    GLdouble glColor[4];
    glGetDoublev(GL_CURRENT_COLOR, glColor);
    QColor fontColor = QColor(glColor[0], glColor[1], glColor[2], glColor[3]);

    // Render text
    QPainter painter(this);
    painter.setPen(fontColor);
    painter.setFont(font);
    painter.drawText(textPosX, textPosY, text);
    painter.end();
}

Upvotes: 5

Bim
Bim

Reputation: 1068

As you seem to be wanting to draw 2D text, use QPainter::drawText(). See here for info about using QPainter on a QOpenGLWidget. For using antialiasing for text rendering on QOpenGLWidgets see here.
If you want to draw 2.5D text (2D text moving with the 3D scene) it is not "too hard" to roll your own classes. Use QFont and QFontMetricsF to build a texture for your font glyphs, build some quads for each glyph into a VBO and draw the proper quads for the glyphs in a string...

Upvotes: 3

Related Questions