Un article pour le moins original dans cette série sur les couples language / toolkit graphique ce mois-ci. Je vous présente Scheme/GIMP !
J'ai décidé de vous présenter le langage scheme qui, à l'image du LISP, est destiné aux amoureux des parenthèses. Le choix du toolkit s'est arrêté sur GIMP, qui n'est pas un toolkit graphique à proprement parler mais qui permet d'atteindre l'objectif fixé dans cette série d'articles à savoir, (pour ceux qui n'étaient pas là la dernière fois) obtenir une valeur de l'utilisateur et la lui restituer.
Commençons par la partie générale, qu'est ce que scheme ?
Scheme est un langage interprété, orienté objet, à typage dynamique. C'est un langage fonctionnel à l'instar du CAML ou du haskell.
Scheme est un dérivé du LISP et est adapté à l'apprentissage de ce dernier et des techniques de programmation fonctionnelle. On le
retrouve notamment au programme de plusieurs DEUG de la filière mathématiques appliquées.
Que je sois honnête avec vous... Savoir ça ne vous apporte pas grand chose pour lire cet article, mais bon ... Un peu de background ne fait pas de mal.
Nous allons, vous l'avez deviné, écrire un script-fu (tout à fait raisonnable, au demeurant). GIMP nous procure plusieurs outils extrêmement pratiques, pour ne pas dire indispensables pour mener à bien cette activité.
Le scheme est un langage interprété, nous avons donc besoin d'un interpréteur. Le monde est bien fait, il y en a un dans GIMP.
La Procedural DataBase ou Base de Données des Procédures. C'est un outil fourni par GIMP qui recense toutes les procédures (fonctions) scheme pouvant être utilisées dans un script-fu. Elle inclut un moteur de recherche des différentes procédures.
Pour connaître les détails sur une procédure, il suffit de cliquer sur son nom, dans la partie gauche de la fenêtre (en ayant, si besoin, fait une préselection grâce au moteur de recherche intégré). On obtient alors le nom de la procédure ainsi qu'une description, ses paramètres et leurs types, les variables retournées lors d'un appel, une aide à l'utilisation et les informations d'auteur, date de création et copyright.
Pour consulter la Base de données des procédures, dans GIMP, choisissez "Exts/Explorer la base de procédures..."
C'est l'élément indispensable pendant la phase de mise au point de vos scripts. Vous y saisissez des lignes de scheme et le résultat de l'interprétation est affiché au fur et à mesure. La console, vous permet également d'ouvrir la PDB et d'en importer des commandes, grâce au bouton "Parcourir...".
Pour lancer la console script-fu, dans GIMP, choisissez "Exts/script-fu/Console..."
Dans GIMP, toutes les actions que vous pouvez faire à la main sont reproductibles par le biais de scripts. Ces scripts sont écrits en Scheme (ou en Perl, pour les Perl-Fu, mais ce n'est pas le propos ici), ce sont les célèbres script-fu.
Pour écrire des script-fu, nul besoin de connaître toutes les spécificités du Scheme sur le bout des doigts (je n'aurais jamais écrit cet article sinon).
Voyons les bases utiles maintenant, vous pouvez lancer GIMP et la console script-fu, celà vous permettra de tester les exemples ci-dessous.
Un script Scheme est composé de blocs, matérialisés par des paires de parenthèses. Le contenu de ces parenthèses est :
( nom_de_fonction argument_1 argument_2 etc ...)
nom_de_fonction
peut, bien entendu être un opérateur. Essayer de taper ces quelques lignes dans la console script-fu :
(+ 8 9) (+ 8 (* 3 3)) (+ (- 14 6) (* 3 3))
La saisie de ces trois lignes provoque l'apparition du nombre 17 ( 8+9, 8+3*3, 14-6+3*3 ). Le style de notation est l'inverse d'une notation polonaise inversée (ou RPN, ceux qui ont possédé une calculatrice HP savent de quoi je parle), c'est donc une notation polonaise. En effet, dans la notation polonaise inversée les opérateurs sont après les opérandes alors que dans le cas du scheme, ils sont avant.
Pour déclarer une variable locale en scheme, on utilise :
; Les lignes préfixées par ; sont des commentaires en scheme (let ( ; Début des déclarations des variables (var1 valeur1) (var2 valeur2) ; ... ) ; Fin des déclarations ; ici, var1 et var2 existent ) ; ici, var1 et var2 n'existent plus
On écrit let
, suivi des déclarations de variables. Chaque variable est définie dans des parenthèses et
l'ensemble des déclarations est lui aussi placé entre parenthèses.
Après ce bloc de déclarations, on peut écrire du code qui a connaissance des variables déclarées. Quand les variables ne sont
plus nécessaires, on ferme la parenthèse ouverte avant let
.
Exemple: (vous devez le taper sur une seule ligne dans la console)
=> (let ((var1 4)(var2 5)) (+ var1 var2)) 9 => (+ var1 var2) ERROR: unbound variable (errobj var1)
Comme vous le voyez ci-dessus, les variables ainsi déclarées n'existent que dans le bloc comprenant le mot clé let
.
Généralement on utilise let*
plutôt que let
. Cette forme assure en effet que les variables sont déclarées
au fur et à mesure et non toutes en même temps à la fin du bloc de déclarations comme c'est le cas pour let
.
Exemple:
=> (let ((var1 4)(var2 var1)) (+ var1 var2)) ERROR: unbound variable (errobj var1) => (let* ((var1 4)(var2 var1)) (+ var1 var2)) 8
Pour déclarer des variables globales, on utilise set!
mais on ne déclare pas de variables globales,
c'est pas beau les variables globales ;-).
Scheme/GIMP utilise énormément les listes. Or la plupart des paramètres que l'on doit passer aux fonctions sont des éléments de listes. Je vais donc vous montrer comment déclarer une liste, accéder au premier de ses éléments et aux autres.
On définit une liste ainsi (prenons le cas d'une couleur bleu/vert codée en RGB) :
(let* ((ma_couleur '(36 154 160))) ... )
Remarquez l'utilisation de la cote ('
) qui permet de définir une liste. Sans cette dernière, scheme
cherche une fonction nommée 36 pour lui passer comme paramètres 154 et 160 ...
Pour accéder aux éléments d'une liste, on utilise car
et cdr
, qui renvoient respectivement le premier élément
de la liste et la liste amputée de son premier élément. On accède donc aux éléments de la façon détaillée ci-dessous :
=> (set! ma_couleur '(36 154 160)) (36 154 160) => (car ma_couleur) 36 => (car (cdr ma_couleur)) 154 => (car (cdr (cdr ma_couleur))) 160 => (cadr ma_couleur) 154 => (caddr ma_couleur) 160
On en profite pour voir comment définir une variable globale (non, c'est pas beau, mais c'est pratique ...). Une fois cela fait, on a une variable liste ma_couleur
.
(car ma_couleur)
renvoit en toute logique 36, le premier élément. (cdr ma_couleur)
renvoit (154 160), on obtient
donc la valeur 154 en prenant le premier élément de cette liste. (cdr (cdr ma_couleur))
peut être un piège pour les
débutants, on pourrait s'attendre à ce que cette expression représente 160 or elle représente (160), une liste ne contenant qu'un élément.
Si on veut un entier, on doit utiliser (car (cdr (cdr ma_couleur)))
. Ceci est très important comme nous le verrons par la suite.
Pour alléger la syntaxe, on peut remplacer (car (cdr ma_couleur))
et (car (cdr (cdr ma_couleur)))
respectivement par (cadr ma_couleur)
et (caddr ma_couleur)
, comme montré ci-dessus.
Pour définir une fonction en scheme, on utilise le mot clé define
. Une déclaration de fonction utilise la structure
suivante :
( define (nom_de_fonction parametre_1 parametre_2) ; corps de la fonction, instructions )
Exemple :
=> (define (fonc a b) (+ a b)) #<CLOSURE (a b) (+ a b)> => (fonc 5 6) 11
Vous voyez que l'on utilise un nom de fonction comme un opérateur. Le reste se passe de commentaires... en C celà s'écrirait :
#include <stdio.h> int fonc (int a, int b) { return a+b; } int main (void) { printf ("%d\n", fonc (5,6)); }puis,
$ gcc -o fonc fonc.c $ ./fonc 11
A présent nous avons les connaissances en scheme nécessaires pour écrire un premier script. Mais pour ce faire nous avons besoin de connaître quelques procédures de la pdb de GIMP :
gimp-image-new
qui renvoit un identifiant de l'image créée. Cette fonction prend en paramètres la largeur et
la hauteur de l'image à créer ainsi que le "type" de cette dernière, choisi parmi RGB, GRAY ou INDEXED.
gimp-text-fontname
qui renvoit un identifiant de calque. En effet, l'image créée grâce à gimp-image-new
ne contient aucun calque or nous avons besoin d'un claque pour écrire notre texte. Cette fonction prend les paramètres suivants :
"-*-Dragonwick-*-r-*-*-24-*-*-*-p-*-*-*"
. Ne vous inquiétez pas, on récupèrera
une valeur toute faite grâce à l'utilisateur ...gimp-palette-set-foreground
et gimp-palette-set-background
, comme leurs noms l'indiquent,
servent respectivement à modifier les couleurs d'avant et d'arrière plan et prennent donc logiquement comme paramètre
une couleur.gimp-drawable-width
et gimp-drawable-height
renvoient respectivement la largeur ou la hauteur
d'un calque passé en paramètre ou, plus exactement, une liste contenant cette valeur.gimp-image-resize
redimensionne l'image passée en premier paramètre. Les autres paramètres sont la
nouvelle largeur, la nouvelle hauteur et les translations en x et y devant être appliquées à l'ancienne image pour la positionner dans
son nouvel emplacement.gimp-display-new
affiche l'image passée en paramètre.
Ces éléments nous permettront d'écrire notre script "Bonjour Toi en Scheme/GIMP", ce que nous entreprendrons enfin juste après une dernière précision.
Une fois un script écrit pour le GIMP, on doit informer ce dernier de son existence, on utilise à cette fin la procédure
script-fu-register
qui prend en arguments :
<Toolbox>/
si on souhaite la faire apparaître dans le menu "Xtns"
de l'interface principale du GIMP ou <Image>/
si on veut le rendre disponible lors du clic droit sur image. Voir à ce propos le renvoi R1*. Pour nous, ce sera
"<Toolbox>/Xtns/Script-Fu/LinuxMag/Bonjour toi"
, ce qui crée une entrée "LinuxMag" dans le menu "Exts/Script-Fu" du GIMP
(Les chemins doivent être ceux avant traduction, ainsi, pour placer une entrée dans "Exts/Divers", il faut en fait tapez "Exts/Misc")SF-STRING
, pour transmettre une chaîne de caractères au script-fuSF-VALUE
, pour une valeurSF-FONT
, pour une policeSF-COLOR
, pour une couleurSF-IMAGE
) et un claque (SF-DRAWABLE
) comme deux premiers arguments.Tapez ceci dans un fichier bonjour.scm
(par exemple). Pour qu'il soit reconnu par le GIMP vous devez le placer dans un répertoire où ce dernier ira le chercher.
Vous avez deux solutions pour ce faire. Soit vous voulez installer ce script pour tous les utilisateurs et vous le placez dans /usr/share/gimp/1.2/scripts/
(chez moi, il est possible que ce soit différent pour vous. Recherchez un répertoire contenant des fichiers *.scm pour trouver ce répertoire chez vous), soit vous n'êtes pas root et ne pouvez donc pas ajouter de fichier dans ce répertoire et n'installerez alors ce script que pour vous.
Si tel est le cas, placez le fichier dans ~/.gimp-1.2/scripts/
ou équivalent, selon votre version de GIMP.
01: (define (bonjourtoi texte taille police bgcolor coul) 02: (gimp-palette-set-foreground coul) 03: (gimp-palette-set-background bgcolor) 04: (let* ( 05: (img (car (gimp-image-new 256 100 RGB))) 06: (layBonjour (car (gimp-text-fontname img -1 0 0 (string-append "Bonjour " texte) 10 1 taille 0 police))) 07: (larg (car (gimp-drawable-width layBonjour))) 08: (haut (car (gimp-drawable-height layBonjour))) 09: ) 10: (gimp-image-resize img larg haut 0 0) 11: (gimp-display-new img) 12: ) 13: ) 14: 15: (script-fu-register "bonjourtoi" 16: "<Toolbox>/Xtns/Script-Fu/LinuxMag/Bonjour toi" 17: "Bonjour Toi en Scheme/GIMP" 18: "Xavier Garreau" 19: "Xavier Garreau" 20: "2003-2004" 21: "" 22: SF-STRING "Votre prénom" "Zazou" 23: SF-VALUE "Hauteur du texte (pixels)" "50" 24: SF-FONT "La police" "" 25: SF-COLOR "Couleur du fond" '(145 160 56) 26: SF-COLOR "Couleur du texte" '(56 145 160) 27: )
Un script-fu est un ensemble de procédures scheme définies dans un fichier. Une de ces procédures est déclarée dans GIMP
par le biais de script-fu-register
et devient alors une procédure GIMP mais cette procédure peut faire appel à d'autres
fonctions scheme, ce n'est toutefois pas le cas dans cet exemple simple.
On commence naturellement par définir une fonction bonjourtoi
prenant en paramètres le texte à écrire, la taille et la police à utiliser pour ce faire ainsi que deux couleurs pour le texte et le fond.
On change les couleurs de la palette (on pourrait d'abord sauvegarder les actuelles en utilisant les procédures gimp-palette-get-foreground
et gimp-palette-get-background
pour les rétablir à la fin du script).
Ce seront les couleurs utilisées lors des actions suivantes.
On commence un bloc let*
car on a besoin de quelques variables.
On déclare ainsi img
, notre nouvelle image, de type RGB, occupant 256x100 pixels.
Ligne 6, on crée le calque contenant le texte. On prévoit une bordure de 10 pixels autour du texte, on active l'anti-aliasing.. On stocke (une référence à) la couche contenant le texte dans la variable layBonjour
pour utilisation lignes 7 et 8, dans lesquelles on en récupère la largeur et la hauteur,
stockées respectivement dans les variables larg
et haut
.
Toutes les valeurs de retour sont des listes, pour obtenir des valeurs, on doit utiliser car
.
Ligne 6, on note l'utilisation de la fonction de concaténation de chaînes de caractères en scheme, j'ai nommé
string-append
. C'est elle qui nous permet de coller le prénom de l'utilisateur à "Bonjour ".
Ligne 10, on redimensionne notre image à la taille nécessaire pour qu'elle soit de la même taille que le calque
contenant le texte. Ce dernier est automatiquement créé de sorte que tout le texte soit visible par la procédure
gimp-text-fontname
.
Enfin, ligne 11, on affiche l'image.
On appelle lignes 13 à 27 la procédure script-fu-register
pour enregistrer la procédure bonjourtoi
dans les script-fu de GIMP. On choisit de placer le script-fu dans un sous-menu LinuxMag de l'interface principale du GIMP
(enfin, plus exactement dans un sous menu de Xtns/Script-Fu).
La description et les informations de date et copyright ne nécessitent pas d'explication.
On ne spécifie pas de type d'image puisqu'il ne s'agit pas d'un script d'image.
Viennent ensuite lignes 22 à 26 la déclaration des paramètres d'entrée du script-fu. Les types de ces derniers ont déjà été
expliqués. Ils sont suivis de la description qui apparaît dans l'interface utilisateur et d'une valeur par défaut.
Il est très important que les paramètres du script-fu dans le define
et dans le script-fu-register
soient dans le même ordre car ils sont associés les uns aux autres dans l'ordre de leur apparition dans le script.
"Annuler l'annulation" : Une fois votre image apparue, si vous annulez plusieurs fois (par Ctrl+Z, par exemple)
vous pouvez revenir à une image vide. Généralement, on empêche celà en encadrant les instructions du script
par des appels aux procédures gimp-image-undo-disable
et gimp-image-undo-enable
On remplace donc ce bloc :
(gimp-image-resize img larg haut 0 0) (gimp-display-new img)par
(gimp-image-undo-disable img) (gimp-image-resize img larg haut 0 0) (gimp-image-undo-enable img) (gimp-display-new img)
Remplir le fond : vous avez remarqué que nous n'utilisions pas le paramètre bgcolor (non ?). Il est temps de s'en servir à présent. On va ajouter un fond à notre image. Nous allons pour cela créer un nouveau calque et le placer tout en bas de la pile des claques qui composent notre image afin que le texte soit visible, au-dessus.
Voici le corps de la fonction, modifié pour ajouter un calque, on aurait pu écrire une procédure pour ce faire.
J'ai préféré ajouter un bloc let*
.
(define (bonjourtoi texte taille police bgcolor coul) (gimp-palette-set-foreground coul) (gimp-palette-set-background bgcolor) (let* ( (img (car (gimp-image-new 256 taille RGB))) (layBonjour (car (gimp-text-fontname img -1 0 0 (string-append "Bonjour " texte) 10 1 taille 0 police))) (larg (car (gimp-drawable-width layBonjour))) (haut (car (gimp-drawable-height layBonjour))) ) (gimp-image-undo-disable img) (gimp-image-resize img larg haut 0 0) (let* ( (layBG (car (gimp-layer-new img larg haut RGBA_IMAGE "Fond" 100 NORMAL))) ) (gimp-drawable-fill layBG BG-IMAGE-FILL) (gimp-image-add-layer img layBG -1) (gimp-image-lower-layer-to-bottom img layBG) ) (gimp-image-undo-enable img) (gimp-display-new img) ) )
On y découvre de nouvelles procédures :
gimp-layer-new
crée un nouveau calque. On lui passe en paramètre l'image qui contiendra ce calque,
la largeur et la hauteur, le type de calque, son nom, son opacité et le mode de mélange (C'est bien NORMAL et non
NORMAL_MODE comme le dit la pdb mais celà varie peut être d'une version à l'autre de GIMP).gimp-drawable-fill
sert à remplir un calque. On lui passe en paramètre un calque et un type de
remplissage, ici, on remplit avec la couleur de fond, on choisit donc BG-IMAGE-FILL
(ici encore la pdb ne
donne pas la même orthographe pour ce paramètre ...).gimp-image-add-layer
sert à ajouter un calque à une image. En effet, gimp-layer-new
ne le
fait pas automatiquement. On lui passe en paramètre une image, un calque et une position pour le calque. -1 indique que l'on
souhaite placer le calque au dessus des autres. On abaisse le calque ensuite.gimp-image-lower-layer-to-bottom
nous sert à "envoyer le fond à l'arrière plan". Les paramètres sont l'image
et le calque à abaisser.Réutiliser le travail des autres
Tout d'abord, on devrait préfixer le nom de notre procédure par script-fu-
, pour qu'il se retrouve avec tous ses petits copains les Script-Fu
dans la pdb. C'est à dire remplacer (script-fu-register "bonjourtoi"
par (script-fu-register "script-fu-bonjourtoi"
et (define (bonjourtoi texte taille police bgcolor coul)
par (define (script-fu-bonjourtoi texte taille police bgcolor coul)
.
Vous remarquez (maintenant qu'ils sont regroupés) que tous les script-fu sont des procédures (presque) comme les autres dans la pdb.
On peut donc appeler des script-fu dans d'autres script-fu (ne tenez pas compte du premier paramètre de type INT32
dans ce cas) ... Vous pouvez par exemple ajouter dans le script original
(script-fu-basic1-logo-alpha img layBonjour bgcolor coul)
juste avant (gimp-display-new img)
pour obtenir le résultat suivant :
drawable vs layer : une précision s'impose en effet, un drawable n'est pas équivalent à un calque. Il peut également s'agir d'un masque de calque.
Tests de vos scripts : n'oubliez pas de redémarrer GIMP pour tester les modifications apportées à vos scripts.
En deux mots : "comme gimp". Partout où GIMP est installé et fonctionne, votre script peut fonctionner aussi. Il suffit de le placer dans le bon répertoire. Ce dernier est facile à trouver, il s'appelle "scripts", est souvent dans un répertoire "share" et contient plein de fichiers .scm ...
Voilà, j'espère vous avoir donné envie de visiter un peu la pdb de GIMP et vous avoir fait aimer les parenthèses grâce à Scheme. Si vous voulez
pousser plus avant votre "expérience script-fu", lisez ceux qui existent, les sources sont disponibles.
Je continuerai cette série "langage/kit graphique" par un couple plus "standard" la prochaine fois, d'ici là bon ski pour les veinards
qui partent en vacances !
@+
Xavier Garreau - <xavier@xgarreau.org>
http://www.xgarreau.org/
Ingénieur de recherche PRIM'TIME TECHNOLOGY
http://www.prim-time.fr/
Membre fondateur du ROCHELUG
http://www.rochelug.org/
a+