Profitez-en, après celui là c'est fini

Processing, quatorzième cours

décembre 10th, 2008 Posted in Processing

Après les variables, les boucles et les objets, décompressons un peu avec les images.
Processing est capable de charger un fichier graphique en mémoire et de le manipuler de diverses façons notamment en l’affichant, en obtenant la valeur colorée d’un point précis ou en dessinant sur cette image. Les principes sont simples mais il y a une étape à ne pas négliger, celle du chargement du fichier graphique dans le dossier de données externes de l’animation. Lorsque l’on enregistre une animation, celle-ci se place dans un dossier qui porte le même nom. Dans ce dossier se trouve parfois un sous-dossier nommé « data » où seront stockées toutes les ressources que l’utilisateur souhaite utiliser : images, sons, mais aussi typographies « rastérisées » (sujet qui ne nous occupera pas tout de suite). Deux méthodes permettent de manipuler un fichier graphique.
Soit on crée le sous-dossier « data » pour y placer le fichier en question, soit, plus facile, en passant par la commande Sketch>Add File. Lorsque l’on sélectionne un fichier avec cette seconde méthode, Processing crée une copie du fichier à ajouter et place celle-ci dans le dossier « data » de l’animation, dossier que le programme crée s’il n’existe pas.

Pour que tout se déroule bien, il faut respecter quelques règles très simples.

  • Avant d’ajouter un fichier à l’animation, il faut avoir pris soin d’enregistrer cette animation. Si on ne le fait pas, le programme range l’image dans un dossier temporaire.
  • Si la même image est utilisée par un autre programme, il faut avoir enregistré une copie de l’image dans le dossier de ressources de chaque animation.
  • L’image doit être dans un format lisible par Processing. Il y en a quatre pour l’instant, à savoir les formats gif, jpeg, tga et png. Ces images peuvent être, selon les formats, en RGB, en couleurs indexées ou en RGB avec couche Alpha.
  • Le chargement de l’image se fait de préférence au début de l’animation, dans la fonction setup().

Lorsque l’on manipule une image en mémoire, Processing la stocke sous la forme d’une variable de type PImage. (qui signifie Processing Image). Voici un exemple typique de déclaration et de chargement d’une image :

PImage monImage = loadImage("paysage.png");

La première partie, PImage monImage sert à déclarer une donnée PImage nommée monImage. Dans le même mouvement, on remplit cette variable monImage avec la commande loadImage(). En écrivant loadImage(un nom de fichier), on demande à Processing de se rendre dans le sous-dossier « data » pour y chercher le fichier en question et pour le charger en mémoire. Nous pouvons ensuite utiliser cette image à notre guise et accéder à ses propriétés1 . Par exemple en écrivant print(monImage.width);, nous obtiendrons la largeur de l’image (sous réserve que celle-ci ait effectivement été chargée). Le fait de connaître la largeur et la hauteur d’une image peut nous permettre, par exemple, de caler la taille de la fenêtre de notre application sur le format de l’image, comme ceci : size(monImage.width, monImage.height);.

Faisons un premier essai de chargement et d’affichage d’une image. Mon image se nomme « image_quend.jpg » (pour l’anecdote, il s’agit d’une photographie de Quend-Plage en Baie-de-Somme). Je vais tout d’abord charger l’image en mémoire, régler la taille de la fenêtre d’affichage sur la taille de l’image, puis afficher cette image, ce qui me donnera précisément ce résultat :

// déclaration de l'image nommée img
PImage img;

void setup(){
 // chargement du fichier externe image_quend.jpg dans img
  img = loadImage("image_quend.jpg");
// réglage de la taille de la fenêtre sur les dimensions de img
  size(img.width, img.height);
// affichage de l'image img à partir du point (0,0).
  image(img,0,0);
}

La dernière commande, image(), sert à afficher une image à un endroit précis de la scène selon cette syntaxe : image( nom_de_l'image, x, y); ou image( nom_de_l'image, x, y, largeur, hauteur);, où nom_de_l'image est le nom de la variable de type PImage dans laquelle a été chargée l’image ; x est l’abscisse du point supérieur gauche de l’image ; y est son ordonnée ; les valeurs facultatives largeur et hauteur permettent de régler la largeur et la hauteur à laquelle l’image est reproduite. Dans la variante qui suit, l’image est copiée quatre fois sur la scène à la moitié de sa largeur (img.width/2) et de sa hauteur (img.height/2) et à quatre coordonnées différentes : (0,0), (la moitié de la largeur, 0), (la moitié de la largeur, la moitié de la hauteur), (0, la poitié de la hauteur).

PImage img;
void setup(){
  img = loadImage("image_quend.jpg");
  size(img.width, img.height);
  image(img,0,0,img.width/2,img.height/2);
  image(img,img.width/2,0,img.width/2,img.height/2);
  image(img,0,img.height/2,img.width/2,img.height/2);
  image(img,img.width/2,img.height/2,img.width/2,img.height/2);
}

Et voici le résultat :

Ici le rapport entre hauteur et hauteur reste homothétique, mais il peut ne pas l’être.

Dans l’exemple qui suit, la fenêtre fait 100×100 pixels et l’image est déformée pour avoir une largeur de 100 pixels et une hauteur de 100 pixels :

PImage img;

void setup(){
 img = loadImage("image_quend.jpg");
 size(100,100);
 image(img,0,0,100,100);
}

La manipulation d’images sous Processing permet aussi d’obtenir la valeur colorée d’un pixel d’une image. Pour ce faire, on utilise la commande get(x,y);, où x et y sont l’abscisse et l’ordonnée du point dont on veut connaître la valeur. Par exemple pour connaître la valeur colorée du point (30,40) de l’image img, il me suffit d’écrire :

color c = img.get(30,40);

Dans ce cas précis, la couleur obtenue est stockée dans une variable de type color nommée c. Je peux ensuite utiliser cette couleur comme nous l’avons vu dans de précédents chapitres, par exemple avec background(c); ou fill(c); ou encore stroke(c);. Je peux aussi décomposer la couleur en extrayant sa valeur dans les rouges, dans les verts ou dans les bleux (respectivement : red(c), green(c) et blue(c)) ou encore en vérifiant sa luminosité (brightness(c)), etc.
La fonction get(x,y) est donc symétrique à point(x,y), à cette différence près qu’avec la fonction get(), les coordonnées du point doivent impérativement être des nombres de type entier (sans virgule). On peut donc écrire get(10,10) mais pas get(10.5,10.9).
Si l’on ne spécifie pas l’image que l’on veut manipuler (par exemple si l’on écrit get(10,10) et non monImage.get(10,10), c’est toute la fenêtre de l’application qui est prise en compte.

Passons à un cas pratique. Dans le programme qui suit, nous allons vérifier la couleur d’un point précis de l’image tous les dix pixels. Puis nous allons vérifier la luminosité de ce point (sur une échelle de 0 à 255) et transformer celle-ci en une valeur allant de 0 à 10 par l’utilisation de la fameuse règle de trois2 .
Ensuite, un rectangle est dessiné sur la scène avec la couleur du point testé, à l’emplacement de ce point et avec une taille correspondant à la valeur de 1 à 10 qui a été calculée. La boucle itérative (for(int a=0;a<…) sert à parcourir successivement les points de l’image.

PImage monImage;
noStroke();smooth();
background(0);
monImage = loadImage("image_quend.jpg");
size(monImage.width,monImage.height);
for(int a=0;a<width;a+=10){
 for(int b=0;b<height;b+=10){
   color c=monImage.get(a,b);
   fill(c);
   float t=( brightness(c)/255.0)*10;
   rect(a,b,t,t);
 }
}

Voilà l’image qui résulte de ces calculs :

On peut imaginer de nombreuses manipulations avec la commande get() et les commandes de dessin (point(), rect(), ellipse(), etc.). On peut en fait facilement créer des filtres de traitement d’image comme le flou, le « virage » vers une couleur, l’ajout de bruit, etc.

Parmi les autres commandes à connaître, mentionnons copy() et blend().
Ces deux commandes sont en apparence assez similaires à la commande image() que nous avons vu au début de l’article mais elles permettent d’extraire une partie précise d’une image et de la copier à des coordonnées et dans une taille précise de l’image. La syntaxe de copy() est la suivante :

copy(monImage, x, y, largeur, hauteur, dx, dy, largeur_destination, hauteur_destination);

x et y sont les coordonnées du point supérieur gauche de la portion d’image que l’on veut copier. Les dimensions de cette portion d’image sont largeur et hauteur. dx et dy sont les coordonnées auxquelles va être copiée l’image et largeur_destination et hauteur_destination sont la largeur et la hauteur du rectangle dans lequel va être copiée la portion d’image. Si le paramètre monImage est omis, la commande s’appliquera à la fenêtre de Processing.
Quand à la commande blend(), elle s’utilise comme suit :

blend(monImage, x, y, largeur, hauteur,  dx, dy, largeur_destination, hauteur_destination, MODE);

Sa syntaxe et sont fonctionnement sont identiques à ceux de la commande précédente si ce n’est qu’un dernier paramètre est ajouté, le « mode ». Ce mode est un mode de superposition classique (identique à ce que propose Photoshop ou à ce que l’on appelle les « encres » dans Director par exemple) qui peut être ADD, SUBTRACT, DARKEST, LIGHTESTBLENDDIFFERENCE, EXCLUSIONMULTIPLYSCREEN, OVERLAYHARD_LIGHT,  SOFT_LIGHT, DODGE ou BURN (toujours écrit de cette manière, en majuscules).

Cette nouvelle image a été obtenue avec le programme suivant :

PImage monImage;
size(524,100); background(255,0,0);
monImage = loadImage("image_quend.jpg");
blend(monImage, 0, 0, 100, 100,  20, 10, 80, 80, BLEND);
blend(monImage, 0, 0, 100, 100,  120, 10, 80, 80, ADD);
blend(monImage, 0, 0, 100, 100,  220, 10, 80, 80, SUBTRACT);
blend(monImage, 0, 0, 100, 100,  320, 10, 80, 80, LIGHTEST);
blend(monImage, 0, 0, 100, 100,  420, 10, 80, 80, DARKEST);

Tout d’abord, le fond de la fenêtre est « peint » en rouge (background(255,0,0)) puis les 100×100 premiers pixels de notre image de départ sont copiés et rétrécis à la taille de 80×80 pixels. Ils sont appliqués sur notre fond rouge avec plusieurs modes : BLEND, ADD, SUBTRACT, LIGHTEST et DARKEST.

Il existe un moyen alternatif extrêmement performant pour manipuler les images en agissant directement sur la liste des pixels de l’image. Pour cela on utilise les fonctions loadPixels(), updatePixels() et le tableau d’entiers pixels[]. Nous ne verrons pas cela aujourd’hui.

Pour finir, signalons que l’on peut aussi charger et afficher dans Processing des images vectorielles3 au format SVG (facile à créer avec Illustrator par exemple) avec les commande loadShape() et shape(). Nous ne verrons pas cela aujourd’hui non plus.

  1. Si l’on veut empêcher le programme de s’exécuter tant que l’image n’est pas chargée (ce qui est pratique sur Internet par exemple), on peut remplacer loadImage() par requestImage() []
  2. Pour transformer une valeur comprise entre 0 et 255 en une valeur allant de 0 à 10, la formule est : valeur sur 255 divisée par 255.0 puis multipliée par 10. Par exemple (255/255.0)*10 = 10 ; (0/255.0)*10 = 0 ; (128/255.0)*10 = 5 ; etc. []
  3. Une image vectorielle n’est pas un canevas de points comme une image bitmap mais contient la description géométrique de formes []
  1. 13 Responses to “Processing, quatorzième cours”

  2. By taclab on Août 6, 2009

    Merci encore pour ces tutoriels.

    Je pense qu’il y a une petite erreur dans celui-ci.
    Ne faut il pas remplacer :
    PImage monImage = loadFile(« paysage.png »);
    par
    PImage monImage = loadImage (« paysage.png »);

  3. By Jean-no on Août 9, 2009

    @taclab: si si, merci, c’est corrigé (depuis le web café d’une ile perdue de Croatie)

  4. By Philippe FORMET on Fév 13, 2016

    Bonjour,

    J’aimerais savoir comment enregistrer une image drée sous processing en svg.

    en début de sketch je mets :
    import processing.svg.*

    et comme commande d’enregistement:
    saveFrame(« ####.svg »)

    l’image est enregistrée avec l’extension
    .svg.tiff

    donc je n’ai pas réellemnet une image svg?

  5. By Jean-no on Fév 13, 2016

    @Philippe FORMET : save() et saveFrame() font une copie conforme de l’écran, un instantané, quoi. SVG est un format vectoriel, ça ne peut pas fonctionner comme ça. L’enregistrement des images SVG est calqué sur l’utilisation de la librairie PDF. Voici un court programme qui enregistre une image SVG à chaque exécution de draw(). Pour que le dossier ne soit pas trop encombré, je place l’image dans un sous dossier.

    import processing.svg.*;

    void setup(){
    size(500,500);
    }

    void draw(){
    beginRecord(SVG, "images/image"+nf(frameCount, 5)+".svg");
    ellipse(mouseX, mouseY, 100,100);
    endRecord();
    }

    Nota : nf(frameCount, 5) renvoie le numéro de frame, écrit avec cinq chiffre : 00001, 00002, 00003, etc.

  6. By Jean-no on Fév 13, 2016

    @Philippe Formet : Votre question m’a donné envie d’écrire un article complet sur la question !

  7. By BeJuCl on Mar 21, 2016

    Bonjour,
    Dans le cadre d’un projet d’ISN (sur Processing) nous voulions, à partir d’une photo trouver la taille de vêtement correspondant via la longueur épaule/bassin et largeur bassin, voilà ce que nous avons pour le moment:

    PImage img;
    int px,py,a,d;
    float lum;

    void setup() {
    img= loadImage(« corpsjulien.png »);
    size(img.width, img.height);
    colorMode(HSB,100,100,100);
    img.filter(GRAY);
    }
    void draw() {
    background(210);
    image(img,0,0);
    delay(500);

    for(py=0;py<img.height;py++)
    {
    color maCouleur = img.get(img.width/3,py);
    lum = brightness(maCouleur);
    println("y=",py,"lum=",lum);
    }
    }

    Cependant nous sommes bloqués au moment où il faut introduire une variable qui correspond au changement de luminosité, donc qui nous aiderai à déterminer la distance épaule/bassin et donc continuer le programme.

    Des idées pour nous aider?
    Merci d'avance pour votre aide

  8. By Jean-no on Mar 21, 2016

    @BeJuCl : je ne peux pas tellement vous répondre, n’ayant pas la photo utilisée et ne comprenant pas vraiment ce que vous voulez faire et comment un tel programme vous permet d’obtenir quelque chose qui se rapporte à la forme présente sur l’image !

  9. By CleJuBen on Avr 18, 2016

    Bonjour,
    Toujours dans le cadre de notre projet d’ISN, j’ai rencontré un problème, qui doit être surement facile à résoudre quand on s’y connait, mais vu que je débute, je ne trouve pas; je cherche a faire un balayage pour trouver la distance épaule/bassin représenté par la surface noir du tee-shirt, j’ai donc chercher les pixels avec une luminosité <15, je trouve donc tous les points avec cette luminosité, cependant je cherche juste le premier et le dernier point avec cette luminosité pour ainsi établir la distance, ou alors directement la distance si c'est possible. Tout cela pour me permettre de continuer mon projet sans encombres.

    Voici deux images qui permettent d'illustrer mon problème :
    http://codelab.fr/up/Captureprocessing.PNG
    http://codelab.fr/up/Capturephoto.PNG

    Merci d'avance pour votre aide.
    Bon journée/soirée.

  10. By Jean-no on Avr 19, 2016

    @CleJuBen : je ne sais pas trop comment je m’y prendrais, mais peut-être pouvez-vous utiliser l’algorithme de Graham, qui permet de trouver les points qui contournent une forme, ou bien d’utiliser une librairie permettant de déterminer des « blogs » de couleurs ? Ou sinon, une kinect est capable de trouver les cotes d’un corps, mais c’est une autre affaire.

  11. By André Connes (46) on Mai 15, 2017

    Je considère le 2e code de cette page.

    Le réglage de la taille de la fenêtre sur les dimensions de img

    size(img.width, img.height);

    renvoie une erreur : « The size of this sketch could not be determined from your code. Use only numbers (not variables) for the size() command. Read the size() reference for more details. » alors que

    print(img.width, img.height);

    retourne les bonnes valeurs et l’affichage est correct en fixant size() aux valeurs précédentes.

    Pouvez-vous m’aider ? (linux mint 18 64 bits). Merci.

    PS : ce problème excepté, tout fonctionne correctement, de 1 jusqu’à 16.

  12. By Jean-no on Mai 16, 2017

    @André : bonjour. Depuis Processing 3.0, on ne peut plus utiliser de variable dans la taille, il faut l’écrire en dur, par exemple : size(1000,1000);
    Vous pourrez néanmoins obtenir le résultat recherché en écrivant :
    surface.setResizable(true);
    surface.setSize(img.width, img.height);

  13. By André Connes (46) on Mai 17, 2017

    Merci pour l’aide.

    En remplaçant size(img.width, img.height);
    par

    surface.setResizable(true);
    surface.setSize(img.width, img.height);

    il n’y a plus d’erreur d’exécution mais l’affichage est incorrect. Désolé.

  14. By Jean-no on Mai 18, 2017

    @André : pour que le code fonctionne il faut que l’image soit au bon endroit et qu’elle ait le même nom que dans le code. Sinon, il sera impossible d’adapter la taille de l’affichage à la taille de l’image.

Postez un commentaire


Veuillez noter que l'auteur de ce blog s'autorise à modifier vos commentaires afin d'améliorer leur mise en forme (liens, orthographe) si cela est nécessaire.
En ajoutant un commentaire à cette page, vous acceptez implicitement que celui-ci soit diffusé non seulement ici-même mais aussi sous une autre forme, électronique ou imprimée par exemple.