Viadeo Twitter Google Bookmarks ! Facebook Digg del.icio.us MySpace Yahoo MyWeb Blinklist Netvouz Reddit Simpy StumbleUpon Bookmarks Windows Live Favorites 
Logo Documentation Qt ·  Page d'accueil  ·  Toutes les classes  ·  Classes principales  ·  Annotées  ·  Classes groupées  ·  Modules  ·  Fonctions  · 

Overpainting Example

Files:

The Overpainting example shows how QPainter can be used to overpaint a scene rendered using OpenGL in a QGLWidget.

QGLWidget provides a widget with integrated OpenGL graphics support that enables 3D graphics to be displayed using normal OpenGL calls, yet also behaves like any other standard Qt widget with support for signals and slots, properties, and Qt's action system.

Usually, QGLWidget is subclassed to display a pure 3D scene; the developer reimplements initializeGL() to initialize any required resources, resizeGL() to set up the projection and viewport, and paintGL() to perform the OpenGL calls needed to render the scene. However, it is possible to subclass QGLWidget differently to allow 2D graphics, drawn using QPainter, to be painted over a scene rendered using OpenGL.

In this example, we demonstrate how this is done by reusing the code from the Hello GL example to provide a 3D scene, and painting over it with some translucent 2D graphics. Instead of examining each class in detail, we only cover the parts of the GLWidget class that enable overpainting, and provide more detailed discussion in the final section of this document.

GLWidget Class Definition

The GLWidget class is a subclass of QGLWidget, based on the one used in the Hello GL example. Rather than describe the class as a whole, we show the first few lines of the class and only discuss the changes we have made to the rest of it:

    class Bubble;
    class QPaintEvent;
    class QWidget;

    class GLWidget : public QGLWidget
    {
        Q_OBJECT

    public:
        GLWidget(QWidget *parent = 0);
        ~GLWidget();
        ...
    protected:
        void initializeGL();
        void paintEvent(QPaintEvent *event);
        void resizeGL(int width, int height);
        void mousePressEvent(QMouseEvent *event);
        void mouseMoveEvent(QMouseEvent *event);
        void showEvent(QShowEvent *event);

    private slots:
        void animate();

    private:
        GLuint makeObject();
        void createBubbles(int number);
        void formatInstructions(int width, int height);
        ...
        QList<Bubble*> bubbles;
        QTimer animationTimer;
    };

As usual, the widget uses initializeGL() to set up objects for our scene and perform other OpenGL initialization tasks. The resizeGL() function is used to ensure that the 3D graphics in the scene are transformed correctly to the 2D viewport displayed in the widget.

Instead of implementing paintGL() to handle updates to the widget, we implement a normal QWidget::paintEvent(). This allows us to mix OpenGL calls and QPainter operations in a controlled way.

In this example, we also implement QWidget::showEvent() to help with the initialization of the 2D graphics used.

The new private member functions and variables relate exclusively to the 2D graphics and animation. The animate() slot is called periodically by the animationTimer to update the widget; the createBubbles() function initializes the bubbles list with instances of a helper class used to draw the animation; the formatInstructions() function is responsible for a semi-transparent messages that is also overpainted onto the OpenGL scene.

GLWidget Class Implementation

Again, we only show the parts of the GLWidget implementation that are relevant to this example. In the constructor, we initialize a QTimer to control the animation:

    GLWidget::GLWidget(QWidget *parent)
        : QGLWidget(QGLFormat(QGL::SampleBuffers), parent)
    {
        QTime midnight(0, 0, 0);
        srand(midnight.secsTo(QTime::currentTime()));

        object = 0;
        xRot = 0;
        yRot = 0;
        zRot = 0;

        trolltechGreen = QColor::fromCmykF(0.40, 0.0, 1.0, 0.0);
        trolltechPurple = QColor::fromCmykF(0.39, 0.39, 0.0, 0.0);

        animationTimer.setSingleShot(false);
        connect(&animationTimer, SIGNAL(timeout()), this, SLOT(animate()));
        animationTimer.start(25);

        setAttribute(Qt::WA_NoSystemBackground);
        setMinimumSize(200, 200);
        setWindowTitle(tr("Overpainting a Scene"));
    }

For a small performance improvement, we set the widget's Qt::WA_NoSystemBackground attribute to instruct the underlying window system not to paint a background for the widget.

As in the Hello GL example, the destructor is responsible for freeing any OpenGL-related resources:

    GLWidget::~GLWidget()
    {
        makeCurrent();
        glDeleteLists(object, 1);
    }

The initializeGL() function is fairly minimal, only setting up the display list used in the scene. To cooperate fully with QPainter, we defer matrix stack operations and attribute initialization until the widget needs to be updated.

In this example, we implement paintEvent() rather than paintGL() to render our scene. When drawing on a QGLWidget, the paint engine used by QPainter performs certain operations that change the states of the OpenGL implementation's matrix and property stacks. Therefore, it is necessary to construct a QPainter for use on the widget before making any OpenGL calls.

    void GLWidget::paintEvent(QPaintEvent *event)
    {
        QPainter painter;
        painter.begin(this);
        painter.setRenderHint(QPainter::Antialiasing);

After these calls, the OpenGL implementation is in the correct state for drawing 2D graphics. However, we first need to render a 3D scene by setting up model and projection transformations and other attributes. We use OpenGL stack operations to preserve the original state, allowing us to recover it later:

        glPushAttrib(GL_ALL_ATTRIB_BITS);
        glMatrixMode(GL_PROJECTION);
        glPushMatrix();
        glMatrixMode(GL_MODELVIEW);
        glPushMatrix();

We define a color to use for the widget's background, and set up various attributes that define how the scene will be rendered.

        qglClearColor(trolltechPurple.dark());
        glShadeModel(GL_SMOOTH);
        glEnable(GL_DEPTH_TEST);
        glEnable(GL_CULL_FACE);
        glEnable(GL_LIGHTING);
        glEnable(GL_LIGHT0);
        static GLfloat lightPosition[4] = { 0.5, 5.0, 7.0, 1.0 };
        glLightfv(GL_LIGHT0, GL_POSITION, lightPosition);

        resizeGL(width(), height());

Although the resizeGL() implementation we provide is called automatically whenever the widget is resized, we also call it here to set up the projection used for the scene. This is unnecessary in OpenGL examples that implement the paintGL() function because the matrix stacks are usually unmodified between calls to resizeGL() and paintGL().

Since the widget's background is not drawn by the system or by Qt, we use an OpenGL call to paint it before positioning the object defined earlier in the scene:

        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        glLoadIdentity();
        glTranslated(0.0, 0.0, -10.0);
        glRotated(xRot / 16.0, 1.0, 0.0, 0.0);
        glRotated(yRot / 16.0, 0.0, 1.0, 0.0);
        glRotated(zRot / 16.0, 0.0, 0.0, 1.0);
        glCallList(object);

Once the list containing the object has been executed, the matrix and attribute stacks need to be restored before we can begin overpainting:

        glPopAttrib();
        glMatrixMode(GL_MODELVIEW);
        glPopMatrix();
        glMatrixMode(GL_PROJECTION);
        glPopMatrix();

With the original state set by QPainter restored, we simply overpaint the widget with 2D graphics; in this case, using a helper class to draw a number of translucent bubbles onto the widget, and calling QPainter::drawImage() to overlay some instructions:

        foreach (Bubble *bubble, bubbles) {
            if (bubble->rect().intersects(event->rect()))
                bubble->drawBubble(&painter);
        }

        painter.drawImage((width() - image.width())/2, 0, image);
        painter.end();
    }

When QPainter::end() is called, suitable OpenGL-specific calls are made to write the scene, and its additional contents, onto the widget.

The implementation of the resizeGL() function sets up the dimensions of the viewport and defines a projection transformation:

    void GLWidget::resizeGL(int width, int height)
    {
        int side = qMin(width, height);
        glViewport((width - side) / 2, (height - side) / 2, side, side);

        glMatrixMode(GL_PROJECTION);
        glLoadIdentity();
        glOrtho(-0.5, +0.5, +0.5, -0.5, 4.0, 15.0);
        glMatrixMode(GL_MODELVIEW);

        formatInstructions(width, height);
    }

Additionally, we take the opportunity to format the instructions to fit the width of the viewport.

Ideally, we want to arrange the 2D graphics to suit the widget's dimensions. To achieve this, we implement the showEvent() handler, creating new graphic elements (bubbles) if necessary at appropriate positions in the widget.

    void GLWidget::showEvent(QShowEvent *event)
    {
        Q_UNUSED(event);
        createBubbles(20 - bubbles.count());
    }

This function only has an effect if less than 20 bubbles have already been created.

The animate() slot is called every time the widget's animationTimer emits the activated() signal. This keeps the bubbles moving around.

    void GLWidget::animate()
    {
        QMutableListIterator<Bubble*> iter(bubbles);

        while (iter.hasNext()) {
            Bubble *bubble = iter.next();
            update(bubble->rect().toRect());
            bubble->move(rect());
            update(bubble->rect().toRect());
        }
    }

We simply iterate over the bubbles in the bubbles list, updating the widget before and after each of them is moved.

The formatInstructions() function is used to prepare some basic instructions that will be painted with the other 2D graphics over the 3D scene.

    void GLWidget::formatInstructions(int width, int height)
    {
        QString text = tr("Click and drag with the left mouse button "
                          "to rotate the Qt logo.");
        QFontMetrics metrics = QFontMetrics(font());
        int border = qMax(4, metrics.leading());

        QRect rect = metrics.boundingRect(0, 0, width - 2*border, int(height*0.125),
            Qt::AlignCenter | Qt::TextWordWrap, text);
        image = QImage(width, rect.height() + 2*border, QImage::Format_ARGB32_Premultiplied);
        image.fill(qRgba(0, 0, 0, 127));

        QPainter painter;
        painter.begin(&image);
        painter.setRenderHint(QPainter::TextAntialiasing);
        painter.setPen(Qt::white);
        painter.drawText((width - rect.width())/2, border,
                         rect.width(), rect.height(),
                         Qt::AlignCenter | Qt::TextWordWrap, text);
        painter.end();
    }

The instructions are painted onto a suitably-sized semi-transparent image that can be rendered using QPainter in the widget's paintEvent() handler function.

Summary

When overpainting 2D content onto 3D content, we need to use a QPainter and make OpenGL calls to achieve the desired effect. Since QPainter itself uses OpenGL calls when used on a QGLWidget subclass, we need to preserve the state of various OpenGL stacks when we perform our own calls, using the following approach:

  • Reimplement QGLWidget::initializeGL(), but only perform minimal initialization. QPainter will perform its own initialization routines, modifying the matrix and property stacks, so it is better to defer certain initialization tasks until just before you render the 3D scene.
  • Reimplement QGLWidget::resizeGL() as in the pure 3D case.
  • Reimplement QWidget::paintEvent() to draw both 2D and 3D graphics.

The paintEvent() implementation must perform the following tasks:

  • Construct a QPainter object.
  • Initialize it for use on the widget with the QPainter::begin() function.
  • Push the current OpenGL projection and modelview matrices onto their respective stacks.
  • Push the current OpenGL attributes onto the attribute stack.
  • Perform initialization tasks usually done in the initializeGL() function.
  • Call the widget's resizeGL() function to set the correct perspective transformation and set up the viewport.
  • Render the scene using OpenGL calls.
  • Pop the OpenGL projection and modelview matrices off their respective stacks.
  • Pop the OpenGL attributes off the attribute stack.
  • Draw primitives using QPainter's member functions.
  • Call QPainter::end() to finish painting.

Publicité

Best Of

Actualités les plus lues

Semaine
Mois
Année
  1. « Quelque chose ne va vraiment pas avec les développeurs "modernes" », un développeur à "l'ancienne" critique la multiplication des bibliothèques 94
  2. Apercevoir la troisième dimension ou l'utilisation multithreadée d'OpenGL dans Qt, un article des Qt Quarterly traduit par Guillaume Belz 0
  3. Pourquoi les programmeurs sont-ils moins payés que les gestionnaires de programmes ? Manquent-ils de pouvoir de négociation ? 43
  4. Les développeurs ignorent-ils trop les failles découvertes dans leur code ? Prenez-vous en compte les remarques des autres ? 17
  5. Quelles nouveautés de C++11 Visual C++ doit-il rapidement intégrer ? Donnez-nous votre avis 10
  6. Qt Commercial : Digia organise un webinar gratuit le 27 mars sur la conception d'interfaces utilisateur et d'applications avec le framework 0
  7. 2017 : un quinquennat pour une nouvelle version du C++ ? Possible, selon Herb Sutter 9
Page suivante

Le Qt Developer Network au hasard

Logo

Les composants

Le Qt Developer Network est un réseau de développeurs Qt anglophone, où ils peuvent partager leur expérience sur le framework. Lire l'article.

Communauté

Ressources

Liens utiles

Contact

  • Vous souhaitez rejoindre la rédaction ou proposer un tutoriel, une traduction, une question... ? Postez dans le forum Contribuez ou contactez-nous par MP ou par email (voir en bas de page).

Qt dans le magazine

Cette page est une traduction d'une page de la documentation de Qt, écrite par Nokia Corporation and/or its subsidiary(-ies). Les éventuels problèmes résultant d'une mauvaise traduction ne sont pas imputables à Nokia. Qt 4.1
Copyright © 2012 Developpez LLC. Tous droits réservés Developpez LLC. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez LLC. Sinon, vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts. Cette page est déposée à la SACD.
Vous avez déniché une erreur ? Un bug ? Une redirection cassée ? Ou tout autre problème, quel qu'il soit ? Ou bien vous désirez participer à ce projet de traduction ? N'hésitez pas à nous contacter ou par MP !
 
 
 
 
Partenaires

Hébergement Web