If(){design}else{art} Processing, le livre

23 novembre 2020

Vérifier la présence de la caméra

Filed under: tips — jean-noël Lafargue @ 12 h 22 min

Lorsque l’on veut accéder à la webcam avec la librairie video et l’objet Capture, il n’est pas rare que rien ne fonctionne alors même que la caméra est présente et qu’elle fonctionne de manière satisfaisante avec tous les autres logiciels (mais attention, deux logiciels différents ne peuvent pas accéder à une même caméra en même temps !)
Le problème le plus courant est que la définition de la capture que vous voulez obtenir ne fait pas partie des définitions d’image disponibles pour la caméra. Souvent, le format 640×480 est disponible mais il peut tout à fait être absent de la liste des possibilités, notamment avec les webcams récentes, qui sont prévues pour des formats supérieurs.
Le modeste programme qui suit vous permet de connaître la liste de toutes les caméras accessibles par Processing :

import processing.video.*;

String[] cameras = Capture.list();
for(String c:cameras){
  println(c);
}

Il suffit de le lancer pour obtenir, dans la console, une liste de possibilités.
Le résultat sera parfois un simple nom, comme « Logitech HD Webcam C270 », et parfois, une liste signalant les formats disponibles (320×240 à 60 images par seconde, par ex.).
Vous pourrez alors adapter l’initialisation de la capture en conséquence, soit en saisissant le nom du modèle (écrit exactement pareil) :

new Capture(this, "Logitech HD Webcam C270");

Cette méthode (avec le nom exact) est particulièrement importante lorsque plusieurs caméras différents sont connectées au même ordinateur.
S’il n’y a qu’une caméra, on peut se contenter de proposer la définition d’image trouvée (en supposant, dans ce cas, qu’il existe un format 320 x 240 pixels dans la liste) :

new Capture(this, 320, 240);

Voici enfin un simple programme qui :
– importe la librairie vidéo
– déclare un objet Capture() et l’initialise
– adapte le format de la fenêtre à celui de la capture
– et qui lit l’image capturée (si disponible) et l’affiche :

import processing.video.*;
Capture cap;

void setup(){
  cap = new Capture(this, "Logitech HD Webcam C270");
  cap.start();
  surface.setSize(cap.width, cap.height);
}
void draw(){
  if(cap.available()){
    cap.read();
    image(cap,0,0);
  }
}

9 novembre 2019

Performances comparées de Array et ArrayList

Filed under: optimisation — jean-noël Lafargue @ 13 h 47 min

Processing permet de manipuler les tableaux avec deux types de variables : les Array (tableaux « normaux » de Processing) et les ArrayList (tableaux Java). Lorsque les tableaux contiennent des variables d’un type élémentaire (int, float), et que la taille du tableau ne change jamais, on a tendance à employer les tableaux « normaux ».
Lorsque l’on recourt à des données plus complexes, comme l’objet PVector, ou des classes créées par le programmeur, et surtout lorsque l’on veut que le tableau puisse voir sa taille changer, on dit souvent qu’il vaut mieux utiliser ArrayList. Nous avons décidé d’évaluer dans quelle mesure le choix d’une ou l’autre méthode impacte les performances.
Voici deux programmes qui permettent d’effectuer une comparaison. Ces deux programmes créent des particules, et remplissent des tableaux qui finissent par contenir plus de 30 000 instances, dont chacune est individuellement appelée afin de changer de position et de s’afficher.

Voici la version avec un tableau « normal » :

poin[] allPoints;
int cx,cy, begining;

void setup(){
begining = millis();
size(640,640);
cx=width/2;cy=height/2;
allPoints = new poin[0];
}

void draw(){
background(255);
int t=millis();
poin[] newAllPoints = new poin[0];
for(poin p:allPoints){
p.avance();
if(p.alive){
newAllPoints = (poin[]) append(newAllPoints, p);
}
}
float ang=TWO_PI/frameCount;
for(float a=0;a30000){println("total time : "+(millis()-begining));noLoop();}
}

class poin{
float x,y,a;boolean alive=true;
poin(float x, float y, float a){
this.x=x;this.y=y;this.a=a;
}
void avance(){
if(x<0||y<0||x>width||y>height)alive=false;
x+=cos(a)2;y+=sin(a)2;
point(x,y);
}
}

Et voici la version avec ArrayList :

ArrayList allPoints;
int cx,cy, begining;

void setup(){
begining = millis();
size(640,640);
cx=width/2;cy=height/2;
allPoints = new ArrayList(0);
}

void draw(){
background(255);
int t=millis();
ArrayList newAllPoints=new ArrayList();
for(poin p:allPoints){
p.avance();
if(p.alive){
newAllPoints.add(p);
}
}
float ang=TWO_PI/frameCount;
for(float a=0;a30000){println("total time : "+(millis()-begining));noLoop();}
}

class poin{
float x,y,a;boolean alive=true;
poin(float x, float y, float a){
this.x=x;this.y=y;this.a=a;
}
void avance(){
if(x<0||y<0||x>width||y>height)alive=false;
x+=cos(a)2;y+=sin(a)2;
point(x,y);
}
}

Sur mon ordinateur (mais ce sera différent sur un ordinateur de puissance différente), la première méthode est lente puisqu’il faut plus de 2000 millisecondes (deux secondes) pour traiter 30162 instances. Avec la seconde méthode, le même nombre d’instances est traité en moins de 400 millisecondes (4/10e de seconde).

Le tableau ArrayList l’emporte donc ici haut la main en se montrant plus de cinq fois plus rapide !

13 juin 2019

Outil de diffusion d’images

Filed under: Non classé — jean-noël Lafargue @ 9 h 21 min

Le programme qui suit sert à diffuser au hasard les images contenues dans le dossier « data ». Les images affichées voient leur format s’adapter à l’écran lorsqu’elles excèdent ses dimensions.

String[] images;
IntList ordre;
int vitesse=1500; // une nouvelle image s'affiche toutes les 1,5 secondes (1500 millisecondes)
int next=0;
color fond=#000000;

void setup(){
  fullScreen();
  imageMode(CENTER);
  ordre=new IntList();
  images = listFileNames(dataPath("")); 
  background(fond);
}

void draw(){
  if(ordre.size()==0){
    for(int a=0;anext){
    affiche(images[ordre.get(0)]);
    ordre.remove(0);
    next=millis()+vitesse;
  }
}

String[] listFileNames(String dir) {
  File file = new File(dir);
  if (file.isDirectory()) {
    String names[] = file.list();
    String newNames[] = new String[0];
    for(String n:names){
      if(match(n, ".*\\.|jpg|jpeg|png|bmp|gif|JPG|JPEG|PNG|BMP|GIF")!=null){
        newNames = (String[]) append(newNames, n);
      }
    } 
    return newNames ;
  } else { 
    return null;
  }
}

void affiche(String im){
  background(fond);
  PImage fichier = loadImage(im);
  int largeur=fichier.width;
  int hauteur=fichier.height;
  if(largeur>width){
    float pc = float(width)/largeur;
    hauteur = round(pc*hauteur);
    largeur=width;
  }
  if(hauteur>height){
    float pc = float(height)/hauteur;
    largeur = round(pc*largeur);
    hauteur=height;
  }
  
  if(largeur>width||hauteur>height){
    largeur=width;hauteur=height;
  } 
  image(fichier, width/2, height/2, largeur, hauteur);
  
}

28 mars 2018

Déterminer si un point se trouve inscrit dans un triangle

Filed under: Non classé — jean-noël Lafargue @ 8 h 02 min

Pour déterminer si un point est inscrit dans un triangle, nous créons deux fonctions. L’une permet de déterminer le barycentre de trois points. La seconde fonction appellera la première trois fois pour calculer le barycentre des coordonnées du point recherché avec chaque paire de points des trois lignes du triangle :

float barycentre (PVector p1, PVector p2, PVector p3)
{
    return (p1.x - p3.x) * (p2.y - p3.y) - (p2.x - p3.x) * (p1.y - p3.y);
}

boolean dansLeTriangle (PVector pt, PVector v1, PVector v2, PVector v3)
{
    boolean b1, b2, b3;

    b1 = barycentre(pt, v1, v2) < 0 ;
    b2 = barycentre(pt, v2, v3) < 0;
    b3 = barycentre(pt, v3, v1) < 0 ;

    return ((b1 == b2) && (b2 == b3));
}

Un exemple de mise en œuvre :

Dans le programme qui suit, on vérifie si la souris se trouve à l’intérieur d’un triangle ou non, le triangle est rempli en blanc si c’est le cas, en noir sinon.

PVector p1,p2,p3;

void setup() {
  size(500, 500);
  p1 = new PVector(100, 100);
  p2 = new PVector(400, 100);
  p3 = new PVector(250, 400);
}

void draw() { 
  background(255);
  if (dansLeTriangle(new PVector(mouseX, mouseY), p1, p2, p3)) { 
    stroke(0); 
    fill(255);
  } else { 
    noStroke();
    fill(0);
  } 
  triangle(p1.x, p1.y, p2.x, p2.y, p3.x, p3.y);
} 

float barycentre (PVector p1, PVector p2, PVector p3)
{
  return (p1.x - p3.x) * (p2.y - p3.y) - (p2.x - p3.x) * (p1.y - p3.y);
}

boolean dansLeTriangle (PVector pt, PVector v1, PVector v2, PVector v3)
{
  boolean b1, b2, b3;

  b1 = barycentre(pt, v1, v2) <   0;
  b2 = barycentre(pt, v2, v3) <   0;
  b3 = barycentre(pt, v3, v1) <   0;

  return ((b1 == b2)  &&  (b2 == b3));
}

10 mai 2017

Créer un kaléidoscope

Filed under: tips — jean-noël Lafargue @ 12 h 32 min

La fonction qui suit répète une image quatre fois sur l’écran, avec un effet d’inversion qui permet donc de créer un kaléidoscope.
L’image d’origine est celle-ci :

void repeteLimage(PImage g){ 
  image(g,0,0); 
  pushMatrix();
  translate(width,0);
  scale(-1,1);
  image(g,0,0);
  popMatrix();
  pushMatrix();
  translate(width,height);
  scale(-1,-1);
  image(g,0,0);
  popMatrix();
  pushMatrix();
  translate(0,height);
  scale(1,-1);
  image(g,0,0);
  popMatrix(); 

}

L’image finalement obtenue à l’écran est celle-ci :

28 mars 2017

Implémentation de l’algorithme de diffusion Floyd-Steinberg

Filed under: Non classé — jean-noël Lafargue @ 13 h 32 min

L’algorithme de diffusion Floyd-Steinberg, inventé en 1976, permet de tramer une image de manière intelligente. On peut lire son fonctionnement en détails sur Wikipédia.
L’image qui suit nous permet de comparer un détail d’image transformée en image 1bit (noir ou blanc) avec cet algorithme (gauche) et sans (droite).

Le programme qui suit est une implémentation de cet algorithme pour Processing. Il n’est pas forcément rapide mais il fonctionne bien, en niveaux de gris. Lorsque la variable colorsNumber est 2, il n’y a que deux couleurs qui seront employées : le blanc et le noir.


En entrant l’image en couleurs ci-dessus (« montagne.png »), on obtient l’image en noir et blanc qui suit et qui sera enregistrée sous le nom « floyd_steinberg.png ».

float palette[];
float mu1 = 7.0/16, mu2 = 3.0/16, mu3=5.0/16, mu4=1.0/16;


void setup() {
  size(600, 480);
  background(255);
  int colorsNumber = 2; // ici on décide le nombre de couleurs de la palette
  palette=getPalette(0,255,colorsNumber);  
}

void draw() {
  // l'image à charger s'appelle "montagne.jpg" et doit 
  // se trouver dans le dossier "data"
  PGraphics g= floydSteinberg(loadImage("montagne.jpg")); 
  image(g,0,0);
  g.save("floyd_steinberg.png");
  noLoop();
}

PGraphics floydSteinberg(PImage img) { 
  PGraphics g = createGraphics(img.width, img.height);
  g.beginDraw();
  g.background(0);
  float[][] pixelz;
  pixelz = new float[img.width][img.height];
  for (int y=0; y<img.height; y++) {
    for (int x=0; x<img.width; x++) {
      pixelz[x][y]= brightness(img.get(x, y));
    }
  }
  for (int y=0; y<img.height; y++) {
    for (int x=0; x<img.width; x++) {
      float ancien=pixelz[x][y];  
      float[] nouveau = couleur_la_plus_proche(ancien);

      pixelz[x][y] = nouveau[0];
      float error= nouveau[1];
      boolean xMax=(x==img.width-1);
      boolean xMin=(x==0);
      boolean yMax=(y==img.height-1);
      if (!xMax) pixelz[x+1][y] += mu1* error;
      if (!xMin && !yMax) pixelz[x-1][y+1] += mu2* error;
      if (!yMax)pixelz[x][y+1] += mu3* error;
      if (!xMax && !yMax)pixelz[x+1][y+1] += mu4* error;
    }
  }
  for (int y=0; y<img.height; y++) {
    for (int x=0; x<img.width; x++) {
      float b=pixelz[x][y];
      g.stroke(b);
      g.point(x, y);
    }
  }
  g.endDraw();
  return g;
}

float[] couleur_la_plus_proche(float b) {
  float[] paire = {0, 0};
  float min=256; 
  for (int i=0; i<palette.length; i++) {
    float c=palette[i];
    float dis = abs(b-c);
    if (dis<min) {
      min=dis;
      paire[0]=c;
    }
  } 
  paire[1]=b-paire[0];
  return paire;
}

float[] getPalette(float depart, float fin, int n){
  if(n<2)n=2;
  float[] toreturn = new float[0];
  float diff = (fin-depart)/(n-1);
  for(int a=0;a<n;a++){
    toreturn = (float[]) append (toreturn, a*diff);
  } 
  return toreturn;
}

La valeur de chaque pixel (ligne par ligne depuis le haut jusqu’en bas et de gauche à droite à l’intérieur de chaque ligne) est lue et comparé à sa valeur indexée la plus proche. Si la valeur du pixel est 120 et que la valeur la plus proche dans ma palette est 128, la marge (dite « erreur ») sera de -8. Nous allons alors ajouter :
7/16e de -8 à la valeur du pixel (x+1, y)
3/16e de -8 au pixel (x-1, y+1)
5/16e de -8 à (x,y+1)
1/16e de -8 à (x+1,y+1)

14 mars 2017

Ordonner les éléments d’un tableau au hasard

Filed under: tips — jean-noël Lafargue @ 18 h 49 min

Il est souvent intéressant de déterminer aléatoirement l’ordre des éléments d’un tableau.
La fonction qui suit permet très simplement d’obtenir ce résultat : on lui soumet un tableau, elle parcourt ses éléments un à un puis ajoute ces derniers à un nouveau tableau, à une position au hasard. Le type de tableau doit être indiqué, il faut donc remplacer le mot type par String, int, float, etc., selon le besoin :

type[] shuffle(type[] origine){
  type[] resultat = new type[0];
  for(type s:origine){
     int pos = floor(random(resultat.length+1));
     resultat = (type[]) splice(resultat, s, pos);
  }
  return resultat;
} 

Dans l’exemple qui suit, le type de tableau choisi est String[] (chaîne de caractère).
Nous créons un tableau contenant les valeurs « 1 », « 2 », « 3 », « 4 », « 5 », puis, à dix reprises, nous mélangeons ce tableau.

void setup(){
  size(1024,768);
  String[] s = {"A", "B", "C", "D", "E"};
  for(int a=0;a<10;a++){
    s = shuffle(s); println(s); 
  }
}
  
String[] shuffle(String[] origine){
  String[] resultat = new String[0];
  for(String s:origine){
     int pos = floor(random(resultat.length+1));
     resultat = (String[]) splice(resultat, s, pos);
  }
  return resultat;
}

Le résulstat sera (par exemple) :

D B E C A
A E B C D
B D E C A
A D C E B
B D C E A
A C D E B
D C E B A
A E C B D
E D C B A
D A C B E

13 février 2016

L’enregistrement au format SVG

Filed under: Non classé — jean-noël Lafargue @ 16 h 07 min

Processing permet désormais l’enregistrement au format vectoriel SVG.
Le format SVG ne contient pas une matrice de points, comme les formats bitmap Jpeg, png, gif, bmp ou tiff, mais des commandes vectorielles. Pour cela, il serait impossible de se contenter d’une capture de l’espace d’affichage le font les commandes save() et saveFrame(). L’enregistrement au format SVG fonctionne en fait comme l’enregistrement au format PDF, qui est lui aussi un format de document vectoriel.

Voici un exemple qui commence par importer la librairie SVG, crée un écran au format 400×400 pixels, puis, à l’aide de la commande beginRecord(), démarre l’enregistrement au format SVG d’une image que nous choisissons de nommer « image.svg ». Ensuite, nous dessinons une ellipse de 100×100 pixels tous les 25 pixels, à l’aide d’une boucle.
Enfin, nous interrompons l’enregistrement avec endRecord().

import processing.svg.*;
size(400,400);
beginRecord(SVG, "image.svg");
  for(int a=50;a<350;a+=25){
     ellipse(a, a, 100,100);
  }
endRecord();

On le voit, l’image qui en résulte est bien une image vectorielle, qui peut être ouverte et éditée à l’aide d’un logiciel de dessin vectoriel tel que Adobe Illustrator ou que le logiciel libre Inkscape :

exemple_svg

Voici un second exemple, qui permet de remplacer la commande saveFrame() :

import processing.svg.*;

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

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

À chaque rafraîchissement de l’écran (draw()), on enregistre ce qui se passe sur l’écran (en l’occurrence, le dessin d’une ellipse à la position de la souris) à l’intérieur d’un fichier.
nf(frameCount, 5) renvoie le numéro de frame, écrit avec cinq chiffre : 00001, 00002, 00003, etc. Les fichiers auront donc pour nom « image00001.svg », image00002.svg », « image00003.svg » et ainsi de suite.
Le nom du fichier est précédé de « images/ », ce qui est une astuce pour que toutes les images ne se retrouvent pas à l’intérieur du dossier du programme, mais à l’intérieur d’un de ses sous-dossiers, que nous choisissons de nommer « images ».

12 octobre 2014

Placer un point sur un cercle et sur une sphère

Filed under: Non classé — jean-noël Lafargue @ 22 h 18 min

Il est facile de placer un point sur un cercle, par exemple avec les lignes qui suivent :

void setup(){
  size(500,500);
  point(250+cos(PI)*100, 250+sin(PI)*100);
}

…qui consistent à placer un point à 100 pixels du centre de l’écran (250, 250) et à l’angle PI (donc la moitié d’un cercle).
La formule est :
– abscisse de départ + cosinus (angle) * rayon // pour l’abscisse (x)
– ordonnée de départ + sinus (angle) * rayon // pour l’ordonnée (y)

Dans l’exemple qui suit, nous dessinons un trait de 200 pixels, partant du centre de l’écran, et aboutissant successivement à 0°, 1°, 2°, 3°,… et jusqu’à 359°.

float angle=0;

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

void draw(){
  background(255);
  angle+=TWO_PI/360;
  translate(width/2, height/2);
  line(0, 0, cos(angle)*200, sin(angle)*200);
}

Pour placer un point sur une sphère, qui sera repéré par trois coordonnées (abscisse, ordonnée et cote, ou x, y, z), il faut deux angles, que nous nommerons phi et theta.
La formule à appliquer est alors

– abscisse de départ + (sin(phi)*cos(theta))*rayon // pour l’abscisse (x)
– ordonnée de départ + (sin(phi)*sin(theta))*rayon // pour l’ordonnée (y)
– cote de départ + (cos(phi))*rayon // pour la cote (z)

Un exemple d’utilisation avec le programme qui suit, qui affiche une sphère de 24 longitudes x 24 latitudes, tournant sur elle-même :

float rx=0, ry=0;

void setup() {
  size(500, 500, P3D);
  strokeWeight(4);
}

void draw() {
  background(255);
  ry+=0.01;
  rx+=0.031;
  float r=200;
  translate(width/2, height/2);
  rotateX(rx);
  rotateY(ry);
  for (float phi=0; phi<TWO_PI; phi+=TWO_PI/24) {
    for (float theta=0; theta<TWO_PI; theta+=TWO_PI/24) { 
      float x=(sin(phi)*cos(theta))*r;
      float  y=(sin(phi)*sin(theta))*r;
      float z=(cos(phi))*r; 
      point(x, y, z);
    }
  }
}

Voici une capture d’écran :

points

les variables rx et ry correspondent à l’angle de rotation qui est appliqué à l’environnement, à partir du point de référence (width/2, height/2), c’est à dire le centre de l’écran.

8 mai 2014

Créer une animation GIF avec la librairie « gifAnimation »

Filed under: tips — jean-noël Lafargue @ 18 h 22 min

La librairie gifAnimation permet d’exporter un fichier au format GIF animé directement depuis Processing. Elle n’a malheureusement pas encore été portée sous Processing 3 et ne fonctionne donc qu’avec les versions précédentes.

La première étape est de télécharger la librairie gifAnimation.
Depuis la version 2 de Processing, cette opération peut se réaliser de manière assez automatique, depuis le menu « Sketch ».
On commence par aller dans « Import Library… » puis « Add Library… ».

 

import_librairie

 

…ce qui fait apparaître la fenêtre du gestionnaire des librairies, le « Library manager », où figurent les nom des librairies installées et de celles qui sont disponibles.
Pour télécharger une librairie, on clique simplement sur le bouton « Install ». La suite se déroule automatiquement.
Bien évidemment, votre ordinateur doit être connecté à Internet pour que ce téléchargement fonctionne.

 

import_librairie2

 

Une fois installée, la librairie est immédiatement disponible.
L’étape suivante, dans notre programme, sera de déclarer la librairie, et de déclarer un objet GifMaker, que nous choisirons de nommer gifExport :

 

import gifAnimation.*;
GifMaker gifExport ;

 

Cet objet sera plus tard instancié dans la section setup :

 

gifExport = new GifMaker(this, "monjoligifanime.gif"); // on instancie l'objet et on indique le nom qu'aura le fichier
gifExport.setRepeat(0); // on déclare le nombre de répétitions de la boucle (0 : infini)        
gifExport.setTransparent(255,255,255); // on déclare la couleur de transparence (ici, du blanc)

 

Au cours de l’animation, chaque fois que nous désirerons enregistrer le contenu de notre fenêtre en tant que « frame » (photogramme) de notre animation, nous utiliserons les commandes setDelay() (pour définir la durée de l’image, en millisecondes), et addFrame(), pour enregistrer :

 

gifExport.setDelay(20);
gifExport.addFrame();

 

Enfin, lorsque toutes les images que nous souhaitons enregistrer l’auront été, nous pourrons arrêter l’animation et procéder à l’enregistrement :

 

gifExport.finish();  
noLoop();

 

Voici le programme complet, qui dessine un rectangle en train de grandir. Chaque étape est enregistrée, et lorsque le rectangle dépasse le format de notre fenêtre, l’animation s’interrompt et le gif animé est enregistré.

 

import gifAnimation.*;
GifMaker gifExport ;
float taille=0;

void setup() {
size(200,200); 
    gifExport = new GifMaker(this, "monjoligifanime.gif");
    gifExport.setRepeat(0);             
   gifExport.setTransparent(255,255,255);  
    noFill();
    strokeWeight(10);
    rectMode(CENTER);
}

void draw() {
    background(255);
    rect(width/2, height/2, taille, taille);
    taille+=2;
    taille*=1.2;
    
    gifExport.setDelay(20);
    gifExport.addFrame();
    if(taille>width){ 
    gifExport.finish();  
      noLoop();
    }
}

 

Une fois le Gif fabriqué, vous n’aurez plus qu’à aller le récupérer à l’intérieur de votre « Sketch folder », avec la commande « Sketch -> show sketch folder ».
Attention les yeux, voici le résultat :

 

monjoligifanime

 

Older Posts »

Powered by WordPress