Catégorie : Programmation     Tags :      

    Retrouvez cet article dans : Linux Magazine 81

    La version 4 de Qt a apporté de nombreuses améliorations dans le système de dessin utilisé par Qt, les plus notables se trouvant dans les possibilités de la classe QPainter. Voyons comment tout cela permet la réalisation d’interfaces attrayantes. La version de Qt utilisée ici est la dernière version stable disponible, la version 4.1.0.

     Translucidité

    L’une des premières améliorations visibles est un support désormais complet de la translucidité, autrement dit pouvoir dessiner avec une couleur semi-transparente. Voici un exemple :

    /img-articles/lm/81/art-4/fig-1.jpg

    Le dessin a été effectué de l’extérieur vers l’intérieur, les cercles avant les carrés. La paire centrale utilise des couleurs « pures » : le dessin se superpose donc complètement à l’existant. Mais remarquez la paire la plus extérieure : dessinée avec une transparence moyenne, les couleurs se composent avec le fond blanc pour donner des tons pastel. Voici le code pour la paire extérieure :

       30    pen.setColor(QColor(255, 0, 0, 127)) ;
       31    pen.setWidth(5) ;
       32    p.setPen(pen) ;
       33    p.setBrush(QColor(255, 255, 0, 127)) ;
       34    p.drawEllipse(10, 10, width()-20, height()-20) ;
       35    pen.setColor(QColor(0, 0, 255, 127)) ;
       36    p.setPen(pen) ;
       37    p.setBrush(QColor(0, 255, 0, 127)) ;
       38    p.drawRect(20, 20, width()-40, height()-40) ;

    a variable pen est de type QPen, la variable p de type QPainter. La couleur fixée ligne 30 prend une valeur alpha en plus des trois composantes rouge, vert et bleu. La valeur 127 est moyenne ; la transparence totale (donc une couleur invisible) correspond à la valeur 0, l’opacité complète à la valeur 255 – cette quatrième valeur peut donc être vue plus comme une valeur d’opacité que comme une valeur de transparence.
    La paire cercle/carré intermédiaire a été dessinée ensuite avec une opacité à 60 (donc très transparente), la paire centrale avec une opacité à 255 (donc pas transparente du tout).
    Si on regarde l’image de près, on remarque le crénelage classique (aliasing) lorsqu’on dessine autre chose que des lignes verticales et horizontales :

    /img-articles/lm/81/art-4/fig-2.jpg

    C’est évidemment fort disgracieux. Il est possible d’appliquer un filtre anti-crénelage (anti-aliasing) à QPainter au moyen de la méthode setRenderHint() :

       18    QPainter p(this) ;
       19    p.setRenderHint(QPainter::Antialiasing, true) ;

    Le paramètre true active l’anti-crénelage, le paramètre false le désactive. Voici le résultat, de loin et de près :

    /img-articles/lm/81/art-4/fig-3.jpg

    /img-articles/lm/81/art-4/fig-4.jpg

    Le résultat est sensiblement plus esthétique. Si ce n’est pas forcément critique sur un dessin aussi trivial, cette possibilité améliore considérablement la lisibilité des caractères.

    Dégradés

    Une autre nouveauté est la présence de quelques classes permettant de définir des dégradés, pouvant être utilisés comme remplissage. Actuellement, trois modèles de dégradés sont proposés :

    • Dégradé linéaire, le plus simple, par la classe QLinearGradient : idéal pour les formes rectangulaires.
    • Dégradé radial, par la classe QRadialGradient : la couleur change en cercles concentriques à partir d’une focale.
    • Dégradé conique, par la classe QConicalGradient : la couleur change par un balayage circulaire autour d’un centre.

    Toutes ces classes dérivent de QGradient. Les couleurs peuvent être définies par la méthode setColorAt(), qui prend en premier paramètre une position relative normalisée (entre 0.0 et 1.0) et en second paramètre une couleur (QColor).
    Plutôt que de vous faire un long discours, je vous propose de créer quelques images de chacun de ces gradients en utilisant les positionnements suivants (grad étant une variable de l’un des trois types sus-cités) :

    grad.setColorAt(0.0, Qt::yellow) ;
    grad.setColorAt(0.5, Qt::blue) ;
    grad.setColorAt(1.0, Qt::red) ;

    Nous allons donc avoir des dégradés du jaune vers le rouge en passant par le bleu.
    Voici les résultats :

    /img-articles/lm/81/art-4/fig-5.jpg

    /img-articles/lm/81/art-4/fig-6.jpg

    Ces dessins ont été obtenus en affectant le dégradé à la brosse (brush) de l’instance de QPainter, puis en dessinant simplement un rectangle. Voici par exemple le code (presque) complet pour le premier dégradé :

    QLinearGradient
       grad(0.0, 0.0, 0.0, 100.0) ;
    grad.setColorAt(0.0, Qt::yellow) ;
    grad.setColorAt(0.5, Qt::blue) ;
    grad.setColorAt(1.0, Qt::red) ;
    p.setBrush(QBrush(grad)) ;
    p.drawRect(10, 10, 100, 100) ;

    Les autres sont obtenus sur le même principe.
    Si le dégradé est plus petit que la zone à couvrir (par exemple dans le cas du dégradé linéaire incliné), par défaut l’espace restant est uniformément rempli de la couleur finale. Pour éviter cela, vous pouvez utiliser la méthode setSpread() pour définir comment le dégradé doit être diffusé. Trois valeurs sont possibles, illustrées par ces exemples :

    /img-articles/lm/81/art-4/fig-7.jpg

    L’origine des brosses

    Les paramètres de position et de dimension donnés aux dégradés sont relatifs à l’espace de dessin sur lequel opère l’instance de QPainter utilisée. Cela signifie que l’origine d’un dégradé n’est pas située à l’origine de la forme dessinée, ce qui a des conséquences. Voici par exemple le dessin de quatre disques en utilisant un unique dégradé :

    /img-articles/lm/81/art-4/fig-8.jpg

    Ce phénomène vient du fait que toutes les brosses (représentées par la classe QBrush) possèdent une origine commune relative à l’espace de dessin, qui se trouve être par défaut le coin supérieur gauche.
    Toute se passe alors comme si le dégradé remplissait tout l’espace de dessin, les formes dessinées (rectangles, ellipses, etc.) étant comme des « fenêtres » ouvrant sur cette couverture. Mais ce n’est peut-être pas ce que vous voulez...
    Deux solutions existent. La première consiste à modifier les paramètres du dégradé, pour le faire coïncider comme vous le souhaitez à la forme à dessiner.
    La seconde consiste à déplacer l’origine de dessin des motifs de remplissage à l’aide de la méthode setBrushOrigin() de QPainter. Par exemple, insérez la ligne suivante juste avant le dessin de la troisième ellipse :

     p.setBrushOrigin(100, 100) ;

    Ce qui donne le résultat :

    /img-articles/lm/81/art-4/fig-9.jpg

    Si votre dégradé ne s’applique pas là où vous l’attendiez, cherchez dans cette direction, c’est une source commune de bizarreries.
    Dernier mot concernant les dégradés, il est également possible de les utiliser pour dessiner du texte, avec ou sans transparence.
    Voici un petit exemple élémentaire :

    /img-articles/lm/81/art-4/fig-10.jpg

    et le code qui l’a produit :

    QLinearGradient grad(0.0, 0.0, 100.0, 25.0) ;
    grad.setColorAt(0.0, Qt::red) ;
    grad.setColorAt(1.0, Qt::blue) ;
    grad.setSpread(QGradient::ReflectSpread) ;
    QPen pen ;
    pen.setBrush(QBrush(grad)) ;
    p.setPen(pen) ;
    QFont fnt(“FreeSerif”, 48, QFont::Bold, true) ;
    p.setFont(fnt) ;
    p.setBrushOrigin(10, 50) ;
    p.drawText(10, 50, “LinuxMagazine”) ;
    QLinearGradient grad2(0.0, 0.0, 100.0, 25.0) ;
    grad2.setColorAt(0.0, QColor(200, 200, 0, 130)) ;
    grad2.setColorAt(1.0, QColor(0, 255, 255, 130)) ;
    grad2.setSpread(QGradient::ReflectSpread) ;
    pen.setBrush(QBrush(grad2)) ;
    p.setPen(pen) ;
    p.setBrushOrigin(25, 100) ;
    p.drawText(25, 65, “LinuxPratique”) ;

    Les possibilités liées aux textes ont globalement bien progressé. Nous les verrons plus en détail plus tard.

    Un peu d’OpenGL

    Une autre grande nouveauté est la possibilité d’utiliser QPainter pour dessiner dans une fenêtre OpenGL. Ce faisant, QPainter fait appel à l’accélération matérielle (si elle est présente) pour obtenir les meilleures performances possibles. Mais il y a une petite subtilité pour que cela fonctionne, malheureusement absente de la documentation de Qt (du moins dans la version disponible à ce jour, la version 4.1.0).
    Pour information, si vous ne le saviez pas déjà, un contexte OpenGL peut être ouvert dans un widget Qt au moyen de la classe QGLWidget – elle-même dérivée de QWidget. Cette classe est destinée à être dérivée, trois méthodes virtuelles devant être surdéfinies :

    • initializeGL(), pour mettre en place le contexte OpenGL.
    • resizeGL(), invoquée lorsque les dimensions du widget sont changées.
    • paintGL(), la méthode principale dans laquelle la scène est supposée être dessinée.

    Ce n’est qu’une présentation lapidaire, il existe bien d’autres fonctionnalités OpenGL offertes par Qt, mais ce n’est pas l’objet du présent article et cela suffit pour notre propos.
    Voici maintenant la « petite » subtilité lorsque vous voulez utiliser QPainter pour dessiner dans un QGLWidget, dénichée à force de recherches et d’interrogations de la liste de diffusion de Qt :

    • QPainter::begin(), qui doit être invoquée avant toute opération de dessin avec une instance de QPainter (notez qu’elle est invoquée par le constructeur si celui-ci reçoit en paramètre un objet sur lequel il peut dessiner, c’est-à-dire une classe dérivée de QPaintDevice, comme QWidget ou QPixmap), vide les buffers : aucune instruction OpenGL ne doit donc apparaître avant elle.
    • QPainter::end(), invoquée pour terminer les opérations de dessin (invoquée par le destructeur), effectue la permutation des tampons de dessin (buffer swap) : aucune instruction OpenGL ne doit donc apparaître après elle.

    De là, on déduit la séquence d’opérations qui fonctionne, au sein de la méthode paintGL() :

    • 1. Création et activation d’une instance de QPainter, par exemple avec QPainter p(this).
    • 2. Sauvegarde des attributs OpenGL, par exemple avec glPushAttrib(GL_ALL_ATTRIB_BITS).
    • 3. Empilement des matrices de vue et de projection (avec glPushMatrix()).
    • 4. Mise en place des matrices et des attributs propres à la scène 3D.
    • 5. Dessin de la scène 3D.
    • 6. Restauration des matrices précédemment empilées (avec glPopMatrix()).
    • 7. Restauration des attributs avec glPopAttrib().
    • 8. Dessin de la scène 2D avec l’instance p de QPainter créée à la première étape.

    Voici un petit exemple, dans lequel deux triangles en dégradé tournent sur eux-mêmes (c’est la partie OpenGL) autour d’un joli cœur bien gros (c’est la partie QPainter) :

    /img-articles/lm/81/art-4/fig-11.jpg

    Autant les fonctionnalités OpenGL facilitent grandement la réalisation d’affichages 3D, autant QPainter est certainement la meilleure solution pour tout ce qui concerne le dessin 2D. La possibilité d’associer les deux permet d’obtenir aisément des effets tout à fait intéressants.

    Sur les chemins...

    Si vous vous demandez comment le cœur de l’exemple précédent a été dessiné, il s’agit en fait d’une succession d’arcs de cercle.
    Ceux-ci peuvent être dessinés avec la méthode drawArc() de QPainter. Mais si vous utilisez simplement cette méthode, vous obtiendrez bien le contour du cœur, mais pas l’intérieur, ce qui donnerait un cœur bien vide.
    La solution consiste à utiliser une autre nouvelle classe, la classe QPainterPath.
    Celle-ci permet de définir un ensemble de chemins ou contours, constitués d’une succession d’éléments de base comme des arcs d’ellipse, des segments ou des courbes de Bézier.  Chaque chemin peut être fermé ou non. Les chemins clos définissent des contours, qu’il est alors possible de remplir en utilisant une brosse – par exemple avec un dégradé...
    Voici le code complet qui dessine le cœur de l’exemple précédent, à la fin de la méthode paintGL() surdéfinie :

       89    p.translate(width()/2, 150) ;
       90    p.scale(1.2, 1.1) ;
       91    QPen pen ;
       92    pen.setColor(Qt::yellow) ;
       93    pen.setWidth(3) ;
       94    pen.setJoinStyle(Qt::RoundJoin) ;
       95    pen.setCapStyle(Qt::RoundCap) ;
       96    p.setPen(pen) ;
       97    QRadialGradient grad(-40, 10, 40, -45, 5) ;
       98    grad.setColorAt(0.0, QColor(240, 240, 240)) ;
       99    grad.setColorAt(1.0, QColor(255, 100, 100)) ;
      100    p.setBrush(grad) ;
      101    QPainterPath path ;
      102    path.moveTo(0, 0) ;
      103    path.arcTo(-100,  -50, 100, 100,   0, 180) ;
      104    path.arcTo(-100, -100, 200, 200, 180,  60) ;
      105    path.arcTo(-200,   74, 200, 180,  60, -60) ;
      106    path.arcTo(   0,   74, 200, 180, 180, -60) ;
      107    path.arcTo(-100, -100, 200, 200, 300,  60) ;
      108    path.arcTo(   0,  -50, 100, 100,   0, 180) ;
      109    path.closeSubpath() ;
      110    p.drawPath(path) ;
      111    p.end() ;

    La variable p est l’instance de QPainter créé au début de la méthode paintGL(). Le contour du cœur est défini entre les lignes 103 et 109, la dernière instruction ayant pour objet d’assurer la fermeture.  Si tout est donné ici sous forme de nombres entiers, en réalité la méthode arcTo() (et la plupart des autres méthodes de QPainterPath) prend des paramètres en virgule flottante, ce qui permet d’obtenir une bonne précision.
    Du point de vue de l’efficacité, il serait préférable de stocker l’instance de QPainterPath quelque part plutôt que de la reconstruire à chaque fois que le widget doit être redessiné.

    Conclusion

    Vous pouvez constater que le sous-système de dessin de Qt a connu quelques améliorations remarquables. Il y aurait encore beaucoup à dire, mais cela nous entraînerait trop loin : découvrez les nouvelles possibilités par vous-même !
    La prochaine fois, nous aborderons le vaste sujet de la manipulation de textes enrichis et de documents structurés.

    Retrouvez cet article dans : Linux Magazine 81

    Posté par admin-web (fabrice) | Signature : Yves Bailly | Article paru dans

    Laissez une réponse

    Vous devez avoir ouvert une session pour écrire un commentaire.


    • Il y a actuellement

    • 465 articles/billets en ligne.