Programmation Qt

Dessiner en 2D

Objectif : Dessiner en 2D dans une application GUI/Qt.

Il existe deux approches pour dessiner en 2D dans Qt :

  • un modèle objet basé sur le framework Graphics View
  • un modèle fonctionnel basé sur QPainter

Le framework Graphics View

Le framework Graphics View se décompose en 3 parties essentielles :

  • La scène
  • La vue
  • Les éléments graphiques

L’architecture Graphics View offre une approche basée sur le pattern modèle-vue. Plusieurs vues peuvent observer une scène unique constituée d’éléments de différentes formes géométriques.

La classe QGraphicsScene

La classe QGraphicsScene fournit la scène pour l’architecture Graphics View. La scène a les responsabilités suivantes :

  • fournir une interface rapide pour gérer un grand nombre d’éléments,
  • propager les événements à chaque élément,
  • gérer les états des éléments (telles que la sélection et la gestion du focus)
  • et fournir des fonctionnalités de rendu non transformée (principalement pour l’impression).

La classe QGraphicsView

La classe QGraphicsView fournit la vue “widget” qui permet de visualiser le contenu d’une scène.

La classe QGraphicsItem

La classe QGraphicsItem est la classe de base pour les éléments graphiques dans une scène.

Elle fournit plusieurs éléments standard pour les formes typiques, telles que :

  • des rectangles (QGraphicsRectItem),
  • des ellipses (QGraphicsEllipseItem) et
  • des éléments de texte (QGraphicsTextItem).

Mais les fonctionnalités les plus puissantes seront disponibles lorsque on écrira un élément personnalisé. Entre autres choses, QGraphicsItem supporte les fonctionnalités suivantes : les événements souris et clavier, le glissez et déposez (drag and drop), le groupement d’éléments, la détection des collisions.

Exemple

La scène sert de conteneur pour les objets QGraphicsItem. La classe QGraphicsView fournit la vue “widget” qui permet de visualiser le contenu d’une scène.

Exemple simple :

#include <QApplication>
#include <QGraphicsScene>
#include <QGraphicsRectItem>
#include <QgraphicsView>

int main(int argc, char **argv) 
{
   QApplication app(argc, argv);
   
   QGraphicsScene scene;
 
   QGraphicsRectItem *rect = scene.addRect(QRectF(0, 0, 100, 100));

   QGraphicsView view(&scene);

   view.show();
   
   return app.exec();
}

QPainter

En collaboration avec les classes QPaintDevice et QPaintEngine, QPainter est la base du système de dessin de Qt :

  • QPainter est la classe utilisée pour effectuer les opérations de dessin.
  • QPaintDevice représente un dispositif qui peut être peint en utilisant un QPainter.
  • QPaintEngine fournit le moteur de rendu et l’interface (abstraite) que QPainter utilise pour dessiner sur différents types de dispositifs suivant la plate-forme utilisée.

La classe QPainter est la classe de base de dessin bas niveau sur les widgets et les autres dispositifs de dessins. QPainter fournit des fonctions hautement optimisées : il peut tout dessiner des lignes simples à des formes complexes. QPainter peut fonctionner sur n’importe quel objet qui hérite de la classe QPaintDevice.

L’utilisation courante de QPainter est à l’intérieur de la méthode paintEvent() d’un widget : construire, personnaliser (par exemple le pinceau), dessiner et détruire l’objet QPainter après le dessin.

Lorsqu’un widget doit être dessiné ou redessiné, la méthode paintEvent() est appelée. Pour se dessiner, la majorité des widgets de Qt utilise un QPainter lors de l’appel à cette fonction.

La classe QPainter permet de dessiner sur toutes les classes graphique de Qt : QCustomRasterPaintDevice, QGLFramebufferObject, QGLPixelBuffer, QImage, QPicture, QPixmap, QPrinter, QSvgGenerator et bien évidemment QWidget.

Le QPaintDevice est un espace à deux dimensions dans lequel on peut dessiner. L’origine du système de coordonnées par défaut d’un QPaintDevice est le coin haut gauche (0,0). Les coordonnées en x s’incrémentent vers la droite et les coordonnées en y vers le bas. L’unité par défaut est d’un pixel et d’un point (1/72 d’un pouce (inch)) pour les imprimantes.

Par défaut, il y a correspondance entre les coordonnées logiques du QPainter et coordonnées physiques du QPaintDevice.

Principe

Un widget est “repeint” (dessiné ou redessiné) :

  • Lorsque une fenêtre passe au dessus
  • Lorsque l’on déplace le composant
  • Lorsque l’on le lui demande explicitement en appelant :

    • repaint() entraîne un rafraichissement immédiat
    • update() met une demande de rafraîchissement en file d’attente

Dans tous les cas, c’est la méthode paintEvent() qui est appelée : void paintEvent(QPaintEvent* e);

Pour dessiner dans un widget, il faut donc redéfinir QWidget::paintEvent().

Exemple simple :

#include <QtGui>

class MyWidget : public QWidget
{
  public:
    MyWidget( QWidget *parent = 0 ) : QWidget( parent ) {}

    void paintEvent(QPaintEvent* e) 
    {
        QWidget::paintEvent(e); // effectue le comportement standard

        QPainter painter(this); // construire
         
        painter.setPen( QPen(Qt::red, 2) ); // personnaliser

        painter.drawRect( 25, 15, 120, 60 ); // dessiner

    } // détruire
};

int main(int argc, char* argv[])
{
    QApplication app(argc, argv);
    MyWidget myWidget;

    myWidget.show();
    
    return app.exec();
}

Remarque : Certains widgets sont basés sur QAbstractScrollArea, dont le but est de permettre l’affichage d’un widget plus grand que sa zone d’affichage, en ajoutant des barres de défilement, par exemple. Appliquer un painter directement sur celle-ci n’aura pas l’effet souhaité. Cette classe implémente la méthode viewport() qui permet d’accéder au widget qui est réellement affiché. Il faut donc appliquer le painter sur celui-ci. Les widgets concernés sont QAbstractItemView, QGraphicsView, QMdiArea, QPlainTextEdit, QScrollArea, QTextEdit, QTextBrowser, QColumnView, QHeaderView, QListView, QTableView, QTreeView, QHelpContentWidget, QTreeWidget, QTableWidget, QHelpIndexWidget, QListWidget, QUndoView.

void paintEvent(QPaintEvent* e) 
{
    if (this->viewport())
    {
        QPainter painter(this->viewport());  // construire
        painter.setPen( QPen(Qt::red, 2) ); // personnaliser
        painter.drawRect( 25, 15, 120, 60 ); // dessiner
    } // détruire
}

Dessiner avec QPainter

La classe QPainter fournit de nombreuses méthodes dessiner et personnaliser son dessin :

  • setPen() : lignes et contours
  • setBrush() : remplissage
  • setFont() : texte
  • setTransform(), etc. : transformations affines
  • setClipRect/Path/Region() : clipping (découpage)
  • setCompositionMode() : composition
  • Lignes et contours : drawPoint(), drawPoints(), drawLine(), drawLines(), drawRect(), drawRects(), drawArc(), drawEllipse(), drawPolygon(), drawPolyline(), etc … et drawPath() pour des chemins complexes
  • Remplissage : fillRect(), fillPath()
  • Transformations de coordonnées : translate(), rotate(), scale()
  • Texte et images : drawText(), drawPixmap(), drawImage(), drawPicture()

Conjointement à la classe QPainter, on utilise de nombreuses autres classes utiles :

  • Entiers : QPoint, QLine, QRect, QPolygon
  • Flottants : QPointF, QLineF, …
  • Chemin complexe : QPainterPath
  • Zone d’affichage : QRegion
  • Stylo (trait) : QPen
  • Pinceau (remplissage) : QBrush
  • Couleur : QColor

Exemple simple :

#include <QtGui>

class MyWidget : public QWidget
{
  public:
   MyWidget( QWidget *parent = 0 ) : QWidget( parent ) {}
   void paintEvent(QPaintEvent* e) 
   {
      QPainter painter(this);
      QPen pen;      
      QBrush brush;

      brush.setStyle(Qt::CrossPattern);
      brush.setColor(Qt::blue);      

      pen.setStyle(Qt::DashDotLine);
      pen.setWidth(3);
      pen.setBrush(Qt::green);
      pen.setCapStyle(Qt::RoundCap);
      pen.setJoinStyle(Qt::RoundJoin);

      painter.setPen(pen);
      painter.setBrush(brush);
      painter.drawLine( 25, 15, 145, 75 );
      painter.setPen( QPen(Qt::red, 2) );
      painter.drawRect( 25, 15, 120, 60 );
   }
};

int main(int argc, char* argv[])
{
    QApplication app(argc, argv);
    MyWidget myWidget;

    myWidget.show();
    
    return app.exec();
}

Remarque : On peut aussi utiliser begin() pour commencer à dessiner et end() pour terminer. Tous les paramètres du QPainter (setPen(), setBrush(), etc …) sont réinitialisés à leur valeur par défaut lorsque begin() est appelée.

QPainter painter;
QPen pen;
pen.setStyle(Qt::SolidLine);
pen.setCapStyle(Qt::RoundCap);
pen.setJoinStyle(Qt::RoundJoin);
   
painter.begin(this);
pen.setBrush(Qt::white);
pen.setWidth(2);
painter.setPen(pen);
painter.drawLine(0, 0, 0, h);
painter.drawLine(0, h, w, h);
painter.drawLine(w, h, w, 0);
painter.drawLine(w, 0, 0, 0);
painter.end();

Il est donc possible de dessiner :

  • Un (ou plusieurs) point(s) sur un plan donné :
QPointF p(1.0, 0.0);
QPainter(this);
painter.drawPoint(p)

Voir aussi : QPainter::drawPoints().

  • Une ligne :
QLineF line(10.0, 80.0, 90.0, 20.0);
QPainter(this);
painter.drawLine(line);
  • Des segments :
static const QPointF points[3] = {
    QPointF(10.0, 80.0),
    QPointF(20.0, 10.0),
    QPointF(80.0,30.0)
};
QPainter painter(this);
painter.drawPolyline(points, 3);
  • Un polygone :
static const QPointF points[4] = {
    QPointF(10.0, 80.0),
    QPointF(20.0, 10.0),
    QPointF(80.0, 30.0),
    QPointF(90.0, 70.0)};
QPainter painter(this);
painter.drawPolygon(points, 4);

Voir aussi : QPainter::drawConvexPolygon().

  • Un rectangle :
QRectF rectangle(10.0, 20.0, 80.0, 60.0);
QPainter painter(this);
painter.drawRect(rectangle);
  • Un rectangle (angles arrondis) :
QRectF rectangle(10.0, 20.0, 80.0, 60.0);
QPainter painter(this);
painter.drawRoundRect(rectangle);
  • Une ellipse :
QRectF rectangle(10.0, 20.0, 80.0, 60.0);
QPainter painter(this);
painter.drawEllipse(rectangle);
  • Un arc de cercle :
QRectF rectangle(10.0, 20.0, 80.0, 60.0);
int startAngle = 30 * 16;
int spanAngle = 120 * 16;
QPainter painter(this);
painter.drawArc(rectangle, startAngle, spanAngle);
  • Une forme circulaire :
QRectF rectangle(10.0, 20.0, 80.0, 60.0);
int startAngle = 30 * 16;
int spanAngle = 120 * 16;
QPainter painter(this);
painter.drawPie(rectangle, startAngle, spanAngle);
  • Une corde :
QRectF rectangle(10.0, 20.0, 80.0, 60.0);
int startAngle = 30 * 16;
int spanAngle = 120 * 16;
QPainter painter(this);
painter.drawChord(rect, startAngle, spanAngle);
  • Un chemin :
QPainterPath path;
path.moveTo(20, 80);
path.lineTo(20, 30);
path.cubicTo(80, 0, 50, 50, 80, 80);
QPainter painter(this);
painter.drawPath(path);
  • Une région ou une zone :

Voir QPainter::setClipRegion(), QPainter::setCLipPath()

  • Du texte :
QRect rect(0, 0, 170, 45);
QPainter painter(this);
painter.drawText(rect, Qt::AlignCenter, "Qt by\nTrolltech");

Les images

Qt fournit quatre classes de traitement des données d’image : QImage, QPixmap, les QBitmap et QPicture :

  • QImage fournit une représentation d’image indépendante du matériel qui permet un accès direct aux pixels. QImage est conçu et optimisé pour les E/S.
  • QPixmap est conçu et optimisé pour afficher les images sur l’écran.
  • QBitmap n’est qu’une classe de commodité qui hérite QPixmap.
  • QPicture est un dispositif permettant d’enregistrer des commandes d’un QPainter et de les “rejouer”.

Ces 4 classes héritent de QPaintDevice et on peut donc dessiner dedans avec un QPainter.

Elles possèdent aussi les méthodes load() et save() d’accès aux fichiers (dont les principaux formats sont supportés).

QRect rect(0, 0, 170, 45);
QPainter painter(this);
QPixmap pixmap;
pixmap.load("qt-logo.png");
painter.drawPixmap(45, 50, pixmap);

Documentations

Retour au sommaire