Dessin de lignes et de formes avec le Canvas Lazarus
Introduction aux techniques de dessin dans Lazarus
Dans le précédent tutoriel de cette série, nous avons exploré des techniques utilisant des masques pour créer des transitions plus attrayantes et variées qu'une simple superposition. Certaines transitions répètent un motif sur la zone d'affichage. Afin d'illustrer ce cas, nous allons construire une série de transitions qui dessineront des rectangles régulièrement espacés pour faire apparaître l'image de destination.
Transitions basées sur des bandes
Bandes horizontales
Dans un premier temps, il s'agit de produire des bandes horizontales. Le principe est de diviser la hauteur de l'image par le nombre de bandes et de commencer à dessiner l'image de destination depuis les points ainsi trouvés. La transition StripsDown nous servira de modèle. Pour nos essais, nous déclarerons une constante C_NumberOfStrips qui fournira le nombre de bandes à dessiner. Grâce à cette constante, nous pourrons calculer aisément l'ordonnée de départ de chacune des bandes. Comme la hauteur de l'image est quelconque, nous pourrions nous heurter à deux problèmes. Le premier proviendrait du fait que la hauteur ne serait pas exactement divisible par le nombre de bandes, si bien qu'il nous faudrait en tenir compte afin de bien recouvrir toute la surface de l'image. Pour éviter ce problème potentiel, nous prévoyons systématiquement une bande supplémentaire. Le code ressemble beaucoup à ce qui a déjà été étudié. La réciproque de cette transition sera appelée StripsUp.
Bandes verticales
La réalisation de bandes verticales est très proche de celle de bandes horizontales, si bien que nous allons en profiter pour introduire une autre façon que l'emploi d'une constante pour gérer le nombre de bandes. Nous allons donc créer une fonction nommée NumberOfStrips imbriquée dans le gestionnaire OnClick dont nous nous servons sans cesse. Les valeurs choisies ont été fixées empiriquement. Le nombre de bandes par défaut bénéficie d'une constante, car il s'agit d'une valeur susceptible d'être modifiée selon les goûts de chacun ! Il sera bien entendu avantageux de proposer par la suite une fonction plus générale qui prendra aussi en compte la hauteur de l'image : il suffira pour cela d'introduire un paramètre qui remplacera la donnée figée ClientWidth. Ce sont à présent les ordonnées qui sont constantes en recouvrant toute la hauteur de l'image. Nous pouvons de la même manière créer la transition StripsRight.
Tuto CSS | Les bases de l'animations + bonus
Entrelacement de bandes
Entrelacer des bandes repose sur le même principe que leur dessin horizontalement ou verticalement, à savoir l'agrandissement progressif de rectangles selon une seule dimension. La différence essentielle tient au fait qu'un rectangle suivant un autre croîtra à partir du bord opposé de l'image. Tout de suite apparaît un problème spécifique à ce type de transition : commencerons-nous par la bande qui se déplace vers la droite ou au contraire par celle qui se dirige vers la gauche ? Pour le dessin, nous utiliserons comme ci-avant une itération, ainsi pourrons-nous décider du dessin à partir de la parité du numéro de la bande. Le travail similaire à effectuer pour des bandes verticales ne devrait pas poser de problèmes. Bien sûr, il faudra nous souvenir d'utiliser la largeur de l'image pour le calcul automatique des bandes.
Combinaison de bandes horizontales et verticales
Une variante du travail précédent consisterait à travailler simultanément avec les bandes horizontales et verticales. Par exemple, nous pouvons décider de dessiner des rectangles qui se formeraient alternativement en recouvrant vers le bas puis vers le haut l'image d'origine. Ce schéma décrit une transition que nous pourrions baptiser RectDownUp puisqu'elle commence par un recouvrement en descendant. Il nous faudra prendre garde à la hauteur des rectangles tels qu'ils ont été définis afin de les recouvrir entièrement.
Dessin de formes géométriques complexes et courbes
Formes aux coins arrondis
Dans cette section, nous pouvons considérer que nous avons épuisé le filon des transitions à base de rectangles. Les fonctions comme Fills a rounded rectangle with antialiasing. elliptical radius of 'rx' and 'ry'. draw the corners. permettent de définir un rectangle aux coins arrondis grâce à deux rayons ellipsoïdaux et des options qui indiquent la forme de l'arrondi pour chacun des sommets. Ces méthodes présentent un intérêt restreint pour le sujet des transitions.
Introduction aux splines pour des courbes douces
Avec les méthodes employées jusqu'à présent pour dessiner, nous avons presque uniquement eu affaire à des polygones, donc à des angles vifs. Tout au plus avons-nous croisé des ellipses facilement transformables en cercles. Nous voici donc limités à des formes géométriques de base avec lesquelles nous pouvons faire beaucoup, mais souvent avec des calculs ardus dès qu'il s'agit d'obtenir des courbes douces. Ce sont les splines qui vont lever ces limitations en proposant des courbes adoucies très appréciables, par exemple lorsque nous chercherons à imiter un liquide. Selon Wikipédia, « en mathématiques appliquées et en analyse numérique, une spline est une fonction définie par morceaux par des polynômes ». L'article précise qu'elles sont très utilisées dans les problèmes d'interpolation et dans ceux liés au lissage de données expérimentales ou de statistiques. Le plus simple pour comprendre leur intérêt est de produire une transition avec les outils actuellement en notre possession et d'appliquer ensuite ce nouvel outil. Nous allons proposer une animation qui fera « couler » l'image de destination depuis le sommet de l'image d'origine.
Approximation de courbes avec des polygones
Dans un premier temps, nous nous contenterons d'utiliser les polygones pour une approximation de cet objectif. Le code est suffisamment commenté pour en comprendre les principales étapes. Une difficulté tient aux extrémités qui doivent apparemment à la fois rester immobiles et se déplacer vers le bas : en fait, il s'agit d'un polygone dont des sommets sont confondus au début de la transition. Une amélioration à apporter à ce code consisterait à faire disparaître la constante numérique C_Points utilisée pour le nombre de points de la courbe. Elle serait alors remplacée, soit par un calcul en fonction de la largeur de l'image, soit par un paramètre fourni par l'utilisateur. Les angles des coulures sont bien trop saillants par endroits ! La question qui se pose est alors : comment pourrions-nous lisser cette courbe pour en faire disparaître les angles vifs ?
Mise en œuvre des splines
Pour mettre en œuvre les splines, nous allons déclarer un tableau ouvert de TPointF que nous appellerons LSpline et grâce auquel nous calculerons la nouvelle courbe.
Tuto CSS | Les bases de l'animations + bonus
Nouvelles méthodes pour le dessin de courbes
Les nouvelles méthodes introduites méritent que nous nous y arrêtions un instant. Nous voyons qu'elle prend pour paramètres un tableau de points (de type TPointF avec des coordonnées flottantes) et un style de type TSplineStyle. Certains des styles ne permettent pas d'obtenir un recouvrement complet de l'image d'origine. ComputeOpenedSpline a pour pendant la méthode ComputeClosedSpline qui ferme la spline. La seconde méthode nouvelle est DrawPolyLineAntialias. Comme son nom l'indique, elle dessine avec anticrénelage une ligne continue composée d'un ou de plusieurs segments de ligne. ''Closed'' specifies if the end of the line is closed. Nous voyons que nous pouvons jouer sur les points, les couleurs, les textures, les jointures, la couleur de remplissage, l'épaisseur du trait (paramètre w) ou l'apparence de la fin de la ligne pour un éventuel raccord.
Limites des courbes et gestion de la couverture
Les transitions décrites jusqu'à présent reposaient la plupart du temps sur l'utilisation d'un ou de plusieurs polygones (triangles et rectangles pour l'essentiel). Reprenons par exemple la transition RightBottomExpand qui agrandit un rectangle en direction du point inférieur droit de l'image finale, recouvrant peu à peu l'image d'origine par celle de destination. La forme d'origine de la ligne multiple est reproduite au fur et à mesure de la transition. Elle est d'autant plus visible que sa couleur de dessin est le rouge. Nous verrons ainsi ce que signifie tel ou tel paramètre. L'intérêt de ces options paraît limité pour les transitions, mais il est utile de les connaître afin d'obtenir la courbe la plus proche possible de ce qui est attendu. En ce qui concerne notre travail, il faudra surtout prendre garde au fait que ces courbes, tout comme les ellipses déjà étudiées, ne sont pas totalement couvrantes si nous nous bornons à faire varier nos coordonnées entre 0 et la largeur ou la hauteur de l'image de résultat.
Problématiques spécifiques au dessin de lignes
Épaisseur des lignes et rendu sur le Canvas
Le sujet du dessin de lignes sur un TImage canvas soulève des questions quant à leur épaisseur. Par exemple, une ligne verticale de deux pixels peut apparaître comme une seule pixel. Il a été observé que même en définissant une épaisseur de trait (pen.width) à 2, la ligne résultante pouvait ne pas avoir l'épaisseur attendue, surtout si la coordonnée X est trop proche du bord ou inférieure à la moitié de l'épaisseur du trait, ce qui entraîne un rognage de la ligne.
Il est possible d'obtenir une ligne de deux pixels en utilisant un code tel que :
procedure TForm1.PaintBox1Paint(Sender: TObject);const x = 10;begin with paintbox1.canvas do begin pen.width := 2; line(x, 10, x, 100); end;end;
Cependant, il est important de noter que si la valeur de X est inférieure à la moitié de pen.width, la ligne sera rognée.
Solutions et contournements pour l'épaisseur des lignes
Le problème de la ligne de deux pixels verticaux devenant un pixel est un problème connu. Bien qu'il n'y ait pas toujours de solution universelle facile à trouver, l'utilisation de la propriété pen.width est la méthode standard pour contrôler l'épaisseur du trait. Des techniques alternatives comme le dessin de rectangles avec insideFrame ont été essayées, mais cela peut interférer avec d'autres éléments dessinés sur le canvas. Le résultat de StretchDraw sur le canvas peut également ne pas fonctionner comme prévu dans ces cas.
Concepts avancés et autres fonctionnalités du Canvas
Initialisation et dessin de tracés
Une fois les bases du dessin établies, les premières fonctions les plus abordables sont celles qui vont tracer des formes géométriques ou des chemins. Un tracé est d'abord initialisé par la méthode beginPath(). Le point de référence de début du tracé est désigné avec moveTo(x,y). Il s'agit en quelque sorte de décider à partir de quel emplacement le pinceau va être posé. Puis vient le tracé de la ligne elle-même avec la méthode lineTo(x,y) qui va ajouter un segment au chemin qui fut débuté par beginPath(). La forme n'apparaît qu'une fois appelée l'une des deux méthodes fill() pour remplir et stroke() pour le contour.
Modification des styles de dessin
Pour moduler les styles des couleurs de contour et de remplissage dont dépendent fill() et stroke(), il faut agir sur des propriétés du contexte de dessin qui sont fillStyle et strokeStyle. Les valeurs acceptées sont tous les codes couleurs reconnus par le navigateur, par exemple comme en CSS : par nom (red, black), par code hexadécimal (#f00, #000000), par code rgb, etc. On remarque qu'il s'agit ici bien de s'adresser à une propriété JavaScript et non pas de déclencher une fonction (il n'y a pas de parenthèses à la fin de la ligne, mais bien une affectation grâce au signe égal).
Gestion du redimensionnement et de la persistance du dessin
Il en est de même si la "Form" est redimensionnée (minimisée ou agrandie). L'objectif est d'éviter le "bouton" "Rafraîchir" qui redessine ce qui a disparu. Si des éléments comme des boutons sont sur la même zone de dessin (Canvas) que le dessin principal, à chaque modification d'aspect de la Form (passage des boutons à "Enables" ou déplacement des boutons ou de la Form...), la Form va se redessiner, et le Canvas n'est pas mémorisé, le dessin est perdu. Il est donc nécessaire de gérer la persistance du dessin, potentiellement en le redessinant dans l'événement OnPaint.
Exemple d'animation : déplacement de balle
Dans l'onglet Propriétés de l'inspecteur d'objet, choisir la propriété Color et la régler à noir (clBlack). Vous pouvez aussi changer la propriété Caption (qui indique Form1) en Pong. Nous allons ajouter manuellement dans cette procédure la couleur de fond noire. MiHauteur est une variable de type entier (integer). À chaque fois que l'on appelle la procédure DessineBalle, la balle est effacée (coloriée en noir), puis retracée un peu plus loin. L'ordinateur va désormais gérer le déplacement de la balle. La balle se déplace toute seule vers la droite. La balle arrive sur le mur avec un angle égal à Direction. Attention : begin est précédé de la déclaration des variables locales. Le test pour savoir s'il y a un vainqueur se fait dans la procédure NouveauService. Lorsque la balle circule sur le terrain, il apparaît un problème d'effacement de l'écran (effacement du score, du filet…). Le son est émis à chaque rebond sur les côtés ou sur la raquette, et lorsqu'un côté ou l'autre perd.

Ce tutoriel s'achève ainsi la longue série des transitions obtenues essentiellement à partir de masques et de translations, avec une pointe de splines pour une meilleure saveur ! Avant de nous lancer dans la construction d'un composant regroupant tout ce que vous avez étudié, vous aurez encore un détour à faire du côté des rotations et des homothéties.
tags: #lazarus #canvas #epaisseur #de #ligne
