Sites


Processing

Les listes

On peut mettre de nombreux genres de choses dans une variable : un chiffre, un chiffre à virgule, la phrase d'un texte, voire même toute une image ou tout un morceau de son. Mais bien que les variables puissent théoriquement contenir presque tout type de valeur, elles ne peuvent contenir qu'une seule de ces valeurs à la fois. Dans certains cas, il serait pratique d'avoir plusieurs choses regroupées, au moins du même genre, dans une seule entité. C'est pour cette raison qu'un genre très particulier de variables à été inventé, les listes.

Les listes permettent de stocker un nombre fixé d'avance de données ou d'objets dans une même variable. Au lieu de créer 20 variables pour stocker 20 valeurs différentes d'un même genre, nous pouvons créer un seul contenant pour ces 20 valeurs et y accéder une par une via cette seule et unique variable.

Créer une liste

Si nous utilisons des éléments dits fondamentaux, comme les chiffres, il est très facile de fabriquer une liste :

int[] numbers = {90,150,30};

Le signe du double crochet signifie qu'il ne s'agit plus d'une variable avec un seul entier à l'intérieur, mais d'une liste d'entiers avec plusieurs valeurs à l'intérieur. Ensuite nous remplissons cette liste dénommée numbers (nous aurions très bien pu lui donner un autre nom) avec les valeurs notées entre les accolades.

L'ordinateur créera assez d'emplacements dans la mémoire et placera chacune des valeurs dans les cases correspondantes :

illustration_remplissage_liste.png

C'est d'ailleurs pour cette raison qu'il faut indiquer le mot int, car Processing a besoin de connaître la taille de chaque case de la liste (dans ce cas précis, nous lui indiquons à l'aide de cette instruction qu'il s'agit de nombres entiers). S'il s'agissait d'images, comme on le verra plus loin, chaque case de la liste aurait besoin de davantage de place.

Notez que sur certains claviers francophones ou sur certaines plates-formes, il est parfois difficile de localiser le crochet d'ouverture « [ » et de fermeture « ] ». Sur un clavier français d'ordinateur Apple, par exemple, il faut appuyer en même temps sur les 3 touches {alt} + {maj} + ( pour le crochet d'ouverture, et {alt} + {maj} + ) pour le crochet de fermeture. Par ailleurs, on doit taper {alt} + ( pour l'accolade d'ouverture et {alt} + ( pour l'accolade de fermeture.

Créer une liste vide

Bien qu'elle soit lisible, cette méthode de création directe ne marchera pas avec des éléments comme les sons ou les images. À cause de cette limitation, vous risquez d'utiliser rarement cette méthode.

Déclarer une liste se fera donc le plus souvent de la manière suivante : type[] nomDeLaListe = new type[NOMBRE D'ÉLÉMENTS]. L'exemple ci-dessous crée une liste de trois nombres entiers. Attention ! Au moment où la liste dénommée numbers est créée, celle-ci est constituée d'une suite de cases vides qui ne contiennent aucune valeur.

int[] numbers = new int[3]; 

Le chiffre 3 entre accolades indique que nous créons une liste de 3 cases actuellement vides.

illustration_creation_liste.png

Remplir la liste

Placer des valeurs dans une liste fonctionne de la même manière qu'assigner une valeur à une variable. Il faut en plus préciser à quelle position de la liste on ajoute la valeur.

numbers[0] = 90;
numbers[1] = 150;
numbers[2] = 30;

La première position de la liste commençant par 0, si nous créons une liste de 3 valeurs, les cases les contenant seront donc numérotées de 0 à 2. On passe d'une position de la liste à une autre en l'incrémentant de 1.

Utiliser le contenu d'une liste

Utiliser l'élément d'une liste est similaire également à l'utilisation d'une variable. Il faut juste préciser la position de l'élément en question :

println( numbers[0] );

A nouveau, notez que l'ordinateur commence à compter à la position 0 et non pas 1. Si nous demandons la valeur numbers[2], Processing nous donnera la valeur à la troisième position et non pas à la deuxième. Un bon moyen pour se rappeler cette particularité,  c'est de considérer que Processing compte de la manière suivante : zérotième, premier, deuxième, troisième, etc. La zérotième valeur concerne la valeur au début de la liste.

Dans l'exemple ci-dessous nous utilisons une boucle pour calculer la somme de tous les éléments de la liste déclarée précédemment :

int[] numbers = new int[3];

numbers[0] = 90;
numbers[1] = 150;
numbers[2] = 30;

int somme = 0;

for (int i = 0; i < 3; i++) {
    somme = somme + numbers[i];
}

println(somme);

Le programme devrait afficher le résultat suivant dans votre console, tout en bas de la fenêtre Processing :

processing_listes_console_1.png

Quelques éléments supplémentaires d'explication pour mieux comprendre le fonctionnement de ce programme. Tout d'abord, nous créons une variable qui contiendra la somme de toutes les valeurs. Elle débute à zéro.

Ensuite, nous enclenchons une boucle qui se répétera 3 fois, en additionnant à chaque fois la prochaine valeur à la somme. Ici nous pouvons voir la relation étroite qui existe entre une liste et une boucle for(). La valeur i permet de passer une à une dans chacune des valeurs de la liste, en commençant avec la  position zéro, ensuite la position 1, et enfin la position 2.

Si vous vous mettez à analyser le code de programmes consultables en ligne, par exemple dans les sketchs librement mis à disposition sur OpenProcessing, vous verrez beaucoup de code avec « quelqueChose[i] ».

Une suite d'images

Un des usages les plus pratiques des listes concerne l'importation de médias dans Processing. Imaginons que nous voulons importer cinq images dans Processing destinées ensuite à être utilisées dans un sketch. Sans employer les listes, il faudrait écrire quelque chose comme ceci :

PImage photo1;
photo1 = loadImage("photo_1.png");
image(photo1,0,0);

PImage photo2;
photo2 = loadImage("photo_2.png");
image(photo2,50,0);

PImage photo3;
photo3 = loadImage("photo_3.png");
image(photo3,100,0);

PImage photo4;
photo4 = loadImage("photo_4.png");
image(photo4,150,0);

PImage photo5;
photo5 = loadImage("photo_5.png");
image(photo5,200,0);

Certes, cet exemple reste encore relativement gérable dans la mesure où vous faites appel à seulement 5 images. Cela étant, les occasions d'erreurs de saisie sont nombreuses. En écrivant ces quelques lignes pour ce manuel, nous-mêmes avons plusieurs fois effectué des erreurs de frappe, notamment en oubliant de changer un chiffre que nous venions de copier d'une ligne précédente !

Une meilleure façon d'écrire cet exemple serait d'utiliser des listes :

PImage[] images = new PImage[5];

images[0] = loadImage("image_0.png");
images[1] = loadImage("image_1.png");
images[2] = loadImage("image_2.png");
images[3] = loadImage("image_3.png");
images[4] = loadImage("image_4.png");

image( images[0], 0, 0);
image( images[1], 50, 0);
image( images[2], 100, 0);
image( images[3], 150, 0);
image( images[4], 200, 0);

En utilisant une liste, on peut mettre toutes nos images dans une seule variable qui doit être initialisée qu'une seule fois : PImage[] images = new PImage[5]. Ensuite, il suffit de remplir chaque valeur de fichier pour chacun des emplacements dans la liste.

Mais même cette écriture est trop longue. Comment allez-vous faire, par exemple, lorsque vous aurez 200 images, voire 2.000 ? Allez-vous vraiment écrire 200 fois toutes ces lignes ? Et comment ferez-vous lorsque vous voudrez changer le nom des fichiers importés ?

Voici la meilleure manière d'importer une suite d'images :

size(500,500);

PImage[] images = new PImage[20];

for(int i=0; i<images.size(); i++) {
  images[i] = loadImage("image_" + i + ".png");
  image( images[i], random(width), random(height) );
}

En utilisant une répétition for(), nous pouvons désormais importer autant d'images que nous voulons.

Tout d'abord, nous créons la liste d'images. Il s'agit au départ d'une liste vide :

PImage[] images = new PImage[20];

Ensuite nous récupérons la longueur de cette liste en demandant à la liste elle-même combien d'éléments elle contient via images.size() et utilisons cette longueur dans la répétition for().

for(int i=0; i<images.size(); i++)

Nous pouvons même automatiser l'importation des images en utilisant la valeur i pour composer le nom du fichier des images dans le dossier data. En utilisant le signe « + », on peut concaténer deux mots ensemble, ce qui veut dire que nous allons assembler deux mots/signes en un seul message.

images[i] = loadImage("image_" + i + ".png");

En concaténant les mots « image_ », la valeur de la variable i, et « .png », nous obtenons un message qui ressemblerait à quelque chose comme « image_42.png ». De cette manière, nous pouvons utiliser une seule ligne de code pour faire rentrer autant d'images que nous voulons. Si la variable i contient la valeur de 9, le nom du fichier importé sera image_9.png. Si la variable i contient la valeur 101, le nom du fichier importé sera image_101.png.

Pour que cette technique marche, il faut juste préparer au préalable des fichiers d'images dans votre dossier data. Voici, par exemple, un dossier data contenant 20 fichiers d'images :

fenetre_importer_20_images.png

Une fois chacune de ces images importées dans notre programme, nous dessinons l'image quelque part dans le sketch en écrivant :

image( images[i], random(width), random(height) );

A l'aide de l'instruction random, cette ligne de code affiche une image (0, 1, 2, 3, ...) qui sera placée à chaque fois de façon aléatoire sur l'axe x et sur l'axe y de l'espace de dessin. Les deux valeurs width et height permettent de connaître automatiquement la taille de la fenêtre de visualisation utilisée par le sketch. En faisant appel à elles, on précise les limites verticales et horizontales maximales où doivent se positionner les images, l'instruction random() ayant pour fonction de générer un nombre au hasard ne dépassant pas la valeur mentionnée entre ses parenthèses.

Une suite de pixels

Il se trouve que vous connaissiez déjà les listes et que vous vous en serviez déjà sans le savoir. Par exemple, si vous avez lu le chapitre sur les images vous savez déjà qu'une variable de type PImage ne contient pas une seule valeur mais plusieurs, à moins que votre image ne fasse qu'un pixel de large et un pixel de haut. À part cette exception rare, votre image contient un ensemble de valeurs qui représentent l'ensemble des pixels qui doivent former l'image. C'est cet ensemble qui s'appelle une liste.

Vous pouvez accéder directement aux pixels d'une image en demandant un accès direct à sa sous-variable nommée « pixels ».

Imaginons que nous avons au départ l'image d'une mystérieuse dame dans un fichier nommé lhooq.png

lhooq.png

Si nous importons cette image dans Processing, nous pouvons l'utiliser pour récupérer la couleur d'un pixel en particulier en entrant à l'intérieur de sa liste de couleurs. Cette liste s'appelle pixels[]

mona_pixel2.tiff

size(512,256);

PImage lhooq;
lhooq = loadImage("lhooq.png");
image(lhooq,0,0);

int x = 119;
int y = 119;

int index = x + (y * lhooq.width);
color c = lhooq.pixels[index];

noStroke();
fill(c);
rect(256,0,256,256);

stroke(0);
line(x-5,y,x+5,y);
line(x,y-5,x,y+5);

Les deux lignes les plus importantes sont soulignées en gras dans le code :

int index = x + (y * lhooq.width);
color c = lhooq.pixels[index];

Nous vous rappelons qu'une image n'est rien d'autre qu'une liste de pixels,  une image étant notamment composée non pas d'une mais de plusieurs couleurs. Dans Processing, les variables de type PImage servent d'ailleurs à stocker les valeurs de ces pixels dans une longue liste linéaire.

illustration_liste_pixels_lineaire_1.png

Ce que nous voyons comme une liste à deux {x,y} dimensions, PImage la voit en une seule dimension. Si nous voulons trouver une valeur à la position {2,1}, en réalité il faut indiquer que nous voulons la position 17 de l'image. En effet, entre un point sur une ligne et le point correspondant sur la prochaine ligne, il existe 16 pixels. Autrement dit, PImage doit compter 16 pixels chaque fois qu'il veut descendre une ligne.

C'est pour cette raison que la formule pour identifier un pixel dans une image s'écrit {x + (largeur image} * y}. Pour chaque ligne y, il existe une largeur de valeurs x qu'il faut dépasser.

Colorier les pixels dans une image

Nous pouvons aller encore plus loin dans l'utilisation du principe de listes appliqué à la description interne d'une image dans Processing. Il est possible en effet de créer nos propres variables de type PImage et d'assigner à ses pixels des couleurs, à l'aide de variables de type color. Les pixels de l'image sont accessibles au moyen d'un chiffre numéroté à partir de zéro, comme dans n'importe quelle liste.

illustration_liste_p_image02

Dans cet exemple, on dessine une grille de trois pixels par trois. Nous allons dessiner cette image plus grande en l'étirant à 80 x 80 pixels afin de mieux la voir.

PImage img = createImage(3, 3, ARGB);
img.loadPixels();
img.pixels[0] = color(255, 0, 0);
img.pixels[1] = color(0, 255, 0);
img.pixels[2] = color(0, 0, 255);
img.pixels[3] = color(255, 0, 255);
img.pixels[4] = color(255, 255, 0);
img.pixels[5] = color(0, 255, 255);
img.pixels[6] = color(0, 0, 0);
img.pixels[7] = color(127, 127, 127, 255);
img.pixels[8] = color(255, 255, 255, 0);
img.updatePixels();
image(img, 10, 10, 80, 80);

Notez qu'il faut indiquer à Processing que nous allons modifier une image via loadPixels() et que nous avons terminé de modifier l'image via updatePixels(). En l'absence de ces instructions, on risque de ne pas voir les résultats de notre modification dans la liste.

Notez également que vous pourriez aussi réaliser ce genre de peinture à numéro en utilisant des répétitions.

Les applications créatives des listes sont multiples. Il ne vous reste plus qu'à laisser le champ libre à votre imagination.

Il y a une erreur de communication avec le serveur Booktype. Nous ne savons pas actuellement où est le problème.

Vous devriez rafraîchir la page.