Introduction à Qt graphisme Gilles Bailly gilles.bailly@enst.fr
Eric Lecolinet Crédits
Objectifs Introduction Signaux et slots Les principales classes Qt Compiler une application Qt Graphisme avancé Création de vos propres widgets Programmation événementielle Notion avancée de graphisme avec Qt Quelques autres notions avancées Machine à états Animation Qt Designer Au delà de Qt
Créer ses propres widgets Dessin : Comment dessiner sur des composants graphiques et autres? Segment, rectangle, Événements : Comment l application répond à l utilisateur? Press, Move, Drag, Release
2 (Re-)dessiner
Dessiner Dessiner ligne, cercle, rectangle, text, Le widget est repeint : Lorsque une fenêtre passe au dessus Lorsque l on déplace le composant Lorsque l on le lui demande explicitement Repaint(), force le widget à être redessiné Update(), un évènement de dessin en file d attente Dans tous les cas, c est la méthode : void paintevent( QPaintEvent* e); qui est appelée
Dessiner Dessiner Segment, rectangle, Redéfinir la méthode paintevent(); //.hpp #include <QWidget> #include <QPaintEvent> class MyWidget : public QWiget{ public: MyWidget(); protected: void paintevent( QPaintEvent* e ); };
Dessiner Dessiner Segment, rectangle, Redéfinir la méthode paintevent(); //.cpp #include «MyWidget» MyWidget::MyWidget(){} void MyWidget::paintEvent( QPaintEvent* e){ // on dessine ici }
Dessiner Dessiner Segment, rectangle, Redéfinir la méthode paintevent(); //.cpp #include «MyWidget» #include <QPainter> MyWidget::MyWidget(){} void MyWidget::paintEvent( QPaintEvent* e){ QPainter painter( this ); painter.drawline( 50, 10, 100, 20); }
Quel est le modèle derrière? 10
Modèle "damaged / repaint" Evénements Boucle de gestion des événements void mycallback(...) }... update();... update(x, y, w, h);... update(region1); update(region2);... void paintevent(qpaintevent* e) { }... - update() signifie qu'une zone est endommagée - paintevent() effectue le réaffichage
Modèle "damaged / repaint" - update() signifie qu'une zone est endommagée - zone = tout le widget sauf si on précise - met les demandes dans une file d'attente - paintevent() effectue le réaffichage - n'est appelée qu'une seule fois, au retour dans la boucle de gestion des événements et après compactage des demandes
Modèle "damaged / repaint" Remarques - repaint() entraîne un réaffichage immédiat (contrairement à update()) - ne pas l'utiliser sauf cas particuliers (animation) - Java fonctionne de manière similaire mais les noms sont différents - update() Qt == repaint() Java - paintevent() Qt == paint() Java
Dessiner Dessiner Segment, rectangle, Redéfinir la méthode paintevent(); //.cpp #include «MyWidget» #include <QPainter> MyWidget::MyWidget(){} void MyWidget::paintEvent( QPaintEvent* e){ QPainter painter( this ); painter.drawline( 50, 10, 100, 20); }
Dessiner #include «MyWidget» #include <QPainter> MyWidget::MyWidget(){} void MyWidget::paintEvent( QPaintEvent* e){ QPainter painter( this ); painter.drawline( 50, 10, 100, 20); } QPainter QPaintEngine QPaintDevice QPainter : Outil de dessin QPaintEngine : Interface utilisée par QPainter pour dessiner sur un QPaintDevice QPaintDevice : Abstraction d un espace 2D dans lequel on dessine QWidget hérite de QPaintDevice
Dans quoi dessiner Tous ce qui hérite de QPaintDevice QWidget, QPrinter, QPixmap, QImage, QPicture,QGLPixelBuffer, QGLFrameBufferObject Rendu de «haut niveau» : SVG QSvgRenderer, QSvgwidget QPainter QPaintEngine QPaintDevice QPainter : Outil de dessin QPaintEngine : Interface utilisée par QPainter pour dessiner sur un QPaintDevice QPaintDevice : Abstraction d un espace 2D dans lequel on dessine
Dessiner QPainter : drawline(), drawellipse(), drawrect, drawpath(), etc fillrect(), fillpath() drawtext() drawpixmap(), drawimage() setpen(), setbrush() QPainter QPaintEngine QPaintDevice QPainter p(this); p.setpen(qt::red); p.setbrush(qt::black); p.drawrect(10,10,100,30);
Questions Où dessiner? Dans la méthode paintevent(qpaintevent *e); Comment demander le réaffichage d un widget update() repaint() Quelle est la différence entre update() et repaint()? update() indique qu une zone est à réafficher (mais l affichage n est pas instantané) repaint() réaffiche immédiatement (mais peut introduire de la latence) Comment dessiner dans paintevent(qpaintevent*) instancier un QPainter: QPainter(this)
Gestion des événements 3
Évenements Les événements de la souris liés au widget: Lorsqu on presse un boutons Lorsqu on relâche un boutons Lorsqu on déplace la souris Lorsqu on double click Remarque pas de signals /slots dans ce cas Méthodes de QWidget à réimplementer void mousepressevent( QMouseEvent* e); void mousereleaseevent( QMouseEvent* e); void mousemoveevent( QMouseEvent* e); void mousedoubleclickevent( QMouseEvent* e);
Évenements Redéfinir la méthode mousepressevent(); //.hpp #include <QWidget> #include <QMouseEvent> class MyWidget : public QWiget{ public: MyWidget(); protected: void mousepressevent( QMouseEvent* e ); };
Évenements Redéfinir la méthode mousepressevent(); //.cpp #include «MyWidget» MyWidget::MyWidget(){} void MyWidget:: mousepressevent( QMouseEvent* e){ // on traite le press ici }
Évenements Redéfinir la méthode mousepressevent(); //.cpp #include «MyWidget» MyWidget::MyWidget(){} void MyWidget:: mousepressevent( QMouseEvent* e){ if (e.button() == Qt::LeftButton){ } } update(); //demande réaffichage
Évenements Redéfinir la méthode mousepressevent(); //.cpp QMouseEvent : Permet de récupérer le bouton qui a déclenché l événement Permet de récupérer l état des autres boutons (cliqués ou non) Permet de récupérer l état des modifieurs claviers Permet de récupérer la position de la souris (globale et locale)
Évenements #include «MyWidget» void MyWidget:: mousepressevent( QMouseEvent* e){ } if (e.button() == Qt::LeftButton){ } update(); //demande réaffichage QMouseEvent permet de récupérer (selon versions) : - button( ) : bouton souris qui a déclenché l événement. ex: Qt::LeftButton - buttons( ) : état des autres boutons. ex: Qt::LeftButton Qt::MidButton - modifiers( ) : modificateurs clavier. ex: Qt::ControlModifier Qt:: ShiftModifier - pos( ) ou posf( ) : position locale (relative au widget) - globalpos( ), windowpos( ), screenpos( ) : position globale ou relative à ce référentiel - utile si on déplace le widget interactivement
Évenements #include «MyWidget» void MyWidget:: mousepressevent( QMouseEvent* e){ } if (e.button() == Qt::LeftButton){ } update(); //demande réaffichage Alternative pour récupérer la position - QCursor::pos( ) : position du curseur sur l'écran - QWidget::mapToGlobal( ), mapfromglobal( )... : conversion de coordonnées Note : "Ignorer" les événements - QEvent::ignore( ) : signifie que le receveur ne veut pas l'événement ex : dans méthode closeevent() de la MainWindow pour ne pas quitter l'appli
Évenements MouseMoveEvent(QMouseEvent*) - événement généré quand on déplace la souris avec le bouton enfoncé - si on veut les événements quand le bouton n est pas enfoncé: => setmousetracking(true); Java - 2 méthodes - mousedrag (enfoncé) vs. mousemove (relaché) Qt - 1 méthode : mousemove - mousetracking() == true (relaché) MouseTracking() == false (enfoncé) <- défault
Synthèse événements mousepressevent(qmouseevent* e) }...... update(); boucle de gestion des événements paintevent(qpaintevent* e) { QWidget::paintEvent(e); QPainter.painter(this);... } mousemoveevent(qmouseevent* e) }...... update(); mousemoveevent(qmouseevent* e) }...... update(); mousereleaseevent(qmouseevent* e) }...... update();
Dessin avancé (très rapidement) 4
QPainter Attributs - setpen( ) : lignes et contours - setbrush( ) : remplissage - setfont( ) : texte - settransform( ), etc. : transformations affines - setcliprect/path/region( ) : clipping (découpage) - setcompositionmode( ) : composition
QPainter Lignes et contours - drawpoint(), drawpoints() - drawline(), drawlines() - drawrect(), drawrects() - drawarc(), drawellipse() - drawpolygon(), drawpolyline(), etc... - drawpath() : chemin complexe Remplissage Divers - fillrect(), fillpath() - drawtext() - drawpixmap(), drawimage(), drawpicture()
QPainter Classes utiles - entiers: QPoint, QLine, QRect, QPolygon - flottants: QPointF, QLineF,... - chemin complexe: QPainterPath - zone d affichage: QRegion
Pinceau: QPen Attributs - style : type de ligne - width : épaisseur (0 = «cosmetique») - brush : attributs du pinceau (couleur...) - capstyle : terminaisons - joinstyle : jointures Qt::PenStyle Cap Style Join Style
Pinceau: QPen Exemple // dans méthode PaintEvent() QPen pen; // default pen pen.setstyle(qt::dashdotline); pen.setwidth(3); pen.setbrush(qt::green); pen.setcapstyle(qt::roundcap); pen.setjoinstyle(qt::roundjoin); QPainter painter(this); painter.setpen(pen); Qt::PenStyle Cap Style Join Style
Remplissage: QBrush Attributs Qt::BrushStyle - style - color - gradient - texture QBrush brush(... );... QPainter painter(this); painter.setbrush(brush);
Remplissage: QBrush Attributs - style - color QColor - gradient - texture Qt::GlobalColor - modèles RGB, HSV or CMYK - composante alpha (transparence) : alpha blending - couleurs prédéfinies: Qt::GlobalColor
Remplissage: gradients Type de gradients - lineaire, - radial - conique QLinearGradient gradient( QPointF(0, 0), QPointF(100, 100) ); gradient.setcolorat(0, Qt::white); gradient.setcolorat(1, Qt::blue); QBrush brush(gradient); Type de coordonnées - défini par QGradient::CoordinateMode QLinearGradient QConicalGradient QRadialGradient répétition: setspread()
Composition Modes de composition - opérateurs de Porter Duff: - définissent : F(source, destination) - défaut : SourceOver avec alpha blending dst <= asrc * src + (1-asrc) * adst * dst - limitations selon implémentation et Paint Device Méthode: QPainter::setCompositionMode( )
Découpage (clipping) Découpage - selon un rectangle, une région ou un path - QPainter::setClipping(), setcliprect(), setclipregion(), setclippath() QRegion - united(), intersected(), subtracted(), xored() QRegion r1(qrect(100, 100, 200, 80), QRegion::Ellipse); QRegion r2(qrect(100, 120, 90, 30)); QRegion r3 = r1.intersected(r2); QPainter painter(this); painter.setclipregion(r3); // r1: elliptic region // r2: rectangular region // r3: intersection...etc... // paint clipped graphics
Transformations affines Transformations - translate() - rotate() - scale() - shear() Démo - settransform() QPainter painter( this ); painter.setrenderhint( QPainter::Antialiasing ); painter.translate( width( )/2, height( )/2 ); painter.scale( side/200.0, side/200.0 ); painter.save( ); // empile l état courant painter.rotate( 30.0 * ((time.hour() + time.minute() / 60.0)) ); painter.drawconvexpolygon( hourhand, 3 ); painter.restore( ); // dépile
Antialiasing Anti-aliasing - éviter l effet d escalier - particulièrement utile pour les polices de caractères Subpixel rendering - exemples : ClearType, texte sous MacOSX MacOSX ClearType (Wikipedia)
Antialiasing Anti-aliasing sous Qt QPainter painter(this); painter.setrenderhint(qpainter::antialiasing); painter.setpen(qt::darkgreen); painter.drawline(2, 7, 6, 1); Rendering hints - hint = option de rendu effet non garanti dépend de l implémentation et du matériel - QPainter::setRenderingHints( )
Antialiasing et cordonnées Epaisseurs impaire - pixels dessinés à droite et en dessous Dessin anti-aliasé Note QRect::right() = left() + width() -1 QRect::bottom() = top() + height() -1 Mieux : QRectF (en flottant) - pixels répartis autour de la ligne idéale
Paths QPainterPath - figure composée d une suite arbitraire de lignes et courbes affichée par: QPainter::drawPath() peut aussi servir pour remplissage, profilage, découpage Méthodes - déplacements: moveto(), arcmoveto() - dessin: lineto(), arcto() - courbes de Bezier: quadto(), cubicto() - addrect(), addellipse(), addpolygon(), addpath()... - addtext() - translate(), union, addition, soustraction... - et d autres encore... Démo
Paths QPointF center, startpoint; QPainterPath mypath; mypath.moveto( center ); mypath.arcto( boundingrect, startangle, sweepangle ); QPainter painter( this ); painter.setbrush( mygradient ); painter.setpen( mypen ); painter.drawpath( mypath ); ------------------------------------------------------------------------------ QPointF baseline(x, y); QPainterPath mypath; mypath.addtext( baseline, myfont, "Qt" ); QPainter painter( this );... etc... painter.drawpath( mypath );
Paths QPainterPath path; path.addrect(20, 20, 60, 60); path.moveto(0, 0); path.cubicto(99, 0, 50, 50, 99, 99); path.cubicto(0, 99, 50, 50, 0, 0); QPainter painter(this); painter.fillrect(0, 0, 100, 100, Qt::white); painter.setpen(qpen(qcolor(79, 106, 25), 1, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin)); painter.setbrush(qcolor(122, 163, 39)); painter.drawpath(path); Qt::OddEvenFill (défaut) Qt::WindingFill
Surfaces d affichage
Images Types d images - QImage: optimisé pour E/S et accès/manipulation des pixels QPixmap, QBitmap : optimisés pour affichage l écran - QPicture: pour enregistrer et rejouer les commandes d un QPainter - dans tous les cas : on peut dessiner dedans avec un QPainter Entrées/sorties - load() / save() : depuis/vers un fichier, principaux formats supportés - loadfromdata() : depuis la mémoire
Accès aux pixels Format 32 bits : accès direct QImage image(3, 3, QImage::Format_RGB32); QRgb value; value = qrgb(122, 163, 39); // 0xff7aa327 image.setpixel(0, 1, value); image.setpixel(1, 0, value) Format 8 bits : indexé QImage image(3, 3, QImage::Format_Indexed8); QRgb value; value = qrgb(122, 163, 39); // 0xff7aa327 image.setcolor(0, value); value = qrgb(237, 187, 51); // 0xffedba31 image.setcolor(1, value); image.setpixel(0, 1, 0); image.setpixel(1, 0, 1);
QPicture Enregistrer et rejouer les commandes d un QPainter - Enregistrer : QPicture picture; QPainter painter; painter.begin( &picture ); painter.drawellipse( 10,20, 80,70 ); painter.end( ); picture.save( "drawing.pic" ); - Rejouer : // paint in picture // draw an ellipse // painting done // save picture QPicture picture; picture.load( "drawing.pic" ); // load picture QPainter painter; painter.begin( &myimage ); // paint in myimage painter.drawpicture( 0, 0, picture ); // draw the picture at (0,0) painter.end( ); // painting done
Autres surfaces d affichage SVG - QSvgWidget QSvgRenderer OpenGL - QGLWidget QGLPixelBuffer QGLFramebufferObject QSvgWidget Impression - QPrinter
5 Interagir avec des formes géométriques
Picking Picking avec QRect, QRectF - intersects() - contains() Picking avec QPainterPath - intersects(const QRectF & rectangle) - intersects(const QPainterPath & path) - contains(const QPointF & point) - contains(const QRectF & rectangle) - contains(const QPainterPath & path) Retourne l intersection - QPainterPath intersected(const QPainterPath & path)
Picking / Interaction Exemple - teste si la souris est dans le rectangle quand on appuie sur le bouton de la souris - met à jour la position du rectangle pour qu il soit centré QRect rect; // variable d instance de mon Widget de dessin void mousepressevent(qmouseevent* e) { if (rect.contains( e->pos() )) { rect.movecenter( e->pos() ); update( ); }... void paintevent(qpaintevent* e ) { QPainter painter( this );... // specifier attributs graphiques painter.drawrect( rect ); }
Performance de l'affichage 6
Performance de l affichage Problèmes classiques : - Flicking et tearing (scintillement et déchirement) - Lag (latence)
Flicking Flicking - scintillement de l affichage car l oeil perçoit les images intermédiaires - exemple : pour rafraichir cette page il faut : 1) tout «effacer» (repeindre le fond) 2) tout redessiner => scintillement si le fond du transparent est sombre alors que le fond de la fenêtre est blanc source: AnandTech
Double buffering Double buffering - solution au flicking : dessin dans le back buffer recopie dans le front buffer (le buffer vidéo qui contrôle ce qui est affiché sur l écran) - par défaut avec Qt4 source: AnandTech
Tearing Possible problème : Tearing - l image apparait en 2 (ou 3...) parties horizontales - problème : recopie du back buffer avant que le dessin soit complet mélange de plusieurs "frames" vidéo en particulier avec jeux vidéo et autres applications graphiquement demandantes source: AnandTech
Tearing Solution au Tearing - VSync (Vertical synchronization ) - rendu synchronisé avec l'affichage - inconvénient : ralentit l'affichage source: AnandTech
Triple buffering Triple buffering - évite le ralentissement dû à la VSync - un des 2 back buffers est toujours complet - le front buffer peut être mis à jour sans attendre source: AnandTech
Performance de l affichage Latence (lag) - effet : l affichage «ne suit pas» l interaction - raison : le rendu n'est pas assez rapide
Performance de l affichage Latence (lag) - effet : l affichage «ne suit pas «l interaction - raison : le rendu n'est pas assez rapide Solutions - afficher moins de choses : dans l espace dans le temps - afficher en mode XOR
Performance de l affichage Afficher moins de choses dans l'espace - Clipping : réduire la zone d'affichage méthodes rect() et region() de QPaintEvent - "Backing store" ou équivalent 1) copier ce qui ne change pas dans une image 2) afficher cette image dans la zone de dessin 3) afficher la partie qui change par dessus
Performance de l affichage Afficher moins de choses dans le temps - sauter les images intermédiaires : réafficher une fois sur deux ou selon l'heure - les timers peuvent être utiles (cf. QTimer) événements void mousemoveevent(qmouseevent* e) }... if (delay < MAX_DELAY) update(); boucle de gestion des événements void paintevent(qpaintevent* e) { } if (delay > MAX_DELAY) return;... Ne pas réafficher si le délai est trop long Suivant le cas (et le toolkit), test dans une de ces 2 fonctions
XOR Principe - affichage en mode XOR très efficace pour déplacements interactifs Afficher 2 fois pour effacer - 1er affichage: pixel = bg ^ fg - 2eme affichage: pixel = (bg ^ fg) ^ fg = bg Problèmes - couleurs aléatoires - plus disponible avec Qt4 Exemple Java Graphics : - setcolor(color c1) - setpaintmode() : dessine avec c1 - setxormode(color c2) : échange c1 et c2