Sur cette page

Exemple de transformation

L'exemple des transformations montre comment les transformations influencent la manière dont QPainter rend les primitives graphiques.

Application affichant 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 l'échelle du système de coordonnées de QPainter.

L'exemple se compose de deux classes et d'une liste globale :

  • La classe RenderArea contrôle le rendu d'une forme donnée.
  • La classe Window est 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 jeter un coup d'œil rapide à l'énumération Operation, puis nous passerons en revue la classe RenderArea pour voir comment une forme est rendue. Enfin, nous examinerons les fonctionnalités de l'application Transformations mises en œuvre dans la classe Window.

Opérations de transformation

Normalement, le site QPainter utilise le système de coordonnées du périphérique associé, mais il prend également en charge les transformations de coordonnées.

Le système de coordonnées par défaut d'un dispositif de peinture a son origine dans le coin supérieur gauche. Les valeurs x augmentent vers la droite et les valeurs y augmentent vers le bas. Vous pouvez mettre à l'échelle le système de coordonnées en fonction d'un décalage donné à l'aide de la fonction QPainter::scale(), vous pouvez le faire pivoter dans le sens des aiguilles d'une montre à l'aide de la fonction QPainter::rotate() et vous pouvez 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 (appelé cisaillement) à l'aide de la fonction QPainter::shear().

Toutes les opérations de transformation opèrent sur 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 sur le système de coordonnées et QTransform.

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

L'enum global Operation est déclaré 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(), pour pouvoir spécifier la forme du widget RenderArea et transformer le système de coordonnées dans lequel la forme est rendue.

Nous réimplémentons les fonctions minimumSizeHint() et sizeHint() de QWidget pour donner au widget RenderArea une taille raisonnable dans notre application, et nous réimplémentons le gestionnaire d'événements 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 de commodité pour dessiner la forme, le contour du système de coordonnées et les coordonnées, et pour transformer le peintre en fonction des transformations choisies.

En outre, le widget RenderArea conserve une liste des opérations de transformation actuellement appliquées, une référence à sa forme et quelques variables de commodité que nous utiliserons lors du rendu des coordonnées.

Mise en œuvre de la classe RenderArea

Le widget RenderArea contrôle le rendu d'une forme donnée, y compris les transformations du système de coordonnées, en réimplémentant le gestionnaire d'événements QWidget::paintEvent(). Mais tout d'abord, nous allons 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 nous personnalisons la police de caractères 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éciale n'a été définie, ou après l'appel de QWidget::setFont(), il s'agit soit d'une police spéciale pour 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 s'être assuré que la taille de la police est de 12 points, nous extrayons les rectangles entourant les lettres de coordonnées, 'x' et 'y', en utilisant la classe QFontMetrics.

QFontMetrics La classe fournit des fonctions permettant d'accéder aux métriques individuelles de la police, de ses caractères et des chaînes de caractères rendues dans la police. La fonction QFontMetrics::boundingRect() renvoie le rectangle de délimitation du 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 suivies d'un appel au slot QWidget::update() qui planifie un événement de peinture à traiter lorsque Qt retourne à 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 pour donner au widget RenderArea une taille raisonnable dans notre application. L'implémentation par défaut de ces fonctions renvoie une taille invalide s'il n'y a pas de disposition pour ce widget, et renvoie la taille minimale ou la taille préférée de la disposition, respectivement, 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 peinture du widget RenderArea. Un événement de peinture est une demande de repeindre tout ou partie du widget. Il peut se produire à la suite d'un événement QWidget::repaint() ou QWidget::update(), ou parce que le widget était masqué et a été découvert, ou pour de nombreuses autres raisons.

Nous commençons par créer un QPainter pour le widget RenderArea. L'indice de rendu QPainter::Antialiasing indique que le moteur doit antialiaser les bords des primitives si possible. Ensuite, nous effaçons la zone qui doit être repeinte à l'aide de la fonction QPainter::fillRect().

Nous traduisons également le système de coordonnées avec un décalage constant pour nous assurer que la forme originale est rendue avec une marge appropriée.

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

Avant de commencer le rendu de la forme, nous instancions un QPainterStateGuard pour sauvegarder l'état actuel du peintre (c.-à-d. pousser l'état sur une pile), y compris le système de coordonnées actuel lorsqu'il est dans le champ d'application. L'enregistrement de l'état du peintre se justifie par le fait que l'appel suivant à la fonction transformPainter() transformera 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 RenderArea, puis nous restaurons l'état du peintre à l'aide de la fonction QPainter::restore() (c'est-à-dire en sortant l'état sauvegardé de la pile).

    drawOutline(painter);

Nous dessinons ensuite 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 faire un autre appel à la fonction transformPainter().

L'ordre des opérations de peinture est essentiel en ce qui concerne les pixels partagés. La raison pour laquelle nous ne rendons pas les coordonnées lorsque le système de coordonnées est déjà transformé pour rendre la forme, mais que nous reportons leur rendu à la fin, est que nous voulons que les coordonnées apparaissent au-dessus de la forme et de son contour.

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

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 de commodité appelées à partir du gestionnaire d'événements paintEvent(). Pour plus d'informations sur les opérations de dessin de base de QPainter et sur la manière d'afficher les primitives graphiques de base, voir 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 à partir du gestionnaire d'événements paintEvent() et transforme le système de coordonnées de QPainter en fonction des choix de transformation de l'utilisateur.

Définition de la classe Window

La classe Window est la fenêtre principale de l'application Transformations.

L'application affiche quatre widgets RenderArea. Le widget le plus à gauche rend la forme dans le système de coordonnées par défaut de QPainter, les autres rendent la forme avec la transformation choisie en plus de toutes les transformations appliquées aux widgets RenderArea à leur gauche.

class Window : public QWidget
{
    Q_OBJECT

public:
    Window();

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

Nous déclarons deux emplacements publics pour permettre à l'application de répondre à l'interaction 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 préféré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 de commodité privée, 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 QPainterPaths. En outre, nous déclarons un enum privé comptant le nombre de widgets RenderArea affichés, à l'exception du widget qui rend la forme dans le système de coordonnées par défaut de QPainter.

Mise en œuvre de la classe de fenêtre

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);

Tout d'abord, nous créons le widget RenderArea qui rendra la forme dans le système de coordonnées par défaut. Nous créons également le site QComboBox 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 rendront leurs formes avec des transformations de coordonnées. Par défaut, l'opération appliquée est No Transformation, c'est-à-dire que les formes sont rendues dans le système de coordonnées par défaut. Nous créons et initialisons les QComboBoxes associés avec des éléments correspondant aux diverses opérations de transformation décrites par l'énumération globale Operation.

Nous connectons également le signal activated() des QComboBoxes à l'emplacement operationChanged() pour 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 disposition 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 de l'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 à partir du constructeur et crée les objets QPainterPath représentant les formes utilisées dans l'application. Pour plus de détails sur la construction, voir le fichier d'exemple painting/transformations/window.cpp. Les formes sont stockées dans un fichier QList. La fonction QList::append() insère la forme donnée à la fin de la liste.

Nous connectons également le signal activated() de QComboBox au slot shapeSelected() pour mettre à jour l'application lorsque l'utilisateur change de 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 le QComboBoxes associé. Les widgets RenderArea transformés sont censés rendre la forme avec la transformation spécifiée par sa combobox associée en plus de toutes les transformations appliquées aux widgets RenderArea situés à sa gauche. C'est pourquoi, pour chaque widget que nous interrogeons, nous ajoutons l'opération associée à une 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 préférée, en mettant à jour les widgets RenderArea à l'aide de leur fonction publique setShape().

Résumé

L'exemple des transformations montre comment les transformations influencent la manière dont QPainter rend les primitives graphiques. Normalement, QPainter fonctionne sur le système de coordonnées de l'appareil, mais il prend également bien en charge les transformations de coordonnées. L'application Transformations permet de mettre à l'échelle, de faire pivoter et de 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 opèrent sur la matrice de transformation de QPainter. Pour plus d'informations sur la matrice de transformation, consultez la documentation sur le système de coordonnées et QTransform.

La documentation de référence de Qt fournit plusieurs exemples de peinture. Parmi ceux-ci, l'exemple Affine Transformations montre la capacité de Qt à effectuer des transformations sur les opérations de peinture. L'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.