Langage / Toolkit graphique : Scheme/GIMP

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.

Introduction

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.

Les outils

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é.

Interpréteur

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.

PDB

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..."

La console script-fu

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..."

Scheme et GIMP

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.

Syntaxe

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.

Variables locales

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 ;-).

Listes

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.

Fonctions

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

Procédures GIMP

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 :


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.

Ajout au GIMP

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 :

Le script commenté

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.

Fignolage et détails

"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 :

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 :

Scheme/GIMP scshot

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.

Portabilité

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 ...

Fin

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 !

Scheme/GIMP scshot

@+

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/

Liens & Références :

a+

Auteur : Xavier GARREAU
Modifié le 10.09.2004