OpenScheme : Primitives graphiques 2
Par :
Guilhem de Wailly (gdw@erian-concept.com)

et :
Fernand Boéri (boeri@unice.fr)

Résumé
Le mois dernier, nous avons commencé l'étude des primitives graphiques disponibles dans OpenScheme. Cet article termine cette présentation qui va nous permettre de concevoir un logiciel de tracé de courbes et une petite bibliothèque orienté objet présentant quelques objets graphique. Cette étude très pratique permettra d'appréhender la méthodologie objet appliquée aux interfaces graphiques.
L'environnement OpenScheme, version 1.3.2  pour Linux est maintenant disponible sur le CDROM de Linux Magazine. Il est aussi disponible sur www.open-scheme.com. Cet environnement existe en version libre ou commerciale.
Introduction
La bibliothèque graphique d'OpenScheme est un ensemble de fonctions primitives gérant l'affichage graphique.
Ces fonctions s'appuient sur le système graphique existant, que ce soit X11 pour les systèmes Unix, Windows, BeOS ou Machintoch (le portage est prévu pour le premier semestre 2000).
Contrairement aux autres environnements Scheme, Universitaires ou commerciaux, OpenScheme repose donc sur sa propre bibliothèque de primitives. Les autres environnements ont choisi TK (couche graphique de TCL/TK), wxWindow ou s'interfacent directement avec le système d'exploitation.
La bibliothèque de base présentée ici est conçue entièrement par Erian Concept, comme la totalité de l'environnement. Elle est écrite en C dont une partie du code est dépendante du système et l'autre non. Elle se présente sous la forme d'un module (code chargeable séparément et dynamiquement) de manière à n'utiliser la bibliothèque que lorsque cela est nécessaire.
Ce module est appelé OpenScheme toolKit ou OK. Les fonctionnalités offertes sont primitives : par exemple, OK ne permet pas de dessiner un bouton graphique. Il ne connaît que la fenêtre dans sa plus simple expression, c'est à dire un rectangle que l'on peut manipuler et auquel il est possible d'attacher des gestionnaires d'évènements. 
Bien que primitif, OK n'est pas en reste au niveau des performances. Il permet par exemple de définir un angle pour les fonctions de dessin. On peut ainsi écrire du texte à 45°. Un moteur 3D est en cours de développement : il permettra de manipuler des formes en trois dimensions et de les animer, sans vouloir concurrencer les moteurs existants comme OpenGL. Ce moteur est déjà utilisable dans la dernière version d'OpenScheme sur le site www.open-scheme.com. Le support des polices vectorielles est en cours et sera utilisable dans une prochaine version. La particularité des polices de caractères est quelles utiliseront une syntaxe Scheme !
La couche haute de la bibliothèque graphique est un autre module appelé OOK pour Object Oriented OpenScheme ToolKit. Comme son nom l'indique, c'est une bibliothèque de classes offrant les éléments graphiques usuels, comme les boutons, les menus, les zones d'édition. La programmation de cette bibliothèque repose entièrement sur la programmation orientée objet en Scheme qui se prête particulièrement bien à ce genre d'exercice. Dans un prochain article, nous concevrons un mini système d'objets graphiques en utilisant OpenScheme et OK. Le lecteur se rendra compte à quel point la programmation en Scheme est efficace, tant au niveau de la clarté du code que des performances.
L'un des avantages de la solution retenue dans OpenScheme est de permettre une programmation entièrement objet de la bibliothèque graphique de haut niveau, en utilisant des fonctionnalités initiales primitives.
De plus, seule une partie du module OK est dépendante du système d'exploitation et nécessite un portage. Le reste du code ainsi que le module OOK sont indépendants. Nous prévoyons ainsi de créer un plugin pour les navigateurs NetScape et InternetExplorer : notre effort de programmation ne va porter que sur l'interfaçage de OK avec le navigateur, car le reste du code reste complètement indépendant. Cette souplesse se retrouve dans tout l'environnement OpenScheme, conçu dès le départ pour être portable et modulaire.
Nous allons commencer la description de OK par ce qui est visible, c'est à dire la fenêtre affichable à l'écran.
Bitmap
Dans l'article précédent, nous avons vu comment créer des fenêtres. Une fenêtre est une zone rectangulaire destinée à être affichée à l'écran. Elle possède des dispositifs permettant de gérer les interactions avec l'utilisateur sous la forme de gestionnaires d'événements. Nous le verrons plus bas, il est possible de dessiner dans les fenêtres.
OK propose aussi une autre forme rectangulaire, le bitmap. Un bitmap est aussi une zone rectangulaire, mais il n'est pas destiné à être affiché directement et ne propose aucun moyen d'interagir avec l'utilisateur. La seule chose que l'on puisse faire avec un bitmap est d'y dessiner et de recopier son contenu dans une fenêtre. Il est aussi possible de créer un bitmap à partir d'une fenêtre.
Un bitmap est créé simplement avec un appel à la fonction suivante :
(ok:create-bitmap couleur-fond 
                 largeur
                 hauteur)
La couleur est une couleur de OK, comme les constantes ok:red ou ok:blue, ou une couleur créée avec les fonctions ok:make-color ou ok:color. La largeur et la hauteur sont les dimensions en pixels du bitmap. La fonction retourne un objet Scheme. La fonction suivante :
(ok:bitmap? acteur)
retourne #t si acteur est un bitmap et #f dans le cas contraire.
Options de dessin
OK propose un certain nombre de fonctions de dessins s'appliquant indifféremment aux fenêtres ou aux bitmaps, que l'on appellera device. Les fonctions de dessin utilisent un certain nombre d'options initialisées dans le device, comme la couleur du trait, la police utilisée.
Les options de dessin comme les couleurs sont pré-réglées dans le device, alors que les options de placements sont données aux fonctions de dessin. Dans cette section, nous voyons les options de dessins concernant les couleurs, l'angle et l'origine du device.
Couleur de fond, de trait
Les fonctions d dessin utilisent deux types de couleurs : la couleur du trait et la couleur du fond. Par exemple, lorsque l'on dessine un rectangle, la couleur du fond sera placée au centre du rectangle et la couleur de trait sera placée sur les bords. La bibliothèque OK possède une couleur transparente qui peut être utilisée indifféremment pour les traits ou pour le fond.
Les fonctions :
(ok:background device)
(ok:foreground device)
retournent respectivement la couleur de fond et la couleur de trait du device. Les fonctions :
(ok:background! device couleur)
(ok:foreground! device couleur)
modifient respectivement la couleur de fond et la couleur de trait du device.
Angle
OK permet d'affecter un angle aux sorties graphiques. L'angle s'applique globalement au device, affectant ainsi toutes les sorties graphiques. Un exemple de l'utilisation de l'angle est donné plus bas, sur la sortie de texte. La fonction :
(ok:angle device)
retourne l'angle en cours dans le device. Cet angle est exprimé en degrés. La fonction :
(ok:angle! device angle)
permet de modifier la valeur de l'angle.
Origine
Par défaut, l'origine dans un device se situe dans le coin en haut à gauche. Les abscisses augmentent vers la droite et les ordonnées augmentent vers le bas. Le sens des axes ne peut pas être modifié. Il est cependant possible de modifier l'origine. La fonction :
(ok:origin device)
retourne une paire d'entiers. Le premier entier représente l'abscisse de l'axe des y par rapport au bord gauche et le second entier représente l'ordonnée de l'axe de x par rapport au bord haut. La fonction :
(ok:origin! device x y)
déplace l'axe des x et l'axe des y par rapport aux bords gauche et haut.
Opérations de copie
Par défaut, les opérations de dessin s'effectuent en remplaçant les pixels existants par les nouveaux, sans aucune combinaison. Il est possible d'obtenir le mode de fonctionnement en cours avec la fonction :
(ok:mode device)
Cette fonction retourne une constante représentant le mode en cours. Parmi les principaux modes, on trouve ok:mode:set (mode par défaut), ok:mode:xor (inversion du fond), 
Pour modifier le mode, on utilisera la fonction :
(ok:mode! device mode)
Police de caractères
L'affichage de texte dans un device est effectué dans la police en cours. La gestion et l'affichage des polices de caractères sont assez différents d'un système d'exploitation à l'autre. Actuellement, les meilleurs résultats sont obtenus avec les polices TrueType de l'environnement Windows. Ce type sera proposé bientôt dans les environnements Unix, mais cela mettra du temps à devenir un standard dans tous ces environnements. C'est pour cette raison que OK traitera lui-même sous peu les polices TrueType en gérant directement les fichiers de polices. L'avantage est de rendre l'affichage de caractères complètement identique quel que soit le système d'exploitation. De plus, cela permettra de bénéficier de ces polices dans le plugin IMG permettant de manipuler des images comme des images GIF ou JPEG. Le développement du support TrueType a commencé et on peut espérer un premier résultat d'ici l'été 2000.
En attendant, OK essaie de proposer une interface commune à tous les systèmes d'exploitation de la gestion des polices. En effet, il propose quatre polices standards et permet d'énumérer les autres polices de caractères du système. La fonction :
(ok:create-font famille attribut taille)
Les familles possibles sont ok:font:courier, ok:font:times, ok:font:helvetica et ok:font:terminal. L'attribut est l'une des valeurs ok:font:normal, ok:font:bold, ok:font:italic ou ok:font:bold-italic. La taille est spécifiée en points, comme 8, ou 12.
Cette fonction retourne une police de caractères. Cette police est affectée au device avec la fonction :
(ok:font! device police)
Pour exploiter les autres polices de caractères du système, OK propose la fonction :
(ok:enum-fonts device procédure)
La procédure est appelée pour toutes les polices de caractères disponibles dans le système, avec leur nom en argument. Si la fonction retourne #f, l'énumération est terminée. Il est alors possible de créer les polices correspondantes avec la fonction :
(ok:create-font-string nom attribut taille)
L'attribut et la taille ont la même signification qu'avec la fonction ok:create-font. L'argument nom doit être obtenu par la fonction ok:enum-fonts.
Dessins
Avant de commencer, nous allons fabriquer un petit programme créant une fenêtre. La fonction de dessin de cette fenêtre sera modifiée au fur et à mesure pour illustrer les différentes fonctions vues.
; création de la fenêtre
(define f (ok:create-window
           #f       ; parent
           ok:gray  ; couleur de fond
           100      ; x
           100      ; y
           200      ; largeur
           150))    ; hauteur

; fonction de dessin - redéfinie plus bas
(define (refresh)
  #t)

; affectation de la fonction de dessin
(ok:refresh-callback! f refresh)

; affichage de la fenêtre
(ok:show f ok:show:normal)

; gestion des événements
(ok:exec)
Point
La fonction :
(ok:draw-point device x y)
dessine un point à position x, y avec la couleur du trait du device.
La fonction :
(ok:get-point device x y)
retourne une couleur OK correspondant au point situé à la position (x,y) du device. Cette fonction s'applique aussi bien aux bitmaps qu'aux fenêtres. Les valeurs peuvent être positives ou négatives.
Ligne
La fonction :
(ok:draw-line device x y largeur hauteur)
dessine une droite avec la couleur de trait du device entre les points (x,y) et (x+lareur,y+hauteur). Les coordonnées peuvent être positives ou négatives.
le dessin des lignes peut être modifié par un style permettant par exemple de dessiner une ligne en pointillés. La fonction :
(ok:line-style! device largeur style)
modifie le style de tracé de ligne. L'argument largeur détermine la largeur du trait, en pixels, et l'argument style est soit ok:line:solid pour une ligne pleine, soit ok:line:dot pour une ligne dessinée avec des points, ou enfin ok:line:dash pour une ligne dessinée avec des tirets. La fonction ok:line:style appliquée à un device retourne une paire contenant la largeur du tracé, et le style du tracé, dans ce device.
Redéfinissons la fonction refresh de l'exemple donné en introduction :
(define (refresh)
 (ok:line-style! f ok:line:solid 3)
 (ok:draw-line   f 10 10 0 150)
 
 (ok:line-style! f 1 ok:line:dash)
 (ok:draw-line   f 10 10 150 150)
 
 (ok:foreground! f ok:red)
 (ok:line-style! f 4 ok:line:dot)
 (ok:draw-line   f 10 10 150 0))
Nous obtenons alors l'affichage :

Figure 1: Tracé de lignes.
Polylines / Bézier et polygone
La fonction :
(ok:draw-polyline device x y . couples-de-points)
dessine une série de droites avec la couleur de trait du device à partir du point (x,y). Couples-de-points est une série d'entiers représentant les coordonnées des sommets. Les valeurs sont des déplacements par rapport au point initial (x,y).
Redéfinissons la fonction refresh de l'exemple donné en introduction :
(define (refresh)
 (ok:foreground!   f ok:black)
 (ok:background!   f ok:white)
 (ok:draw-polyline f
                   75  75
                    0  50
                   50  50
                   50   0
                   25 -50
                    0   0
                   50   0))
Nous obtenons alors l'affichage :

Figure 2 : Tracé de polygones.
La fonction ok:draw-bezier est équivalente à ok:draw-polyline, mais les points sont utilisés pour dessiner une courbe de bézier. De même, la fonction ok:draw-polygone pourra être utilisée pour dessiner un polygone.
Rectangle
La fonction :
(ok:draw-rectangle device x y largeur hauteur)
est utilisée pour dessiner un rectangle  avec la couleur de trait et de fond du device entre le point (x,y) et le point (x+largeur,y+hauteur). 
Redéfinissons la fonction refresh afin de dessiner un rectangle :
(define (refresh)
 (ok:foreground!    f ok:black)
 (ok:background!    f ok:white)
 (ok:draw-rectangle f 50 30 100 70)
 
 (ok:mode!          f ook:mode:xor)
 (ok:draw-rectangle f 70 60 70 70))
L'utilisation du mode ok:mode:xor permet de dessiner en combinant la couleur existante sur le device et la couleur utilisée pour dessiner le pixel. Ce mode permet d'inverser les couleurs :

Figure 3 : Tracé de rectangle.
Cercle
La fonction suivante permet de dessiner un cercle :
(ok:draw-circle device x y rayon)
est utilisée pour dessiner un cercle  avec la couleur de trait et de fond du device entre le point (x,y) et le point (x+largeur,y+hauteur). 
Redéfinissons la fonction refresh afin de dessiner un cercle :
(define (refresh)
 (ok:foreground! f ok:black)
 (ok:background! f ok:white)
 (ok:draw-circle f 50 50 40)
 
 (ok:foreground! f ok:black)
 (ok:background! f ok:transparent)
 (ok:draw-circle f 100 50 40))
L'utilisation de la couleur transparente pour le fond permet de dessiner un cercle non plein. Nous obtenons l'affichage :

Figure 4 : Tracé de cercles.
Arc
Les cercles sont un cas particulier des arcs qui permettent de dessiner des formes complexes. Un arc est dessiné avec la fonction :
(ok:draw-arc device x y largeur hauteur angle-1 angle-2)
L'arc est dessiné sur le device, avec la couleur de trait et la couleur de fond en cours. L'arc est inscrit dans le rectangle situé entre les points (x,y) et (x+largeur,y+hauteur). L'angle-1 est l'angle en degré multiplié par 100 du départ de l'arc et l'angle-2 est l'angle en degré multiplié par 100 contenant l'arc. Ceci est résumé sur le graphique suivant :

Figure 5 : Valeurs de dessin des arcs.
Les arcs peuvent être refermés en passant par le centre ou directement en reliant les deux sommets extrêmes. Ceci est un style de dessin des arcs que l'on règle en appelant la fonction :
(ok:arc:style! device style)
où style est soit ok:arc:pie (passage par le centre) ou ok:arc:chord (passage par la corde). La fonction ok:arc:style retourne le style en cours dans le device passé en argument.
Le programme suivant utilise les arcs :
(define (refresh)
 (ok:foreground! f ok:black)
 (ok:background! f ok:white)
 
 (ok:arc-style!  f ok:arc:chord)
 (ok:draw-arc    f 10 10 100 100 3000 18000)
 
 (ok:arc-style!  f ok:arc:pie)
 (ok:draw-arc    f 30 50 170 190 3000 105000))
On obtient alors l'affichage :

Figure 6 : Tracé d'arcs passant par la corde ou par le centre.
Texte
La fonction :
(ok:draw-text device x y chaîne . début fin)
dessine une chaîne de caractères sur le device, à la position (x,y). Si début est spécifié, les caractères à partir de début sont pris en compte. Si fin est précisé, seuls les caractères avant fin sont pris en compte. La position à laquelle la chaîne sera dessinée dépend des options d'alignement du device.
Le texte est aligné horizontalement et verticalement avec la fonction :
(ok:text-align! device align-h align-v)
La valeur align-h peut prendre les valeurs ok:align:left (alignement à gauche), ok:align:center (alignement au centre) et ok:align:right (alignement à droite). La valeur align-v prend l'une des valeurs ok:align:top (alignement au sommet), ok:align:midle (alignement au milieu de la hauteur de la ligne), ok:align:baseline (alignement sur la ligne de base) ou ok:align:bottom (alignement au bas de a ligne). La fonction ok:text-align appliquée à un device retourne un paire contenant les valeurs de l'alignement du texte.
La chaîne est dessinée avec la police de caractère en cours dans le device (voir plus bas).
Pour dessiner du texte, on peut écrire par exemple :
(define (refresh)
 (ok:origin! f 100 75)
 (let* ([colors (list
                  ok:black ok:red ok:white 
                 ok:green ok:blue ok:yellow
                 ok:gray ok:light-gray
                 ok:dark-gray)]
        [ncols  (length colors)])
   (do ([i     0 (+ i 20)]
        [color 0 (+ color 1)])
       ((eq? i 360))
    (if (eq? color ncols) (set! color 0))
    (ok:foreground! f (list-ref colors
                                color))
    (ok:angle! f i)
    (ok:draw-text f 20 0 "OpenScheme"))))
On obtient alors l'affichage :

Figure 8 : Tracé de texte.
La fonction :
(ok:text-dim device chaîne)
retourne des informations concernant la chaîne de caractères passée en argument sur le device, en fonction des réglages actifs sur le device. Les informations sont retournées sous la forme d'un vecteur à quatre valeurs : La première valeur est la largeur de la chaîne de caractères, la seconde valeur est la hauteur totale de la chaîne, la troisième est la distance qui sépare le bas de la chaîne de ligne de base et la dernière est la hauteur relative de la chaîne qui dépend des caractères dessinés. Ceci est résumé sur la figure suivante :
   top -----------------------------
                                  ^
           -----------------      |r[1]
             ****          ^      |
            **  **         |      |
            **  **         |r[3]  |
             ***** *       |      |
baseline ------***---      |      |
             *  **  ^      |      |
            **  **  |r[2]  |      |
             ****   v      v      v
   bottom -------------------------
            <---->
             r[0]
  Figure 9 : Signification des valeurs du vecteur retourné par ok:text-dim.
Cette fonction est importante pour effectuer de manière précise des placements de textes.
Flèches
La fonction :
(ok:draw-arrow device x y largeur hauteur a b c)
dessine une flèche sur le device à la position (x,y). Le tracé de la flèche est contrôlé par les valeurs a, b et c, suivant le schéma suivant :

Figure 10 : Contrôle du dessin d'une flèche.

Par exemple, on aura :
(define (refresh)
;                 x   y   w    h  a  b   c
(ok:draw-arrow f 10  10 100    0 30 10  30)
(ok:draw-arrow f 10  30 100    0 30 10   0)
(ok:draw-arrow f 10  50 100    0 30 10 -30)
(ok:draw-arrow f 50 150 100 -100 30 10 10))
Ce qui provoque l'affichage :

Figure 11 : Affichage de flèches.
Copie d'images
La fonction :
(ok:copy src-device 
        x       y
        largeur hauteur
        dest-device
        a       b)
copie la zone rectangulaire comprise entre les points (x,y) et (x+largeur,y+hauteur) du device src-device dans le device dest-device, à la position (a,b). Les devices source et destination peuvent être des bitmaps ou des fenêtres. Cette fonction peut servir, notamment, à la gestion du double-buffering qui permet d'accélérer l'affichage. On pourra aussi l'utiliser pour copier une image bitmap dans une fenêtre.
Dessins de formes
La fonctions:
(ok:draw-frame device 
              x       y
              largeur hauteur
              largeur-bordure
              couleur-haut-gauche
              couleur-bas-droite
              couleur-centre)
dessine un cadre à la position (x,y) jusqu'à (x+largeur,y+hauteur) sur le device. Ce cadre est dessiné avec couleur-haut-gauche sur les bordures en haut et à gauche, couleur-bas-droite sur les bordures en bas et à droite, et couleur-centre au centre. La largeur des bordures est largeur-bordure.
Cette fonction peut être utilisée pour dessiner les boutons graphiques.
La fonction :
(ok:draw-diamond device 
                x       y
                largeur hauteur
                largeur-bordure
                couleur-haut-gauche
                couleur-bas-droite
                couleur-centre)
possède les mêmes arguments que la précédente. Elle dessine un losange inscrit dans le rectangle (x,y,x+largeur,y+hauteur).
La fonction :
(ok:draw-left device 
             x       y
             largeur hauteur
             largeur-bordure
             couleur-haut-gauche
             couleur-bas-droite
             couleur-centre
             a
             b
             c)
dessine une flèche en relief orientée à gauche. La signification des neuf premiers arguments est la même que pour la fonction précédente. La signification des trois derniers arguments est la même que pour la fonction ok:draw-arrow. Il existe aussi les fonctions ok:draw-right, ok:draw-up,et ok:draw-bottom sont utilisées pour dessiner des flèches dans les autres directions.
La prochaine fois
Dans les prochains articles, nous utiliserons la bibliothèque graphique pour concevoir un logiciel de tracé de courbes. Puis nous utiuliserons cette bibliothèque pour construire avec le moteur orienté objet, un ensemble d'objets graphiques, comme les boutons ou les listes. Ceci permettra d'utiliser de manière concrète la programmation orientée objet.
Les auteurs

Dr Guilhem de Wailly, directeur de la société Erian Concept : support, formations, configurations, administration, développements Linux. Environnement OpenScheme.
http://www.erian-concept.com

Pr Fernand Boéri, Professeur à l'Université de Nice - Sophia-Antipolis, membre de l'unité SPORTS du laboratoire I3S, spécialiste en modélisation, évaluation de performances et architectures parallèles.
http://www.i3s.unice.fr
Références
Programmation orientée objet
¨	OMT: 1-Modélisation et conception orientées objet
James Bumbaugh et al.
Masson / Prentice Hall
Scheme
¨	Structure et Interprétation des programmes informatiques
H. Abelson, GJ. Sussman
InterEdition
¨	The Scheme Programming Languages - Ansi Scheme 
R. Kent Dybvig
Prentice Hall
¨	Les langages Lisps -  Christian Queinnec
InterEdition
¨	Programmer avec Scheme - J. Chazarin
Thomson Publishing
¨	Revised4 Report on the Algorithmic Language Scheme
W. Clinger, J. Rees
ftp://ftp.nj.nec.com/pub/kelsey
Environnements Scheme
¨	Scm - A. Jaffer
http://www-swiss.ai.mit.edu/~jaffer
La référence des interprètes Scheme. Très petit, rapide, pour beaucoup de plates-formes, extensible.
¨	PCScheme - Texas Instrument
ftp://cui.unige.ch/public/pcs/pcscheme.exe
Un très bon environnement de programmation Scheme pour DOS, avec éditeur intégré.
¨	DrScheme - Rice University
http://www.cs.rice.edu/CS/PLT/
Environnement Scheme libre très avancé.
¨	ChezScheme - Cadence, Inc
http://www.scheme.com/
Environnement Scheme très performant.
¨	Inlab Scheme - Inlab Software GmbH
http://www.munich.net/inlab/scheme/
Environnement commercial
¨	EdScheme, 3Dscheme - Scheme, Inc
http://www.schemers.com/
Environnement de programmation Scheme pour Windows.
¨	OpenScheme - Erian Concept
http://www.open-scheme.com
Environnement professionnel de programmation Scheme comprenant un interprète, un compilateur et un débogueur symbolique. 
Existe en version libre et commerciale, pour Linux, FreeBSD, Solaris, Windows et BeOS, sur systèmes Intel.