Sur cette page

Exemple de peinture en 2D

L'exemple de dessin 2D montre comment les classes QPainter et QOpenGLWidget peuvent être utilisées conjointement pour afficher des graphiques 2D accélérés sur le matériel pris en charge.

Capture d'écran présentant une comparaison d'une image entre le mode natif et OpenGL

La classe ` QPainter ` sert à dessiner des primitives graphiques 2D sur des périphériques de dessin fournis par les sous-classes de ` QPaintDevice `, telles que ` QWidget ` et ` QImage`.

QOpenGLWidget étant une sous-classe de QWidget, il est possible de réimplémenter sa méthode paintEvent() et d’utiliser QPainter pour dessiner sur le périphérique, exactement comme vous le feriez avec un QWidget. La seule différence réside dans le fait que les opérations de dessin seront accélérées au niveau matériel si les pilotes OpenGL de votre système le prennent en charge.

Dans cet exemple, nous effectuons les mêmes opérations de dessin sur un ` QWidget ` et un ` QOpenGLWidget`. L’ QWidget est affiché avec l’anticrénelage activé, et l’ QOpenGLWidget utilisera également l’anticrénelage si les extensions requises sont prises en charge par le pilote OpenGL de votre système.

Présentation

Afin de pouvoir comparer les résultats du dessin sur une sous-classe de ` QOpenGLWidget ` avec le dessin natif dans une sous-classe de ` QWidget `, nous souhaitons afficher ces deux types de widgets côte à côte. Pour ce faire, nous dérivons des sous-classes de ` QWidget ` et de ` QOpenGLWidget`, en utilisant une classe distincte ` Helper ` pour effectuer les mêmes opérations de dessin pour chacune d’elles, puis nous les disposons dans un widget de niveau supérieur, lui-même fourni par la classe ` Window `.

Définition de la classe d’aide

Dans cet exemple, les opérations de dessin sont effectuées par une classe d’aide. Nous procédons ainsi car nous souhaitons que les mêmes opérations de dessin soient effectuées à la fois pour notre sous-classe ` QWidget ` et pour la sous-classe ` QOpenGLWidget `.

La classe ` Helper ` est très simple :

class Helper
{
public:
    Helper();

public:
    void paint(QPainter *painter, QPaintEvent *event, int elapsed);

private:
    QBrush background;
    QBrush circleBrush;
    QFont textFont;
    QPen circlePen;
    QPen textPen;
};

Hormis le constructeur, elle ne fournit qu’une fonction ` paint() ` permettant d’effectuer le dessin à l’aide d’un « peintre » fourni par l’une de nos sous-classes de widget.

Implémentation de la classe d’aide

Le constructeur de la classe configure les ressources dont elle a besoin pour dessiner du contenu sur un widget :

Helper::Helper()
{
    QLinearGradient gradient(QPointF(50, -20), QPointF(80, 20));
    gradient.setColorAt(0.0, Qt::white);
    gradient.setColorAt(1.0, QColor(0xa6, 0xce, 0x39));

    background = QBrush(QColor(64, 32, 64));
    circleBrush = QBrush(gradient);
    circlePen = QPen(Qt::black);
    circlePen.setWidth(1);
    textPen = QPen(Qt::white);
    textFont.setPixelSize(50);
}

Le dessin proprement dit est effectué dans la fonction ` paint() `. Celle-ci prend en paramètre un ` QPainter ` déjà configuré pour dessiner sur un périphérique de dessin (soit un ` QWidget `, soit un ` QOpenGLWidget`), un ` QPaintEvent ` fournissant des informations sur la région à dessiner, ainsi qu’une mesure du temps écoulé (en millisecondes) depuis la dernière mise à jour du périphérique de dessin.

void Helper::paint(QPainter *painter, QPaintEvent *event, int elapsed)
{
    painter->fillRect(event->rect(), background);
    painter->translate(100, 100);

Nous commençons le dessin en remplissant la région contenue dans l’événement de dessin avant de translater l’origine du système de coordonnées afin que le reste des opérations de dessin soit décalé vers le centre du périphérique de dessin.

Nous dessinons un motif en spirale composé de cercles, en utilisant le temps écoulé spécifié pour les animer de manière à ce qu’ils semblent se déplacer vers l’extérieur et autour de l’origine du système de coordonnées :

    painter->save();
    painter->setBrush(circleBrush);
    painter->setPen(circlePen);
    painter->rotate(elapsed * 0.030);

    qreal r = elapsed / 1000.0;
    int n = 30;
    for (int i = 0; i < n; ++i) {
        painter->rotate(30);
        qreal factor = (i + r) / n;
        qreal radius = 0 + 120.0 * factor;
        qreal circleRadius = 1 + factor * 20;
        painter->drawEllipse(QRectF(radius, -circleRadius,
                                    circleRadius * 2, circleRadius * 2));
    }
    painter->restore();

Comme le système de coordonnées subit de nombreuses rotations au cours de ce processus, nous appelons d’abord la méthode ` save()` pour enregistrer l’état de l’ QPainter, puis la méthode ` restore()` pour le restaurer par la suite.

    painter->setPen(textPen);
    painter->setFont(textFont);
    painter->drawText(QRect(-50, -50, 100, 100), Qt::AlignCenter, QStringLiteral("Qt"));
}

Nous dessinons du texte à l’origine pour parachever l’effet.

Définition de la classe de widget

La classe ` Widget ` fournit un widget personnalisé de base que nous utilisons pour afficher l’animation simple dessinée par la classe ` Helper `.

class Helper;

class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(Helper *helper, QWidget *parent);

public slots:
    void animate();

protected:
    void paintEvent(QPaintEvent *event) override;

private:
    Helper *helper;
    int elapsed;
};

Outre le constructeur, elle ne contient qu’une fonction ` paintEvent()`, qui nous permet de dessiner du contenu personnalisé, et un slot utilisé pour animer ce contenu. Une variable membre garde la trace de la classe ` Helper ` utilisée par le widget pour dessiner son contenu, tandis que l’autre enregistre le temps écoulé depuis sa dernière mise à jour.

Implémentation de la classe de widget

Le constructeur se contente d’initialiser les variables membres, en stockant l’objet ` Helper ` fourni et en appelant le constructeur de la classe de base, et impose une taille fixe au widget :

Widget::Widget(Helper *helper, QWidget *parent)
    : QWidget(parent), helper(helper)
{
    elapsed = 0;
    setFixedSize(200, 200);
}

Le slot ` animate() ` est appelé chaque fois qu’un minuteur, que nous définirons plus loin, arrive à expiration :

void Widget::animate()
{
    elapsed = (elapsed + qobject_cast<QTimer*>(sender())->interval()) % 1000;
    update();
}

Ici, nous déterminons l’intervalle écoulé depuis la dernière expiration du minuteur, et nous l’ajoutons à toute valeur existante avant de rafraîchir le widget. Comme l’animation utilisée dans la classe ` Helper ` s’exécute en boucle toutes les secondes, nous pouvons utiliser l’opérateur modulo pour garantir que la variable ` elapsed ` soit toujours inférieure à 1 000.

Comme c’est la classe ` Helper ` qui se charge de tout le dessin proprement dit, il nous suffit d’implémenter un événement de dessin qui configure une variable ` QPainter ` pour le widget et appelle la fonction ` paint() ` de la classe d’aide :

void Widget::paintEvent(QPaintEvent *event)
{
    QPainter painter;
    painter.begin(this);
    painter.setRenderHint(QPainter::Antialiasing);
    helper->paint(&painter, event, elapsed);
    painter.end();
}

Définition de la classe GLWidget

La définition de la classe ` GLWidget ` est fondamentalement identique à celle de la classe ` Widget `, à l'exception du fait qu'elle dérive de ` QOpenGLWidget`.

class Helper;

class GLWidget : public QOpenGLWidget
{
    Q_OBJECT

public:
    GLWidget(Helper *helper, QWidget *parent);

public slots:
    void animate();

protected:
    void paintEvent(QPaintEvent *event) override;

private:
    Helper *helper;
    int elapsed;
};

Là encore, les variables membres enregistrent l’ Helper utilisé pour dessiner le widget et le temps écoulé depuis la dernière mise à jour.

Implémentation de la classe GLWidget

Le constructeur diffère légèrement de celui de la classe ` Widget ` :

GLWidget::GLWidget(Helper *helper, QWidget *parent)
    : QOpenGLWidget(parent), helper(helper)
{
    elapsed = 0;
    setFixedSize(200, 200);
    setAutoFillBackground(false);
}

La variable membre ` elapsed ` est initialisée et l'objet ` Helper ` utilisé pour dessiner le widget est stocké.

Le slot ` animate() ` est exactement le même que celui fourni par la classe ` Widget ` :

void GLWidget::animate()
{
    elapsed = (elapsed + qobject_cast<QTimer*>(sender())->interval()) % 1000;
    update();
}

La méthode ` paintEvent() ` est pratiquement identique à celle de la classe ` Widget ` :

void GLWidget::paintEvent(QPaintEvent *event)
{
    QPainter painter;
    painter.begin(this);
    painter.setRenderHint(QPainter::Antialiasing);
    helper->paint(&painter, event, elapsed);
    painter.end();
}

Étant donné que l'anticrénelage sera activé s'il est disponible, il suffit de configurer un QPainter sur le widget et d'appeler la fonction paint() de l'assistant pour afficher le contenu du widget.

Définition de la classe Window

La classe ` Window ` présente une définition simple et minimaliste :

class Window : public QWidget
{
    Q_OBJECT

public:
    Window();

private:
    Helper helper;
};

Elle contient un seul objet ` Helper ` qui sera partagé entre tous les widgets.

Implémentation de la classe Window

Le constructeur effectue tout le travail : il crée un widget de chaque type et les insère, accompagnés de leurs libellés, dans une mise en page :

Window::Window()
{
    setWindowTitle(tr("2D Painting on Native and OpenGL Widgets"));

    Widget *native = new Widget(&helper, this);
    GLWidget *openGL = new GLWidget(&helper, this);
    QLabel *nativeLabel = new QLabel(tr("Native"));
    nativeLabel->setAlignment(Qt::AlignHCenter);
    QLabel *openGLLabel = new QLabel(tr("OpenGL"));
    openGLLabel->setAlignment(Qt::AlignHCenter);

    QGridLayout *layout = new QGridLayout;
    layout->addWidget(native, 0, 0);
    layout->addWidget(openGL, 0, 1);
    layout->addWidget(nativeLabel, 1, 0);
    layout->addWidget(openGLLabel, 1, 1);
    setLayout(layout);

    QTimer *timer = new QTimer(this);
    connect(timer, &QTimer::timeout, native, &Widget::animate);
    connect(timer, &QTimer::timeout, openGL, &GLWidget::animate);
    timer->start(50);
}

Un minuteur avec un délai d’expiration de 50 millisecondes est créé à des fins d’animation, puis connecté aux slots « animate() » des objets « Widget » et « GLWidget ». Une fois lancés, les widgets devraient être mis à jour à environ 20 images par seconde.

Exécution de l’exemple

L’exemple montre les mêmes opérations de dessin effectuées simultanément dans un Widget et un GLWidget. La qualité et la vitesse de rendu dans le GLWidget dépendent du niveau de prise en charge du multisampling et de l’accélération matérielle fournis par le pilote OpenGL de votre système. Si l’une ou l’autre de ces fonctionnalités n’est pas prise en charge, le pilote peut recourir à un moteur de rendu logiciel qui privilégiera la vitesse au détriment de la qualité.

Exemple de projet sur code.qt.io

© 2026 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.