Sur cette page

Exemple de transformations

L'exemple « Transformations » montre comment les transformations influencent la manière dont QPainter affiche les primitives graphiques.

Application permettant d'afficher différentes transformations de texte personnalisables

L'application permet à l'utilisateur de manipuler le rendu d'une forme en modifiant la translation, la rotation et la mise à l'échelle du système de coordonnées d'QPainter.

L'exemple se compose de deux classes et d'une énumération globale :

  • La classe « RenderArea » contrôle le rendu d’une forme donnée.
  • La classe ` Window ` correspond à la fenêtre principale de l'application.
  • L'énumération ` Operation ` décrit les différentes opérations de transformation disponibles dans l'application.

Nous allons d'abord examiner rapidement l'énumération ` Operation `, puis nous passerons en revue la classe ` RenderArea ` pour voir comment une forme est affichée. Enfin, nous examinerons les fonctionnalités de l'application « Transformations » implémentées dans la classe ` Window `.

Opérations de transformation

En règle générale, l’ QPainter opère sur le système de coordonnées propre au périphérique associé, mais il prend également bien en charge les transformations de coordonnées.

Le système de coordonnées par défaut d’un périphérique de dessin a son origine au coin supérieur gauche. Les valeurs x augmentent vers la droite et les valeurs y vers le bas. Vous pouvez mettre à l'échelle le système de coordonnées d'un décalage donné à l'aide de la fonction ` QPainter::scale()`, le faire pivoter dans le sens des aiguilles d'une montre à l'aide de la fonction ` QPainter::rotate()` et le translater (c'est-à-dire ajouter un décalage donné aux points) à l'aide de la fonction ` QPainter::translate()`. Vous pouvez également tordre le système de coordonnées autour de l'origine (ce qu'on appelle un cisaillement) à l'aide de la fonction ` QPainter::shear()`.

Toutes les opérations de transformation s’appliquent à la matrice de transformation de QPainter, que vous pouvez récupérer à l’aide de la fonction QPainter::worldTransform(). Une matrice transforme un point du plan en un autre point. Pour plus d’informations sur la matrice de transformation, consultez la documentation relative au système de coordonnées et à la fonction QTransform.

enum Operation { NoTransformation, Translate, Rotate, Scale };

L'énumération globale Operation est déclarée dans le fichier renderarea.h et décrit les différentes opérations de transformation disponibles dans l'application Transformations.

Définition de la classe RenderArea

La classe ` RenderArea ` hérite de ` QWidget` et contrôle le rendu d’une forme donnée.

class RenderArea : public QWidget
{
    Q_OBJECT

public:
    RenderArea(QWidget *parent = nullptr);

    void setOperations(const QList<Operation> &operations);
    void setShape(const QPainterPath &shape);

    QSize minimumSizeHint() const override;
    QSize sizeHint() const override;

protected:
    void paintEvent(QPaintEvent *event) override;

Nous déclarons deux fonctions publiques, ` setOperations() ` et ` setShape()`, afin de pouvoir spécifier la forme du widget ` RenderArea ` et de transformer le système de coordonnées dans lequel la forme est rendue.

Nous réimplémentons les fonctions minimumSizeHint() et sizeHint() de QWidget afin de donner au widget RenderArea une taille raisonnable au sein de notre application, et nous réimplémentons le gestionnaire d’événement QWidget::paintEvent() pour dessiner la forme de la zone de rendu en appliquant les choix de transformation de l’utilisateur.

private:
    void drawCoordinates(QPainter &painter);
    void drawOutline(QPainter &painter);
    void drawShape(QPainter &painter);
    void transformPainter(QPainter &painter);

    QList<Operation> operations;
    QPainterPath shape;
    QRect xBoundingRect;
    QRect yBoundingRect;
};

Nous déclarons également plusieurs fonctions pratiques pour dessiner la forme, le contour du système de coordonnées et les coordonnées, ainsi que pour transformer le peintre en fonction des transformations choisies.

De plus, le widget ` RenderArea ` conserve une liste des opérations de transformation actuellement appliquées, une référence à sa forme, ainsi que quelques variables pratiques que nous utiliserons lors du rendu des coordonnées.

Implémentation de la classe `RenderArea`

Le widget ` RenderArea ` contrôle l’affichage d’une forme donnée, y compris les transformations du système de coordonnées, en réimplémentant le gestionnaire d’événement ` QWidget::paintEvent()`. Mais commençons par jeter un coup d’œil rapide au constructeur et aux fonctions qui permettent d’accéder au widget ` RenderArea ` :

RenderArea::RenderArea(QWidget *parent)
    : QWidget(parent)
{
    QFont newFont = font();
    newFont.setPixelSize(12);
    setFont(newFont);

    QFontMetrics fontMetrics(newFont);
    xBoundingRect = fontMetrics.boundingRect(tr("x"));
    yBoundingRect = fontMetrics.boundingRect(tr("y"));
}

Dans le constructeur, nous transmettons le paramètre parent à la classe de base et personnalisons la police que nous utiliserons pour afficher les coordonnées. La fonction QWidget::font() renvoie la police actuellement définie pour le widget. Tant qu’aucune police spécifique n’a été définie, ou après l’appel de QWidget::setFont(), il s’agit soit d’une police spécifique à la classe du widget, soit de la police du parent, soit (si ce widget est un widget de niveau supérieur) de la police par défaut de l’application.

Après nous être assurés que la taille de la police est de 12 points, nous extrayons les rectangles englobant les lettres de coordonnées « x » et « y » à l’aide de la classe ` QFontMetrics `.

QFontMetrics fournit des fonctions permettant d’accéder aux métriques individuelles de la police, à ses caractères, ainsi qu’aux chaînes de caractères affichées dans cette police. La fonction ` QFontMetrics::boundingRect()` renvoie le rectangle englobant le caractère donné, par rapport au point le plus à gauche de la ligne de base.

void RenderArea::setOperations(const QList<Operation> &operations)
{
    this->operations = operations;
    update();
}

void RenderArea::setShape(const QPainterPath &shape)
{
    this->shape = shape;
    update();
}

Dans les fonctions ` setShape() ` et ` setOperations() `, nous mettons à jour le widget ` RenderArea ` en stockant la ou les nouvelles valeurs, puis en appelant le slot ` QWidget::update()`, qui planifie un événement de dessin à traiter lorsque Qt revient à la boucle d’événements principale.

QSize RenderArea::minimumSizeHint() const
{
    return QSize(182, 182);
}

QSize RenderArea::sizeHint() const
{
    return QSize(232, 232);
}

Nous réimplémentons les fonctions ` minimumSizeHint()` et ` sizeHint()` de ` QWidget` afin de donner au widget ` RenderArea ` une taille raisonnable au sein de notre application. Les implémentations par défaut de ces fonctions renvoient une taille non valide s’il n’existe pas de mise en page pour ce widget, et renvoient respectivement la taille minimale ou la taille préférée de la mise en page dans le cas contraire.

void RenderArea::paintEvent(QPaintEvent *event)
{
    QPainter painter(this);
    painter.setRenderHint(QPainter::Antialiasing);
    painter.fillRect(event->rect(), QBrush(Qt::white));

    painter.translate(66, 66);

Le gestionnaire d’événements paintEvent() reçoit les événements de dessin du widget RenderArea. Un événement de dessin est une demande de rafraîchissement de tout ou partie du widget. Il peut survenir à la suite d’un appel à QWidget::repaint() ou QWidget::update(), ou parce que le widget était masqué et vient d’être démasqué, ou pour de nombreuses autres raisons.

Nous commençons par créer une instance de ` QPainter ` pour le widget ` RenderArea `. L’indicateur de rendu ` QPainter::Antialiasing ` précise que le moteur doit, si possible, appliquer un anticrénelage aux bords des primitives. Nous effaçons ensuite la zone à rafraîchir à l’aide de la fonction ` QPainter::fillRect()`.

Nous translatons également le système de coordonnées d’un décalage constant afin de garantir que la forme d’origine soit affichée avec une marge appropriée.

    {
        QPainterStateGuard guard(&painter);
        transformPainter(painter);
        drawShape(painter);
    }

Avant de commencer à afficher la forme, nous instancions un ` QPainterStateGuard ` pour enregistrer l’état actuel du « painter » (c’est-à-dire pousser l’état sur une pile), y compris le système de coordonnées actuel tant qu’il est dans la portée. La raison pour laquelle nous enregistrons l’état du peintre est que l’appel suivant à la fonction transformPainter() va transformer le système de coordonnées en fonction des opérations de transformation actuellement choisies, et nous avons besoin d’un moyen de revenir à l’état d’origine pour dessiner le contour.

Après avoir transformé le système de coordonnées, nous dessinons la forme de l’ RenderArea, puis nous restaurons l’état du peintre à l’aide de la fonction QPainter::restore() (c’est-à-dire en retirant l’état enregistré de la pile).

    drawOutline(painter);

Ensuite, nous dessinons le contour du carré.

    transformPainter(painter);
    drawCoordinates(painter);
}

Comme nous voulons que les coordonnées correspondent au système de coordonnées dans lequel la forme est rendue, nous devons effectuer un autre appel à la fonction ` transformPainter() `.

L’ordre des opérations de dessin est essentiel en ce qui concerne les pixels partagés. La raison pour laquelle nous ne dessinons pas les coordonnées lorsque le système de coordonnées est déjà transformé pour dessiner la forme, mais reportons plutôt leur dessin à la fin, est que nous voulons que les coordonnées apparaissent par-dessus la forme et son contour.

Il n’est pas nécessaire de sauvegarder l’état d’ QPainter cette fois-ci, puisque le tracé des coordonnées est la dernière opération de dessin.

void RenderArea::drawCoordinates(QPainter &painter)
{
    painter.setPen(Qt::red);

    painter.drawLine(0, 0, 50, 0);
    painter.drawLine(48, -2, 50, 0);
    painter.drawLine(48, 2, 50, 0);
    painter.drawText(60 - xBoundingRect.width() / 2,
                     0 + xBoundingRect.height() / 2, tr("x"));

    painter.drawLine(0, 0, 0, 50);
    painter.drawLine(-2, 48, 0, 50);
    painter.drawLine(2, 48, 0, 50);
    painter.drawText(0 - yBoundingRect.width() / 2,
                     60 + yBoundingRect.height() / 2, tr("y"));
}

void RenderArea::drawOutline(QPainter &painter)
{
    painter.setPen(Qt::darkGreen);
    painter.setPen(Qt::DashLine);
    painter.setBrush(Qt::NoBrush);
    painter.drawRect(0, 0, 100, 100);
}

void RenderArea::drawShape(QPainter &painter)
{
    painter.fillPath(shape, Qt::blue);
}

Les fonctions drawCoordinates(), drawOutline() et drawShape() sont des fonctions pratiques appelées depuis le gestionnaire d’événements paintEvent(). Pour plus d’informations sur les opérations de dessin de base de QPainter et sur l’affichage des primitives graphiques de base, consultez l’exemple « Dessin de base ».

void RenderArea::transformPainter(QPainter &painter)
{
    for (int i = 0; i < operations.size(); ++i) {
        switch (operations[i]) {
        case Translate:
            painter.translate(50, 50);
            break;
        case Scale:
            painter.scale(0.75, 0.75);
            break;
        case Rotate:
            painter.rotate(60);
            break;
        case NoTransformation:
        default:
            ;
        }
    }
}

La fonction de commodité ` transformPainter() ` est également appelée depuis le gestionnaire d’événements ` paintEvent() ` et transforme le système de coordonnées de l’ QPainter donné en fonction des choix de transformation de l’utilisateur.

Définition de la classe de fenêtre

La classe ` Window ` correspond à la fenêtre principale de l’application « Transformations ».

L’application affiche quatre widgets « RenderArea ». Le widget situé le plus à gauche affiche la forme dans le système de coordonnées par défaut de « QPainter », tandis que les autres affichent la forme avec la transformation choisie, en plus de toutes les transformations appliquées aux widgets « RenderArea » situés à leur gauche.

class Window : public QWidget
{
    Q_OBJECT

public:
    Window();

public slots:
    void operationChanged();
    void shapeSelected(int index);

Nous déclarons deux slots publics pour permettre à l’application de répondre aux interactions de l’utilisateur, en mettant à jour les widgets « RenderArea » affichés en fonction des choix de transformation de l’utilisateur.

Le slot ` operationChanged() ` met à jour chacun des widgets ` RenderArea ` en appliquant les opérations de transformation actuellement choisies, et est appelé chaque fois que l’utilisateur modifie les opérations sélectionnées. Le slot ` shapeSelected() ` met à jour les formes des widgets ` RenderArea ` chaque fois que l’utilisateur modifie la forme souhaitée.

private:
    void setupShapes();

    enum { NumTransformedAreas = 3 };
    RenderArea *originalRenderArea;
    RenderArea *transformedRenderAreas[NumTransformedAreas];
    QComboBox *shapeComboBox;
    QComboBox *operationComboBoxes[NumTransformedAreas];
    QList<QPainterPath> shapes;
};

Nous déclarons également une fonction privée d’aide, ` setupShapes()`, qui est utilisée lors de la construction du widget ` Window `, et nous déclarons des pointeurs vers les différents composants du widget. Nous choisissons de conserver les formes disponibles dans un ` QList ` de ` QPainterPath`. De plus, nous déclarons une énumération privée comptant le nombre de widgets ` RenderArea ` affichés, à l’exception du widget qui affiche la forme dans le système de coordonnées par défaut de ` QPainter`.

Implémentation de la classe Window

Dans le constructeur, nous créons et initialisons les composants de l'application :

Window::Window()
{
    originalRenderArea = new RenderArea;

    shapeComboBox = new QComboBox;
    shapeComboBox->addItem(tr("Clock"));
    shapeComboBox->addItem(tr("House"));
    shapeComboBox->addItem(tr("Text"));
    shapeComboBox->addItem(tr("Truck"));

    QGridLayout *layout = new QGridLayout;
    layout->addWidget(originalRenderArea, 0, 0);
    layout->addWidget(shapeComboBox, 1, 0);

Nous créons d’abord le widget RenderArea qui affichera la forme dans le système de coordonnées par défaut. Nous créons également la QComboBox associée qui permet à l’utilisateur de choisir parmi quatre formes différentes : une horloge, une maison, un texte et un camion. Les formes elles-mêmes sont créées à la fin du constructeur, à l’aide de la fonction de commodité setupShapes().

    for (int i = 0; i < NumTransformedAreas; ++i) {
        transformedRenderAreas[i] = new RenderArea;

        operationComboBoxes[i] = new QComboBox;
        operationComboBoxes[i]->addItem(tr("No transformation"));
        operationComboBoxes[i]->addItem(tr("Rotate by 60\xC2\xB0"));
        operationComboBoxes[i]->addItem(tr("Scale to 75%"));
        operationComboBoxes[i]->addItem(tr("Translate by (50, 50)"));

        connect(operationComboBoxes[i], &QComboBox::activated,
                this, &Window::operationChanged);

        layout->addWidget(transformedRenderAreas[i], 0, i + 1);
        layout->addWidget(operationComboBoxes[i], 1, i + 1);
    }

Nous créons ensuite les widgets ` RenderArea ` qui afficheront leurs formes à l’aide de transformations de coordonnées. Par défaut, l’opération appliquée est ` No Transformation`, c’est-à-dire que les formes sont affichées dans le système de coordonnées par défaut. Nous créons et initialisons les objets ` QComboBox` associés avec des éléments correspondant aux différentes opérations de transformation décrites par l’énumération globale ` Operation `.

Nous connectons également le signal ` activated()` des ` QComboBox` au slot ` operationChanged() ` afin de mettre à jour l’application chaque fois que l’utilisateur modifie les opérations de transformation sélectionnées.

    setLayout(layout);
    setupShapes();
    shapeSelected(0);

    setWindowTitle(tr("Transformations"));
}

Enfin, nous définissons la mise en page de la fenêtre de l’application à l’aide de la fonction ` QWidget::setLayout()`, construisons les formes disponibles à l’aide de la fonction de commodité privée ` setupShapes() `, et faisons en sorte que l’application affiche la forme « horloge » au démarrage à l’aide du slot public ` shapeSelected() ` avant de définir le titre de la fenêtre.

void Window::setupShapes()
{
    QPainterPath truck;
    QPainterPath clock;
    QPainterPath house;
    QPainterPath text;
    ...
    shapes.append(clock);
    shapes.append(house);
    shapes.append(text);
    shapes.append(truck);

    connect(shapeComboBox, &QComboBox::activated,
            this, &Window::shapeSelected);
}

La fonction ` setupShapes() ` est appelée depuis le constructeur et crée les objets ` QPainterPath ` représentant les formes utilisées dans l’application. Pour plus de détails sur la construction, consultez le fichier d’exemple ` painting/transformations/window.cpp `. Les formes sont stockées dans une liste ` QList`. La fonction ` QList::append()` insère la forme donnée à la fin de la liste.

Nous connectons également le signal activated() de l’ QComboBox associé au slot shapeSelected() afin de mettre à jour l’application lorsque l’utilisateur modifie la forme préférée.

void Window::operationChanged()
{
    static const Operation operationTable[] = {
        NoTransformation, Rotate, Scale, Translate
    };

    QList<Operation> operations;
    for (int i = 0; i < NumTransformedAreas; ++i) {
        int index = operationComboBoxes[i]->currentIndex();
        operations.append(operationTable[index]);
        transformedRenderAreas[i]->setOperations(operations);
    }
}

Le slot public operationChanged() est appelé chaque fois que l’utilisateur modifie les opérations sélectionnées.

Nous récupérons l’opération de transformation choisie pour chacun des widgets RenderArea transformés en interrogeant l’objet QComboBoxes associé. Les widgets RenderArea transformés sont censés afficher la forme avec la transformation spécifiée par sa liste déroulante associée , en plus de toutes les transformations appliquées aux widgets RenderArea situés à sa gauche. C’est pourquoi, pour chaque widget interrogé, nous ajoutons l’opération associée à une liste QList de transformations que nous appliquons au widget avant de passer au suivant.

void Window::shapeSelected(int index)
{
    QPainterPath shape = shapes[index];
    originalRenderArea->setShape(shape);
    for (int i = 0; i < NumTransformedAreas; ++i)
        transformedRenderAreas[i]->setShape(shape);
}

Le slot « shapeSelected() » est appelé chaque fois que l'utilisateur modifie la forme souhaitée, ce qui met à jour les widgets « RenderArea » à l'aide de leur fonction publique « setShape() ».

Résumé

L’exemple « Transformations » montre comment les transformations influencent la manière dont QPainter affiche les primitives graphiques. Normalement, QPainter opère sur le système de coordonnées propre à l’appareil, mais il prend également bien en charge les transformations de coordonnées. Avec l’application « Transformations », vous pouvez mettre à l’échelle, faire pivoter et translater le système de coordonnées de QPainter. L’ordre dans lequel ces transformations sont appliquées est essentiel pour le résultat.

Toutes les opérations de transformation s’appliquent à la matrice de transformation de QPainter. Pour plus d’informations sur la matrice de transformation, consultez la documentation relative au système de coordonnées et à l’ QTransform.

La documentation de référence de Qt fournit plusieurs exemples de dessin. Parmi ceux-ci figure l’exemple « Affine Transformations » qui illustre la capacité de Qt à effectuer des transformations lors des opérations de dessin. Cet exemple permet également à l’utilisateur d’expérimenter les différentes opérations de transformation.

Exemple de projet @ 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.