Graphisme
Transcription
Graphisme
LE GRAPHISME Ce (début de) cours a été réalisé de manière totalement indépendante du CEPTA, centre de formation où d'ailleurs il n'y a pas de cours consacré graphisme proprement dit. Jean-Claude Armici __________________________________________________________________________________________ Le graphisme (JC Armici) -1www.unvrai.com 1. Introduction Toutes les applications qui interagissent directement avec l'utilisateur font appel d'une manière ou d'une autre à des techniques de programmation graphique. Qui développent des applications destinées à être utilisées par d'autres personnes se doit donc de connaître les bases de la programmation graphique. Bien que les techniques qui seront expliquées dans ce cours font appel à des concepts avancés, le néophyte en programmation graphique ne doit pas les ignorer. Delphi est un environnement de programmation permettant de développer des applications touchant à différents domaines. Par exemple, on trouve de plus en plus de jeux écrits en Delphi, soit sous forme de shareware, soit sous une forme professionnelle. Afin de familiariser le programmeur avec les concepts de base de la programmation graphique, nous allons d'abord examiner les concepts de base tels que pixel, ligne, polygone, bitmaps, ainsi que l'utilisation de l'objet canvas. 2. Le GDI Le système GDI (Graphical Device Interface) constitue la partie de Windows responsable de tout le système graphique. Ce système est accessible au travers de toute une variété de fonctions et de procédures. Que ce soit un bouton, une ligne, une barre de défilement, une application Windows elle-même, ou encore du texte, tout est géré par des fonctions et des procédures du GDI. Remarque: dans ce cours intervient souvent la notion de "device context" qui est normalement traduit en français par "contexte de dispositif". Cette traduction n'étant pas du plus bel effet, nous garderons l'appellation anglaise. Un des points remarquables de Windows est d'être "device indépendant", c'est-à-dire d'être capable de s'adapter à une gamme très étendue de combinaisons matérielles (carte graphiques, imprimantes, etc). Avec cet objectif d'indépendance vis-à-vis du matériel en vue, le rôle principal du GDI est de fournir des mécanismes au travers desquels une application peut créer un contenu graphique sans que le programmeur ne soit concerné par le type et donc les caractéristiques du matériel utilisé. Malheureusement une conséquence directe de cette indépendance est le manque relatif de performances au niveau de la vitesse d'exécution. Ce ralentissement est surtout visible lorsqu'il s'agit de programmer des jeux, pour lesquels une certaine fluidité est généralement requise. C'est une des raisons qui justifie l'existence de systèmes tels que DirectX. Ce cours restera néanmoins axé sur le système GDI qui, dans la plupart des tâches confiées à un programmeur "traditionnel", est tout à fait satisfaisant. __________________________________________________________________________________________ Le graphisme (JC Armici) -2www.unvrai.com 3. Eléments graphiques Avant de se lancer dans la programmation graphique il convient de définir un certain nombre de termes et de concepts (la programmation graphique a aussi son jargon). 3.1 Pixel Lorsque le GDI affiche une image à l'écran, il affiche, en fait, une série de points colorés. Un tel point s'appelle un pixel: il s'agit du plus petit élément graphique pouvant être affiché à l'écran. L'affichage d'une série de points constituant une image est tellement rapide qu'il donne l'impression d'une opération instantanée. Pixel: le plus petit élément constituant une image graphique ou une surface d'affichage L'écran d'un ordinateur peut être vu comme une grille constituée de pixels. La taille de cette grille et la dimension des pixels détermine la résolution du mode graphique utilisé. Le nombre de couleurs avec lesquelles il est possible de colorer un pixel détermine la profondeur de couleur (voir plus loin). 3.2 Ligne La ligne est un élément graphique plus riche qu'un point. On la retrouve fréquemment dans une interface graphique. Par exemple, on peut utiliser des lignes de différentes couleurs pour simuler un effet de profondeur ou de relief. Ligne: un ensemble de points arrangés de façon contiguë La représentation des lignes sur une grille orthogonale pose le problème de l'interpolation, dans le cas où les lignes ne sont ni horizontales ni verticales. __________________________________________________________________________________________ Le graphisme (JC Armici) -3www.unvrai.com Sur la figure suivante on voit bien l'effet d'"escaliers" pour les lignes inclinées. 3.3 Polygones Un polygone est formé d'un certain nombre de lignes, mises bout à bout. Il est ainsi possible de dessiner des figures géométriques, régulières ou irrégulières. Polygone: un ensemble de lignes, placées bout à bout de manière à former une figure continue et fermée 3.4 Bitmaps Le graphisme sur ordinateur serait un peu terne avec uniquement des lignes et des figures géométriques. Notre cerveau a une extraordinaire capacité de reconnaissance de formes. C'est probablement pour cette raison que les nouvelles interfaces graphiques utilisent abondamment icônes et images de toutes sortes. Bitmap: un ensemble de pixels arrangés dans une configuration rectangulaire. Il constitue généralement une forme, une image ou une texture reconnaissable. Par essence, les bitmaps utilisent une grande quantité de mémoire; raison pour laquelle beaucoup de formats disposent d'algorithmes de compression. __________________________________________________________________________________________ Le graphisme (JC Armici) -4www.unvrai.com 3.5 Classification des éléments graphiques Lorsque l'on parle d'affichage graphique, on distingue deux catégories: • • Les graphiques en mode points (Raster Graphics) Les graphiques vectoriels (vector graphics) Graphiques en mode points Il s'agit d'images formées de pixels statiquement arrangés de manière à former une image ou un dessin. Les icônes ou les bitmaps en sont deux exemples. Graphiques en mode points: graphiques composés de pixels formant une image ou un dessin. En général les graphiques en mode points sont très dépendants de la résolution et supportent mal les redimensionnements. Les algorithmes de redimensionnement sont d'ailleurs très complexes et les résultats sont de qualité variable. Sujet de réflexion: Imaginez une image en mode points sur laquelle est représentée une ligne verticale d'un pixel de large. Si on veut rendre cette image deux fois plus petite, comment faut-il traiter la ligne ? Sera-t-elle encore visible une fois l'image réduite? Le format le plus connu dans cette catégorie est le fameux BMP de Windows. Graphiques vectoriels Les graphiques vectoriels sont des images décrites comme une série de points connectés entre eux dans une grille virtuelle en 2 ou 3 dimensions. Ce type de graphique inclut les lignes, polygones, cercles, courbes, etc. Graphiques vectoriels: graphiques composés par une série de points reliés dans un système de coordonnées virtuel à 2 ou 3 dimensions. Contrairement au graphiques en mode points, les graphiques vectoriels sont indépendants de la résolution et peuvent être redimensionnés sans perte de qualité. Un exemple de format graphique vectoriel est le format Metafile de Windows. C'est d'ailleurs pour ces raisons que les cliparts sont le plus souvent fournis sous forme vectorielle. Le contenu d'une image vectorielle n'est pas un ensemble de pixels, mais plutôt un ensemble de directives (instructions) permettant de retracer ou redessiner l'image. __________________________________________________________________________________________ Le graphisme (JC Armici) -5www.unvrai.com 4. Résolution et nombre de couleurs Dans le monde du graphisme, on parle souvent du mode vidéo: résolution et nombre de couleurs par pixels. Mode vidéo: état dans lequel se trouve le matériel d'affichage, en termes de résolution et nombre de couleurs affichables. Les deux paramètres, résolution et nombre de couleurs, influent sur la qualité perçue d'une image graphique. Plus ces deux paramètres sont élevés, plus l'image affichée est réaliste et détaillée. Mais d'un autre côté, plus la quantité de mémoire utilisée est grande, donc plus les opérations graphiques prennent du temps. Souvent on opte pour le choix d'une résolution moyenne et un grand nombre de couleurs, plutôt que pour une grande résolution et un nombre moyen de couleurs. C'est le cas de la plupart des jeux, du moins jusqu'à l'apparition d'accélérateurs et contrôleurs graphiques ultra-performants. 4.1 Résolution La résolution d'un mode graphique donné spécifie combien de pixels peuvent être affichés sur l'écran. Résolution: nombre de points pouvant être affichés. Par exemple une image de 50 x 50 pixels apparaîtra plus grande dans une résolution de 800 x 600 pixels que dans une résolution de 1024 x 768 pixels. La résolution supportée par un écran dépend des caractéristiques de l'écran (en termes de fréquences de balayage), mais aussi du contrôleur (carte) graphique installé dans l'ordinateur. Les résolutions de 320 x 200 d'il y a une quinzaine d'années ont laissé place à des résolutions de 800 x 600 (minimum vital), 1024 x 768 (probablement la plus standard), voire 1280 x 1024 (relativement courante) ou 1600 x 1200. Malheureusement le développement d'applications grand public oblige à prendre en compte également les basses résolutions, ce qui diminue la place disponible pour les éléments d'interface. On peut bien entendu effectuer des adaptations dynamiques et automatiques en y mettant le code, donc le temps, donc l'argent nécessaire. 4.2 Nombre de couleurs On parle également de profondeur de couleurs ou encore de nombre de bits par pixel. Plus il y a de bits disponibles pour représenter la couleur d'un pixel, plus le nombre de couleurs à disposition est grand. Nombre de couleurs: mesure du nombre de bits par pixels requis pour décrire la couleur d'un pixel. Le nombre de couleurs affecte bien plus que la résolution les techniques utilisées pour afficher des éléments graphiques. Entre autres, le nombre de couleurs détermine si le mode graphique sera palettisé ou non palettisé (voir plus loin). La tableau suivant indique le nombre de couleurs disponibles en fonction du nombre de bits par pixels: __________________________________________________________________________________________ Le graphisme (JC Armici) -6www.unvrai.com Bits par pixel Nombre de couleurs disponibles 4 16 couleurs 8 256 couleurs 16 65'535 couleurs (high color) 24 16'777'216 couleurs (true color) 32 16'777'216 couleurs plus 256 niveaux de transparence (true color) Les deux premiers modes sont de moins en moins utilisés, au profit des modes 16 ou 24 bits par pixel. 4.3 Mémoire vidéo Généralement les cartes graphiques supportent un grand nombre de modes vidéo, en termes de combinaison de résolution et de nombre de couleurs. La quantité de mémoire vidéo disponible constitue aussi un facteur limitatif. Par exemple un mode 800 x 600 en 8 bits par pixel requiert 480'000 octets. Un mode 1600 x 1200 en 32 bits par pixel demande 7'680'000 octets. 4.4 Composantes de couleur La couleur d'un pixel est exprimée de manière interne en trois composantes: rouge, vert et bleu. L'intensité de chaque couleur de base peut varier de 0 à 255. Il s'agit de la représentation RGB des couleurs. On dispose de 16'777'216 couleurs, ce qui est bien plus que le nombre de nuances que l'oeil humain peut distinguer. 4.5 Modes palettisés Les modes avec 4 et 8 bits par pixel sont les seuls modes palettisés. Nous nous attarderons uniquement sur le mode à 8 bits par pixel. Chaque pixel peut avoir des valeurs comprises entre 0 et 255. Chacune de ces valeurs doit être vue comme un index dans un tableau de 256 couleurs, matérialisant la palette. Chaque entrée (ou élément) du tableau contient 3 valeurs codées sur 8 bits (donc 3 octets) pour les 3 composantes R, G et B. Donc on dispose de 256 couleurs distinctes, affichables simultanément, prises parmi 16'777'216 couleurs possibles. 0 0 122 0 0 0 0 0 0 R 0 15 G 0 0 1 15 ... 120 145 112 121 127 65 122 233 45 B 0 32 2 48 54 __________________________________________________________________________________________ Le graphisme (JC Armici) -7www.unvrai.com Un premier inconvénient de ce mode d'affichage est que si plusieurs bitmaps sont affichés, ils doivent utiliser la même palette. De plus, un bitmap créé avec une palette donnée, mais affiché sur un dispositif ayant une palette différente aura une apparence imprévisible. Souvent l'environnement graphique (par exemple Windows) doit se réserver quelques entrées dans la palette pour ses propres éléments graphiques, ce qui réduit d'autant le nombre de couleurs à disposition. Il est possible de faire correspondre à un pixel d'un bitmap la couleur la plus proche de la palette courante, mais cette procédure est généralement lente; du moins trop lente pour un domaine comme les jeux. Toutefois, ce que l'on perd en limitation de palette, on le gagne par un traitement très rapide des images, car des opérations portant sur des registres à 32 bits permettent de traiter 4 pixels à la fois. On peut également changer de palette en fonction des scènes ou des images à afficher. Un avantage de ce mode graphique est la possibilité d'animer la palette, cycliquement ou ponctuellement, ce qui permet de modifier l'aspect de l'image en changeant uniquement les points d'entrée dans la palette. L'utilisation des palettes n'est pas une opération facile. De plus, rien n'est directement prévu dans Delphi concernant la gestion des palettes. Il faut donc faire appel aux fonctions de l'API de Windows. Enfin, signalons qu'en graphisme certains résultats peuvent dépendre du matériel (carte vidéo, écran, etc ) et du logiciel (essentiellement les drivers graphiques). 4.6 Modes non palettisés Les modes ayant 16, 24 ou 32 bits par pixel stockent directement en mémoire la valeur de la couleur d'un pixel. Généralement, dans le mode avec 16 bits par pixel, 5 bits sont attribués à chaque composante. Dans le mode avec 24 bits par pixel, 8 bits (1 octet) sont attribués à chaque composante. Le mode avec 32 bits par pixel a aussi 8 bits par composante, plus 8 bits décrivant la transparence. Les modes non palettisés ont l'avantage de pouvoir afficher n'importe quel nombre d'images bitmap simultanément, sans souci de gestion de palette. Mais, comme nous l'avons déjà vu, ils demandent une plus grande quantité de mémoire. 5. Techniques d'affichage graphique Il existe des centaines de fonctions et procédures liées à l'affichage graphique. Dans un environnement comme Delphi, il est possible de travailler à plus ou moins bas niveau. On peut utiliser les primitives mises à disposition par le GDI ou bien faire appel aux routines de Delphi. 5.1 Device context Un device context représente une surface sur laquelle un graphique peut être affiché. Cette surface peut se trouver sur un écran, une imprimante, un traceur de courbes, etc. __________________________________________________________________________________________ Le graphisme (JC Armici) -8www.unvrai.com 6. Utilisation du graphisme avec Delphi 6.1 Introduction Dans la suite de ce cours nous allons aborder les différents aspects de la programmation graphique en Delphi au travers de nombreux exemples. Nous étudierons d'abord les opérations simples faisant appel à des objets courants. Nous nous attaquerons ensuite à des cas un peu plus complexes, tels que le traitement d'images. Delphi faisant partie de la grande famille des outils RAD, rappelons que les comportements par défaut n'ont pas besoin d'être définis et donnent des résultats satisfaisants. Ainsi, si l'on dessine un rectangle à l'aide de la procédure appropriée, il aura les caractéristiques suivantes: • • • • Couleur du trait: noir Epaisseur du trait: 1 pixel Style du trait: continu Couleur de l'intérieur du rectangle: blanc (non transparent) On peut se lancer dans la programmation graphique en Delphi et ignorer ce qu'elle cache au niveau de Windows. Ce serait vraiment dommage, car il est toujours intéressant et parfois très utile d'avoir une connaissance ne serait-ce que basique de l'architecture avec laquelle on s'apprête à travaille. Revenons sur le Device context. Bien que le GDI soit indépendant du matériel, les routines auxquelles on fait appel doivent connaître les caractéristiques du périphérique auquel elles s'attaquent. Ainsi l'instruction suivante: Rectangle (0, 0, 200, 400); dessine un rectangle de 200 x 400 unités. Mais on ignore quelles sont ces unités et à quel emplacement de l'écran va s'afficher le rectangle. Le GDI peut le savoir grâce au Device context, qui lui, connaît les particularités du périphérique. En réalité voici comment on devrait dessiner un rectangle en faisant appel aux fonctions de l'API de Windows: procedure TForm1.Button1Click(Sender: TObject); var DC : HDC; begin DC := GetDC (handle); Rectangle (DC, 20, 20, 120, 130); ReleaseDC (handle, DC); end; Ex1.exe et voici le résultat: __________________________________________________________________________________________ Le graphisme (JC Armici) -9www.unvrai.com Dans cet exemple, la fonction GetDC fournit un Device context pour la fenêtre qui en fait la demande au travers de son Handle (ou descripteur). Une fois cette information obtenue, le GDI sait qu'il va travailler sur l'écran et en connaît les caractéristiques. On peut donc dessiner le rectangle à l'aide de la fonction Rectangle du GDI, à laquelle on fournit précisément le Device context DC. Ce n'est pas la même fonction que celle de Delphi, qui possède uniquement 4 paramètres. Il est bien entendu impératif de libérer la ressource après son utilisation. Dans Delphi, l'objet Canvas encapsule toutes les caractéristiques d'un Device context de Windows et se charge, entre autres, d'obtenir et de libérer le Device context. Le second bouton de l'exemple ci-dessus dessine un rectangle à l'aide des instructions suivantes: procedure TForm1.Button2Click(Sender: TObject); begin canvas.Rectangle (140, 20, 240, 130); end; Le canvas utilisé ici est celui de la fenêtre principale du programme. Ceci est sousentendu, et on aurait pu écrire: Ex1.exe procedure TForm1.Button2Click(Sender: TObject); begin Form1.canvas.Rectangle (140, 20, 240, 130); end; Dans cet exemple le gain en complexité n'est pas spectaculaire. Mais on peut effectuer une seconde comparaison pour le dessin d'un rectangle rouge avec un intérieur jaune. Voici les instructions en version GDI: procedure TForm1.Button1Click(Sender: TObject); var DC : HDC; NewPen, OldPen : HPen; NewBrush, OldBrush : HBrush; begin DC := GetDC (handle); NewPen := CreatePen (PS_SOLID, 1, RGB (255,0,0)); OldPen := SelectObject (DC, NewPen); NewBrush := CreateSolidBrush (RGB (255,255,0)); OldBrush := SelectObject (DC, NewBrush); Rectangle (DC,20,20,120,130); SelectObject (DC, OldPen); SelectObject (DC, OldBrush); __________________________________________________________________________________________ Le graphisme (JC Armici) - 10 www.unvrai.com DeleteObject (NewPen); DeleteObject (NewBrush); ReleaseDC (handle, DC); end; Et en version canvas de Delphi: Ex2.exe procedure TForm1.Button2Click(Sender: TObject); begin canvas.Brush.color := clYellow; canvas.pen.color := clRed; canvas.rectangle (140,20,240,130); end; Plusieurs composants de Delphi disposent d'un canvas. Si nous voulions dessiner ce même rectangle à l'intérieur d'un composant Image1 de type Timage, il suffirait de préfixer la spécification du canvas par l'objet sur le canvas duquel on veut dessiner: procedure TForm1.Button2Click(Sender: TObject); begin Image1.canvas.Brush.color := clYellow; Image1.canvas.pen.color := clRed; Image1.canvas.rectangle (140,20,240,130); end; Dans ce qui suit, nous examinerons les principale propriétés et méthodes associées au Canvas. Exercice 1: Ecrire un programme permettant d'afficher des rectangles de diverses caractéristiques: emplacement, dimensions, couleur, remplissage, épaisseur... 6.2 Pen et Brush L'objet Pen de type Tpen représente le crayon (ou la plume) et permet de dessiner. L'objet Brush de type Tbrush représente le pinceau et sert à peindre, c'est-à-dire à remplir des surfaces d'une certaine couleur, avec un certain motif. Un pinceau possède également une forme. __________________________________________________________________________________________ Le graphisme (JC Armici) - 11 www.unvrai.com Par défaut, Pen possède les caractéristiques suivantes: • Couleur: noir • Epaisseur: 1 pixel • Style: trait continu • Mode: pmCopy, c'est-à-dire la couleur spécifiée par la propriété Color Par défaut, Brush possède les caractéristiques suivantes: • Couleur: blanc • Style: bsSolid, c'est-à-dire remplissage uniforme Indications concernant les couleurs Lorsque l'on s'intéresse au graphisme en informatique il faut savoir comment les couleurs sont représentées. Le programme proposé ici permet d'explorer les mélanges de couleurs fondamentales dans les deux types de synthèses. Il est donné uniquement dans un but d'illustration. La manière dont il est écrit n'est pas forcément très explicite à ce stade du cours. Ex2b.exe La représentation RGB (Red Green Blue ou Rouge Vert Bleu) utilise une synthèse additive des couleurs et intervient, par exemple, dans l'affichage sur les écrans. Dans ce cas c'est la lumière qui est mélangée: on part du noir (aucune couleur) et on ajoute des couleurs. La représentation CMY (Cyan Magenta Yellow) utilise une synthèse soustractive des couleurs et intervient, par exemple, dans l'impression. Ce sont des encres ou des pigments qui sont mélangés. __________________________________________________________________________________________ Le graphisme (JC Armici) - 12 www.unvrai.com Ex2b.exe En synthèse additive: noir + rouge = rouge noir + vert = vert noir + bleu = bleu noir + rouge + vert = jaune noir + rouge + bleu = magenta noir + bleu + vert = cyan noir + rouge + vert + bleu = blanc En synthèse soustractive: blanc - rouge = cyan blanc - vert = magenta blanc - bleu = jaune blanc - rouge - vert = bleu blanc - rouge - bleu = vert blanc - bleu - vert = rouge blanc - rouge - vert - bleu = noir Avec ces précisions à l'esprit, une tulipe sera plutôt "non bleue" que "jaune". 6.3 Pixels Pour chaque objet ou composant disposant d'un canvas, il est possible de spécifier et/ou de lire la couleur d'un pixel donné. Par exemple, pour placer un point rouge aux coordonnées (80,100), il suffit d'écrire: __________________________________________________________________________________________ Le graphisme (JC Armici) - 13 www.unvrai.com Canvas.pixels[80,100] := clRed; On voit que le canvas, en tant que surface de dessin, est considéré comme un tableau à deux dimensions. L'origine du canvas étant le point (0,0) qui se trouve en haut et à gauche du canvas, les coordonnées croissant vers la droite et vers le bas. Dans notre exemple, clRed est une constante entière indiquant la couleur rouge. On aurait également pu écrire: Canvas.pixels[80,100] := 45766; pour autant que l'on connaisse la couleur associée à la valeur 45766, ou encore: Canvas.pixels[80,100] := RGB (255, 0, 0); la fonction RGB indiquant une couleur au moyen de ses composantes rouge, vert et bleu; chaque composante peut avoir une valeur comprise entre 0 et 255. Pour connaître la couleur d'un pixel, il faut faire appel au contenu du tableau pixels pour la coordonnée désirée, par exemple: Couleur := canvas.pixels[34,56]; La variable Couleur contiendra la valeur entière représentant la couleur du point de coordonnées (34,56). De même, pour tester si un point est "allumé" sur un fond blanc on pourra écrire: If canvas.pixels[30,50] <> clWhite then… Ex3.exe Dans le programme suivant le bouton "Afficher pixel" permet d'afficher des points de la couleur et à des emplacements choisis par l'utilisateur. Le bouton lire pixel effectue l'opération inverse; il affiche la couleur du point dont les coordonnées sont spécifiées. Comme nous l'avons vu, il n'est pas évident de choisir une couleur en fonction de sa seule valeur numérique. Il est donc judicieux de proposer le choix visuel d'une couleur à l'aide du nouveau bouton visible sur l'exemple suivant: __________________________________________________________________________________________ Le graphisme (JC Armici) - 14 www.unvrai.com Ex4.exe Exercice 2: Ecrire un programme permettant d'afficher une image de type BMP dans un composant Timage et de connaître la valeur des composantes R, G et B des points sur lesquels se déplace la souris. Les fonctions GetRvalue, GetGvalue et GetBvalue permettent d'extraire les composante d'une couleur. L'affichage se fera sous la forme: R=230 G=12 B=255. Exercice 3: Modifier le programme de l'exercice précédent afin qu'un clic gauche de souris sur un point de l'image provoque le remplissage uniforme d'un composant (p.ex. Panel, Label, etc) avec la couleur du point sur lequel on a cliqué. 6.4 Lignes Travailler avec des points afin de tracer des lignes est un peu laborieux. Le canvas de Delphi dispose d'une méthode LineTo permettant de tracer un segment de droite. L'instruction: Canvas.LineTo (60,80); Trace une ligne entre la position précédente et le point (60,80). Si aucun tracé n'a encore été effectué, la position précédente est le point (0,0). Sinon il s'agit du point d'arrivée de la dernière opération graphique effectuée sur le canvas. Le canvas connaît donc la notion de position courante. Cette position courante peut être consultée à l'aide de la propriété PenPos (de type TPoint): Var p : TPoint; Indic : string; . . . p := canvas.PenPos; indic := 'La position courante est: x=' + inttostr(p.x) + ' y=' + inttostr(p.y); __________________________________________________________________________________________ Le graphisme (JC Armici) - 15 www.unvrai.com Revenons à nos lignes. Pour tracer une ligne du point (20,20) au point (65,30), il conviendra d'écrire: Canvas.MoveTo (20,20); Canvas.LineTo (65,30); La première instruction déplace le point courant à la position (20,20) et la seconde trace une ligne jusqu'à la position (65,30). Ex5.exe L'exemple qui suit permet de tracer des segments de droites en suivant les clics de la souris. Dans ce programme le tracé est effectué sur le canvas de la fenêtre principale, alors que pour les deux exemples précédents nous avons utilisé le canvas d'un composant de type TImage. Le principe est identique et l'avantage d'utiliser un composant TImage est de restreindre les opérations de dessin dans un rectangle. Il est même possible de déclarer une variable de type TCanvas et de lui affecter, au choix de l'utilisateur, le canvas de l'un ou l'autre de deux composants de type TImage. Ex6.exe public { Déclarations publiques } canv : TCanvas; __________________________________________________________________________________________ Le graphisme (JC Armici) - 16 www.unvrai.com Le clic sur le bouton radio "droite" affecte à la variable canv le canvas du TImage de droite, puis efface le contenu de ce même canvas: canv := image2.canvas; canv.fillrect (rect(0,0,image2.width,image2.height)); Le bouton "Lignes au hasard" trace 100 lignes d'emplacement et de couleur aléatoires, et d'épaisseur 2 pixels. procedure TForm1.HasardClick(Sender: TObject); var i : integer; begin canv.pen.width := 2; for i := 1 to 100 do begin canv.pen.color := RGB(random(256), random(256), random(256)); canv.lineto (random(image1.width), random(image1.height)); application.processmessages; // pour voir une ligne après l'autre end; end; Exercice 4: Ecrire un programme permettant de tracer une ligne entre deux points sur lesquels on a cliqué avec la souris. Dans ce programme, des options doivent permettre de choisir le style de la ligne (traitillée, pointillée, etc), son épaisseur, sa couleur. Que constatez-vous en variant les divers paramètres ? 6.5 Rectangles, ellipses, arcs, cordes Ex7.exe Sur un canvas il est possible de tracer des figures telles que rectangles (avec ou sans les coins arrondis), ellipses, arcs ou cordes. Voici un programme permettant d'afficher ces figures. La procédure suivante trace un rectangle (avec ou sans coins arrondis en fonction de l'état de la case à cocher: procedure TForm1.Button1Click(Sender: TObject); begin if rond.checked then image1.canvas.roundrect (random(100), random(100), 120 + random(100), 120 + __________________________________________________________________________________________ Le graphisme (JC Armici) - 17 www.unvrai.com random(50), 20, 20) else image1.canvas.rectangle (random(100), random(100), 120 + random(100), 120 + random(50)); end; La procédure suivante trace une ellipse: procedure TForm1.Button2Click(Sender: TObject); begin image1.canvas.ellipse (random(100),random(100),120+random(100),120+random(50)); end; La procédure suivante trace un arc d'ellipse: procedure TForm1.Button3Click(Sender: TObject); begin image1.canvas.arc (random(100), random(100), 120 + random(100), 120 + random(50), random(image1.width), image1.height, random(image1.width), 0); end; La procédure suivante trace un arc et une corde d'ellipse: procedure TForm1.Button5Click(Sender: TObject); begin image1.canvas.chord (random(100),random(100),120 + random(100), 120 + random(50), random(image1.width),image1.height,random(image1.width),0); end; 6.6 Rectangles pleins et remplissage de surfaces Ex8.exe Au travers du programme qui suit nous allons voir comment tracer des rectangles remplis de différentes manières. En cliquant avec le bouton gauche ou droit de la souris sur le ColorGrid, on choisit la couleur du crayon (Pen) pour le bord du rectangle ou la couleur du pinceau (Brush) pour l'intérieur du rectangle. __________________________________________________________________________________________ Le graphisme (JC Armici) - 18 www.unvrai.com Un TrackBar permet de choisir l'épaisseur du bord du rectangle (entre 1 et 6). On peut également choisir le style de remplissage du rectangle, parmi les 8 possibilités offertes par Windows. Dans le programme suivant, l'utilisateur peut dessiner en utilisant la souris (avec le bouton gauche enfoncé). Lorsqu'il clique a un emplacement avec le bouton droit de la souris, la surface est remplie de la couleur choisie à l'aide du ColorGrid. Ex9.exe La couleur du tracé est choisie avec le bouton gauche de la souris sur le ColorGrid, alors que la couleur de remplissage est choisie avec le bouton droit de la souris. La procédure suivante permet à l'utilisateur d'effectuer un tracé à l'aide du bouton gauche enfoncé. procedure TForm1.Image1MouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer); begin image1.canvas.pen.color := colorgrid1.ForegroundColor; if shift = [ssleft] then image1.canvas.lineTo (x,y); end; La procédure suivante effectue le remplissage en partant du point cliqué et en utilisant la couleur du pinceau (Brush) jusqu'à rencontrer la couleur colorgrid1.ForegroundColor: procedure TForm1.Image1MouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); begin image1.canvas.Brush.color := colorgrid1.BackgroundColor; if shift = [ssright] then image1.canvas.FloodFill (x, y, colorgrid1.ForegroundColor, fsBorder); end; __________________________________________________________________________________________ Le graphisme (JC Armici) - 19 www.unvrai.com Exercice 5: Ecrire un programme qui ressemble à l'exemple Ex9 ci-dessus et Ex111 ci-dessous, mais dans lequel l'utilisateur trace des segments de droites formant une ligne polygonale. La surface sera ensuite remplie d'une couleur à choix. Lorsque l'utilisateur clique sur la souris le segment de droite en cours suit le mouvement du curseur et s'affiche définitivement lorsque le bouton de la souris est relâché. 6.7 Courbes de Bézier Une courbe de Bézier utilise 4 points. La courbe va du 1er point au 4ème point. Les points intermédiaires servent de points de contrôle. La méthode PolyBezier du Canvas permet de tracer automatiquement une courbe de Bézier, en lui fournissant un tableau de 4, 7, 10, 13, … points. Ex10.exe Le programme suivant permet à l'utilisateur d'indiquer 10 points en cliquant avec le bouton gauche de la souris. Le bouton "Bézier" trace la courbe de Bézier: Il convient de signaler que la courbe est de la couleur du crayon (Pen), mais qu'il n'y a aucun remplissage. De plus, le tracer d'une courbe de Bézier ne change pas la valeur de PenPos. Exercice 6: Ecrire un programme qui s'inspire de l'exemple Ex10 ci-dessus, mais avec lequel il est possible de joindre les segments de courbes de Bézier sans former un angle. __________________________________________________________________________________________ Le graphisme (JC Armici) - 20 www.unvrai.com 6.8 Polygones En prenant comme base de départ 10 points choisis par l'utilisateur comme dans le programme précédent, il est aussi possible de tracer une ligne polygonale, à l'aide de la méthode Polygon du Canvas. La ligne prend les attributs du crayon (Pen) et l'intérieur est rempli en prenant les attributs du pinceau (Brush). La méthode Polygon relie automatiquement le premier et le dernier point. Ex11.exe Par rapport à l'exercice précédent le choix des deux couleurs a été ajouté. Il s'effectue à l'aide d'un ColorGrid. De plus, le tracer d'une telle ligne polygonale ne change pas la valeur de PenPos. Le Canvas dispose également de la méthode PolyLine qui ressemble beaucoup à Polygon, avec toutefois les différences suivantes: • • La ligne n'est pas automatiquement fermée Il n'y a aucun remplissage 6.9 Affichage de textes Le Canvas dispose de deux méthodes permettant d'afficher du texte à un emplacement donné: TextOut et TextRect. La seconde offre la possibilité de spécifier un rectangle à l'extérieur duquel le texte sera masqué. Lorsqu'il s'agit d'afficher du texte il est parfois nécessaire de connaître la hauteur et la largeur de ce texte en pixels. La méthode TextExtent permet de retrouver ces deux informations. Voici sa déclaration: type TSize = record cx: Longint; cy: Longint; end; __________________________________________________________________________________________ Le graphisme (JC Armici) - 21 www.unvrai.com function TextExtent(const Text: string): TSize; Ex12.exe Signalons qu'il existe aussi la méthode TextHeight pour connaître uniquement la hauteur du texte et TextWidth pour connaître uniquement la largeur. L'exemple qui suit illustre l'utilisation de ces nouvelles méthodes du Canvas. Afin de mieux visualiser le rectangle de masquage, le programme en trace le contour en rouge. Les dimensions de la chaîne de caractères sont affichées en cliquant sur le bouton TextOut. Voici le code associé au clic de ce bouton: procedure TForm1.Button1Click(Sender: TObject); var taille : TSize; begin image1.canvas.font.size := 14; image1.canvas.textout (20, 20, 'Bonjour'); taille := image1.canvas.TextExtent ('Bonjour'); edit1.Text := inttostr (taille.cx); edit2.Text := inttostr (taille.cy); end; On peut également observer le choix de la taille de la fonte. La valeur de la propriété Font est un objet de type TFont. Il suffit d'affecter des valeurs aux propriétés de l'objet TFont pour spécifier la police, la couleur, la taille et le style de la fonte. 6.10 Images Jusqu'ici nous avons utilisé le canvas d'un composant TImage pour dessiner. Il est également possible d'attribuer une image de type bitmap à ce composant. Le programme suivant permet à l'utilisateur de charger et d'enregistrer une image, ainsi que de modifier trois propriétés d'une image: Autosize, Center et Stretch. Ce programme fait également appel à deux dialogues standards pour lire et enregistrer les images: OpenPictureDialog et SavePictureDialog. A la différence de leurs homologues OpenDialog et SaveDialog, ils proposent une prévisualisation de l'image à charger ou à sauvegarder. __________________________________________________________________________________________ Le graphisme (JC Armici) - 22 www.unvrai.com Ex13.exe Ex13.exe Voici la boîte de dialogue qui apparaît en cliquant sur le bouton "Ouvrir": La propriété Center permet de centrer l'image dans le composant TImage. __________________________________________________________________________________________ Le graphisme (JC Armici) - 23 www.unvrai.com Ex13.exe Ex13.exe La propriété Stretch effectue un redimensionnement de l'image de manière à ce qu'elle occupe toute la surface du composant TImage: L'option Autosize redimensionne le composant TImage de manière à ce que sa taille corresponde à celle de l'image qui est chargée. Exercice 7: Ecrire un programme permettant d'afficher sous forme réduite toutes les images de type BMP qu'il trouve dans un répertoire donné. Amélioration possible: parcours récursif des sous-répertoires. __________________________________________________________________________________________ Le graphisme (JC Armici) - 24 www.unvrai.com 6.11 Programme de dessin sans ambition Ex13b.exe Nous allons construire un petit programme de dessin (du style Paint). Dans un premier temps notre programme aura un minimum de fonctionnalités. Dans un second temps nous lui ajouterons petit à petit des possibilités, aussi bien au niveau des outils disponibles qu'au niveau des possibilités. La première version ressemble à ceci: Nous allons décrire en détail le programme au niveau de son interface et de son implémentation. Eléments intervenant dans le programme: • • • • • • • • Une fenêtre principale, volontairement non dimensionnable. Un menu contenant aussi bien des options générales (Charger une image, Quitter…) que des options qui, par exemple, se retrouvent au niveau de boutons de l'interface graphique. Une surface de dessin im (de type TImage), placée dans un Panel. Toutes les opérations de dessin s'effectuent sur le canvas de l'objet im. .Une barre d'état (StatusBar), indiquant dans le 1er volet l'outil en cours d'utilisation, dans le 2ème volet les coordonnées du cirseur (souris), dans le 3ème volet la taille de la figure en cours de dessin (uniquement pour les figures inscrites dans un rectangle. Un composant ColorGrid pour le choix des couleurs du crayon et du pinceau (remplissage) Un dispositif permettant de choisir l'épaisseur des lignes (1, 2, 4 ou 8 pixels). Plusieurs possibilités étaient envisageables; nous avons choisi celle consistant à placer 4 Labels de hauteur variable. Différents composants de type TImage contenant des icônes empruntées à MSPAINT.EXE Deux éléments ne sont pas visibles sur la copie d'écran ci-dessus. Il s'agit des dialogues de chargement et de sauvegarde de l'image. __________________________________________________________________________________________ Le graphisme (JC Armici) - 25 www.unvrai.com Voici la définition des constantes correspondant aux 3 outils disponibles: const fLIBRE fRECTANGLE fELLIPSE = 0; = 1; = 2; // main levée // rectangle // ellipse fsLIBRE = 'Desin libre'; fsRECTANGLE = 'Rectangle'; fsELLIPSE = 'Ellipse'; // libellés correspondants De plus, nous avons besoin des variables (champs) suivantes: public { Déclarations publiques } fig : integer; // choix de l'outil x0, y0 : integer; // position intermédiaire xd, yd : integer; // position de départ end; La variable fig contient l'outil courant. Les variables xd, yd, x0 et y0 sont utilisées lors du dessin de figure telles que rectangles ou ellipses. Lors du lancement du programme (OnActivate) on effectue les opérations suivantes: procedure TForm1.FormActivate(Sender: TObject); begin im.canvas.moveto (0,0); fig := fLIBRE; // tracé à main levée statusbar1.panels[0].text := fsLIBRE; end; Deux options du menu Fichier permettent de charger et d'enregistrer l'image: procedure TForm1.Ouvrir1Click(Sender: TObject); begin if OpenPictureDialog1.execute then im.Picture.LoadFromFile (OpenPictureDialog1.FileName); end; procedure TForm1.Enregistrersous1Click(Sender: TObject); begin if SavePictureDialog1.execute then im.Picture.SaveToFile (SavePictureDialog1.Filename); end; Lorsque l'utilisateur clique (OnClick) avec le bouton droit ou gauche pour choisir une couleur, voici les instructions qui sont exécutées: procedure TForm1.ColorGrid1Click(Sender: TObject); begin im.canvas.Pen.color := colorgrid1.ForegroundColor; im.canvas.brush.color := colorgrid1.BackgroundColor; end; Lorsque l'utilisateur clique sur la surface de dessin (OnMouseDown), le code suivant est exécuté: procedure TForm1.imMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); begin im.canvas.moveto (x,y); xd := x; // on garde la position de départ yd := y; end; __________________________________________________________________________________________ Le graphisme (JC Armici) - 26 www.unvrai.com Après que l'utilisateur a appuyé sur le bouton de la souris, il peut déplacer celle-ci; le résultat obtenu dépend de l'outil sélectionné. Voici le code derrière l'événement OnMouseMove: procedure TForm1.imMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer); begin statusbar1.panels[1].text := inttostr (x) + ',' + inttostr(y); if ssleft in shift then case fig of fLIBRE : im.canvas.LineTo (x,y); fRECTANGLE : begin im.canvas.Pen.Mode := pmxor; im.canvas.Rectangle (xd, yd, x0, y0); im.canvas.Rectangle (xd, yd, x, y); statusbar1.panels[2].text := inttostr (abs(x-xd)) + 'x' + inttostr(abs(y-yd)); end; fELLIPSE : begin im.canvas.Pen.Mode := pmxor; im.canvas.ellipse (xd, yd, x0, y0); im.canvas.ellipse (xd, yd, x, y); statusbar1.panels[2].text := inttostr (abs(x-xd)) + 'x' + inttostr(abs(y-yd)); end; end; x0 := x; y0 := y; end; Dans un premier temps on affiche les coordonnées de la souris dans le 2ème volet de la barre d'état. Ensuite les instructions dépendent de l'outil sélectionné. Pour le dessin libre, on effectue simplement un tracé de ligne entre le point précédent et le point actuel. Pour le rectangle ou l'ellipse, on dessine la figure à la position précédente (de (xd,yd) à (x0,y0)) en mode xor, puis à la nouvelle position (de (xd,yd) à (x,y)). L'utilisation du mode xor permet de ne pas détruire le fond de l'image pendant le dessin de la figure. On en profite également pour indiquer, dans le 3ème volet de la barre d'état, les dimensions actuelles de la figure qui est en train d'être dessinée. Enfin on actualise la valeur des coordonnées de la position précédente. C'est dans cette procédure que viendra s'ajouter le code qui concernera l'utilisation d'outils futurs (gomme, spray…). Lorsque l'utilisateur lâche le bouton de la souris (OnMouseUp) les opérations suivantes sont effectuées: procedure TForm1.imMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); begin case fig of fRECTANGLE : begin im.canvas.Pen.Mode := pmcopy; im.canvas.Rectangle (xd, yd, x, y); end; fELLIPSE : begin im.canvas.Pen.Mode := pmcopy; im.canvas.Ellipse (xd, yd, x, y); end; end; statusbar1.panels[2].text := ''; end; En fait, pour le rectangle et pour l'ellipse, il faut passer en mode normal (pmcopy) et de dessiner la figure à sa position finale. Il ne faut pas oublier d'effacer le contenu du 3ème volet de la barre d'état. __________________________________________________________________________________________ Le graphisme (JC Armici) - 27 www.unvrai.com Lorsque l'utilisateur change d'outil, soit à l'aide du menu Outils soit en cliquant sur l'icône de l'outil, le programme doit mémoriser le nouvel outil choisi et afficher sa description dans le 1er volet de la barre d'état. Voici uniquement l'exemple concernant l'outil "Dessin libre": procedure TForm1.Dessinlibrfe1Click(Sender: TObject); begin fig := fLIBRE; statusbar1.panels[0].text := fsLIBRE; end; Pour le choix de l'épaisseur du crayon nous avons utilisé une particularité de Delphi, à savoir le paramètre Sender que l'on retrouve dans quasiment toutes les procédures événementielles. Le code qui gère le choix de l'épaisseur mérite quelques explications. Tout d'abord il faut savoir que les 4 labels représentant les épaisseurs de ligne ont comme noms e1, e2, e4 et e8. Lorsque l'utilisateur clique sur e8 (épaisseur de 8 pixels), voici la procédure événementielle qui est exécutée: procedure TForm1.e8Click(Sender: TObject); begin im.canvas.pen.width := strtoint((sender as Tlabel).name[2]); end; Il suffit de choisir cette même procédure pour les 3 autres labels. En fait: (sender as Tlabel).name représente le nom du label (par exemple 'e8') (sender as Tlabel).name[2] représente le 2ème caractère du nom, donc le nombre de pixels d'épaisseur On transforme ensuite ce caractère en chiffre et on l'affecte à l'épaisseur du crayon pour le canvas de l'objet im (surface de dessin). Enfin, une option "Effacer tout" du menu Edition permet d'effacer le contenu de la surface de dessin. Voici son code: procedure TForm1.Effacertout1Click(Sender: TObject); var back : integer; begin back := im.canvas.brush.color; // sauvegarde de la couleur en cours im.canvas.brush.color := clWhite; im.canvas.fillrect (rect (0, 0, im.width, im.height)); im.Canvas.brush.color := back; // restitution de la couleur en cours end; 6.12 Amélioration du programme de dessin Il manque beaucoup de fonctionnalités à la version précédente de notre programme de dessin. Entre autres: • • • • • Il n'y a aucune possibilité de faire des zooms La taille de la fenêtre est fixe Si l'image chargée est de grande dimension, on n'en voit qu'une partie On ne peut pas choisir d'autres couleurs que les 16 proposées Les outils sont minimalistes, en nombre et en possibilités __________________________________________________________________________________________ Le graphisme (JC Armici) - 28 www.unvrai.com Ex14.exe Voici l'aspect du nouveau programme de dessin: Voyons quelles améliorations ont été apportées au programme. Fenêtre dimensionnable et image avec défilement La fenêtre a un style de bordure dimensionnable. Le composant im (de type TImage) dont on utilise le canvas en tant que surface de dessin est maintenant contenu dans un composant ScrollBox avec les propriétés suivantes: • • • • Autosize: false Autoscroll: true Anchors: les quatre sont true Barres de défilement: o smooth: true o tracking: true o style: ssHotTrack Le composant im a les propriétés suivantes: • • Autosize: true Stretch: false Avec ces propriétés ainsi définies, le dimensionnement de la fenêtre provoque le dimensionnement de l'image. De plus, les composants liés aux icônes des outils, au choix d'épaisseur de ligne et des couleurs sont ancrés en bas et à gauche (au lieu de en haut et à gauche comme précédemment). Enfin les deux Trackbars réglant les paramètres du __________________________________________________________________________________________ Le graphisme (JC Armici) - 29 www.unvrai.com spray sont ancrés en bas et à droite. De cette manière, lors du changement de dimension de la fenêtre, ces composants restent attachés à leur coin respectif. Signalons également qu'une taille minimale à été imposée à la fenêtre à l'aide de ses propriétés Constraints.MinHeight et Constraints.MinWidth. Les outils L'organisation des outils a été améliorée. Voici les définitions de constantes en rapport avec les outils: const MAX = 7; const fLIBRE fRECTANGLE fELLIPSE fSPRAY fSPRAY2 fRECTANGLER fPIPETTE fZOOM = = = = = = = = 0; 1; 2; 3; 4; 5; 6; 7; // // // // // // // // main levée rectangle ellipse spray spray 2ème type rectangle à coins arrondis pipette loupe Les libellés à afficher dans la barre d'état sont maintenant placés dans un tableau: lib : array[0..MAX] of string [30]; et initialisés lors du lancement du programme: procedure TForm1.FormActivate(Sender: TObject); begin lib[0] := 'Desin libre'; lib[1] := 'Rectangle'; lib[2] := 'Ellipse'; lib[3] := 'Spray'; lib[4] := 'Spray continu'; lib[5] := 'Rectangle à coins arrondis'; lib[6] := 'Pipette'; lib[7] := 'Loupe'; im.canvas.moveto (0,0); fig := fLIBRE; // tracé à main levée statusbar1.panels[0].text := lib[0]; tspr := 5; // taille du spray fspr := spROND; // forme du spray dspr := 10; // densité du spray colorgrid1.ForegroundIndex := 0; colorgrid1.BackgroundIndex := 15; fond.color := clwhite; // pannel indiquant la couleur du pinceau texte.color := clblack; // pannel indiquant la couleur du crayon end; Les composants de type TImage contenant l'icône des outils s'appellent maintenant btn0 pour le dessin libre, btn1 pour le rectangle, etc. Lors du clic sur une de ces icônes le code suivant est exécuté (pour tous les outils): procedure TForm1.btn0Click(Sender: TObject); var no : integer; // no. de l'outil begin no := strtoint((sender as TImage).name[4]); fig := no; statusbar1.panels[0].text := lib[no]; cache.Visible := not (fig in [fSPRAY,fSPRAY2]); end; __________________________________________________________________________________________ Le graphisme (JC Armici) - 30 www.unvrai.com Nous avons déjà rencontré précédemment l'utilisation du paramètre Sender. Le composant Cache auquel il est fait référence est un Panel qui masque les deux TrackBars concernant la taille et la densité du spray lorsque le spray n'est pas sélectionné. Enfin, le tracé de figures telles que rectangles et ellipses a été modifié pour qu'elles soient vides (intérieur transparent) lorsqu'elles sont dessinées à l'aide du bouton gauche de la souris et remplies (avec la couleur du pinceau) lorsqu'elles sont dessinées à l'aide du bouton droit de la souris. Rectangle à coins arrondis Ce nouvel outil ressemble à celui dessinant un rectangle. La méthode RoundRect est appelée en lieu et place de la méthode Rectangle. Les deux types de spray Nous avons choisi d'implémenter deux sortes de sprays: • • Un spray qui spraye uniquement lorsqu'on le bouge Un spray qui spraye en continu, même lorsque l'on ne déplace pas la souris Ex14.exe Chaque spray peut avoir une forme carrée ou ronde choisie à l'aide du menu suivant: Les deux options concernant la forme du spray provoque l'exécution du code suivant: procedure TForm1.Circulaire1Click(Sender: TObject); begin fspr := spROND; // forme du spray carr1.checked := false; // enlève la coche __________________________________________________________________________________________ Le graphisme (JC Armici) - 31 www.unvrai.com circulaire1.checked := true; end; // place la coche procedure TForm1.Carr1Click(Sender: TObject); begin fspr := spCARRE; // forme du spray carr1.checked := true; // place la coche circulaire1.checked := false; // enlève la coche end; Le spray simple fonctionne de manière assez simple. La procédure événementielle exécutée lorsque l'on déplace la souris contient le code suivant: fSPRAY : begin for i := 1 to 10*dspr do begin if fspr = spCARRE then im.canvas.pixels[x+tspr-random(2*tspr),y+tspr- random(2*tspr)] := im.canvas.pen.color else begin xx := tspr - random(2*tspr); yy := tspr - random(2*tspr); if sqrt (xx*xx + yy*yy) < tspr then im.canvas.pixels[x+xx,y+yy] := im.canvas.pen.color end; end; end; end; On remarquera qu'une boucle place un certain nombre de points au hasard dans un carré ou un cercle. Le nombre de points dépend du contenu de la variable dspr (qui détermine la densité du spray et est mise à jour à l'aide du 1er TrackBar situé en bas à droite de la fenêtre, lorsque l'un des sprays est sélectionné). La taille du spray dépend de la variable tspr (mise à jour à l'aide du 2ème TrackBar situé en bas à droite de la fenêtre). Pour le spray en mode continu, il fallait trouver une méthode qui permette de placer des points même lorsque la souris n'est pas déplacée. Nous avons opté pour l'utilisation d'un Timer qui est mis en marche lorsque le bouton gauche de la souris est appuyé: if fig = fSPRAY2 then timer1.enabled := true; et qui est arrêté lorsque le bouton de la souris est relâché. case fig of fSPRAY2 : timer1.enabled := false; … L'intervale du timer est réglé sur 10 millisecondes et le code qui lui est associé est le suivant: procedure TForm1.Timer1Timer(Sender: TObject); var i : integer; xx, yy : integer; begin for i := 1 to 10*dspr do begin if fspr = spCARRE then im.canvas.pixels[x0+tspr-random(2*tspr),y0+tspr-random(2*tspr)] := im.canvas.pen.color else begin xx := tspr - random(2*tspr); yy := tspr - random(2*tspr); if sqrt (xx*xx + yy*yy) < tspr then im.canvas.pixels[x0+xx,y0+yy] := im.canvas.pen.color end; __________________________________________________________________________________________ Le graphisme (JC Armici) - 32 www.unvrai.com end; end; Ce code ressemble beaucoup à celui du spray normal, à la différence qu'il est exécuté en permanence aussi longtemps que le bouton de la souris est enfoncé. La pipette Cet outil permet de connaître la valeur de la couleur du point sur lequel il se trouve, lorsque la souris est déplacée. De plus, en cliquant avec le bouton gauche de la souris, la couleur du point sous-jacent est affectée au crayon, alors qu'en cliquant avec le bouton droit de la souris, la couleur est affectée au pinceau. Lorsque la souris est déplacée, la valeur RGB de la couleur est affichée dans le 4ème volet de la barre d'état: if fig = fPIPETTE then begin coul := im.canvas.pixels[x,y]; statusbar1.panels[3].text := inttostr(getRvalue(coul)) + ':' +inttostr(getGvalue(coul)) + ':' +inttostr(getBvalue(coul)); pipettecoul.color := coul; end; Le composant Pipettecoul est un Panel dont la couleur suit le mouvement de la souris. texte fond pipettecoul Texte et fond sont deux Panels dont la couleur indique respectivement celle du crayon et celle du pinceau. Voici le code exécuté lorsque le bouton de la souris est appuyé et concernant l'outil pipette: if fig = fPIPETTE then begin if [ssleft] = shift then begin im.canvas.pen.color := pipettecoul.Color; texte.color := pipettecoul.color; end; if [ssright] = shift then begin im.canvas.brush.color := pipettecoul.Color; fond.color := pipettecoul.color; end; pipettecoul.color := clSilver; end; Le zoom Pour le zoom, plusieurs méthodes étaient utilisables. Nous avons opté pour une méthode simple, qui met en jeu un second composant de type TImage invisible. Nous nous sommes également limités à un zoom d'un facteur deux. Un premier clic sur la loupe effectue un zoom avant d'un facteur deux, un second clic génère un zoom arrière d'un facteur deux. __________________________________________________________________________________________ Le graphisme (JC Armici) - 33 www.unvrai.com Le principe utilisé est le suivant. On choisit une dimension pour le composant im2 (TImage invisible), puis on copie l'image de im dans im2 à l'aide de la méthode CopyRect. On affecte enfin l'image de im2 dans im. Ci-dessous on peut voir la procédure de zoom, avec les quatre lignes importantes en gras: procedure TForm1.btn07Click(Sender: TObject); var rect1, rect2 : TRect; begin pencol0 := im.canvas.pen.color; penwidth0 := im.canvas.pen.width; brushcol0 := im.canvas.brush.color; rect1 := rect (0,0,im.width, im.height); if not bZoom then begin rect2 := rect (0,0,im.width * 2, im.height * im2.width := im.width*2; im2.height := im.height*2; end else begin rect2 := rect (0,0,im.width div 2, im.height im2.width := im.width div 2; im2.height := im.height div 2; end; im2.Canvas.CopyRect (rect2, im.canvas, rect1); im.picture := nil; im.picture.bitmap.Assign (im2.picture.bitmap); im2.picture := nil; // en mode zoom on double l'épaisseur im.canvas.pen.width := penwidth0; if not bZoom then im.canvas.Pen.width := im.canvas.Pen.width * else if im.canvas.pen.width > 1 then im.canvas.Pen.width := im.canvas.Pen.width bZoom := not bZoom; im.canvas.pen.color := pencol0; im.canvas.brush.color := brushcol0; end; 2); div 2); 2 div 2; Il faut encore signaler que l'épaisseur et la couleur du crayon (Pen) ainsi que la couleur du pinceau (Brush) doivent être sauvegardés, car ils ne sont pas transmis automatiquement lors de l'affectation d'un canvas à l'autre. De plus, lors du zoom avant, la taille du crayon est multipliée par deux, afin qu'une ligne tracée sur l'image zoomée soit encore visible lors du zoom arrière. Lors d'un zoom arrière l'épaisseur du crayon est inversement divisée par deux. Cette opération permet de garder une certaine cohérence; il n'est toutefois pas interdit de choisir explicitement une épaisseur de 1 pixel dans le mode zoom. __________________________________________________________________________________________ Le graphisme (JC Armici) - 34 www.unvrai.com