ImageJ est, lui aussi, un logiciel de traitement d'images, dans lequel les images sont des tableaux de pixels. Mais il est écrit en Java, donc multiplateforme, et possède des fonctionnalités de statistiques très poussées, ce qui renforce son utilité en mathématiques. Et, outre un langage de macros proche de Java (donc objet), son interface de scripts est Rhino qui permet donc de le programmer en JavaScript...
=JavaScript sans image=
Pour afficher un résultat (par exemple le contenu d'une variable), on écrit "print" (en minuscules) suivi, entre parenthèses, du nom de la variable à afficher. Par exemple, on souhaite trouver une expression donnant la somme des carrés des entiers jusqu'à "n", en fonction de "n". Pour cela, on peut profiter de l'outil d'interpolation d"'ImageJ", et créer un tableau en JavaScript, contenant une dizaine de valeurs de cette somme. Plus précisément, "ImageJ" a besoin de deux tableaux, l'un pour les valeurs de "x", l'autre pour les valeurs de "y". Voici le script (la variable "s" contient les valeurs successives de la somme):
var s=0;
var xp=new Array();
var yp=new Array();
for(var i=1;i<=10;i++){
 s+=i*i;
 xp[i]=i;
 yp[i]=s;
 print(xp[i]+" "+yp[i]);
Remarque: Bien qu'il ait l'air itératif, le script ci-dessus est basé sur la récursivité, puisqu'il utilise le fait que formula_1 (ligne 5).
Sous "ImageJ", on doit ouvrir une console par "Plugins>New>JavaScript" puis y taper le script ci-dessus. Ensuite le raccourci clavier "Control+R" lance le script, ce qui a pour effet d'ouvrir une fenêtre dans laquelle se trouve un tableau, donnant en première colonne les entiers successifs, et en deuxième colonne les valeurs successives de la suite. Ce tableau est exactement ce dont l'outil "Curve Fitting" d"'ImageJ" a besoin. Il suffit alors de copier ce tableau ("Control+A" puis "Control+C") puis de le coller dans l'outil en question (accessible par "Analyse>Tools>Curve Fitting" qui crée un tableau où il faut remplacer les données actuelles par celles créées par JavaScript). Après choix du polynôme de degré 4 (tâtonnements précédents ou intuition), on obtient ceci:
L'image porte assez d'informations pour qu'on voie que l'expression est exacte (parce que le carré du coefficient de régression est égal à 1), polynomiale (on l'a choisie telle), de degré 3, et ayant "x" comme facteur. Il ne reste alors plus qu'à reconnaître des fractions dans les coefficients, puis à effectuer la factorisation, par exemple avec un logiciel de calcul formel, mais ceci est une autre histoire...
=création d'images en JavaScript=
Lignes de niveau.
L'objet le plus important, c'est ImageJ lui-même, appelé "IJ" en JavaScript. Pour dessiner, il faut un chevalet, en l'occurence une image blanche, où on va placer des pixels, qui sera en couleurs ("RGB") et fera 400 pixels par 400. La méthode "createImage" de l'objet "IJ" s'en charge. On peut en fait considérer l'objet "dessin" ci-dessous comme une instance de la classe "IJ". Mais ce n'est pas fini! Il faut ensuite créer un objet (instance de la classe "image processor") qui hérite des filtres d'ImageJ. Ce qui se fait en invoquant la méthode "getProcessor" de l'objet "dessin" (qui hérite cette méthode d'ImageJ). 
Enfin pour que l'image apparaisse à l'écran, il faut la mettre à jour (transférer son contenu dans une partie de la mémoire qui soit visible à l'écran), avec "dessin.show()" qui est une méthode que l'objet "dessin" a héritée de sa classe mère (IJ).
Fonction de deux variables.
Puisqu'on travaille dans des coordonnées entières (numéros des pixels), on va représenter par ses lignes de niveau, la fonction "pgcd(x,y)" qui est elle-même entière. Dans la boucle sur l'abscisse "i", on a placé un rectangle (méthode "showProgressBar" de l'objet "IJ", affiché en bas à droite dans la console ImageJ) qui affiche la progression du programme, ce qui permet de voir quelle partie de l'image occupe le plus de temps de calcul. En mode "RGB", les couleurs sont indiquées par des entiers longs, dont le reste modulo 256 indique la quantité de bleu. Donc une valeur nulle représente du noir, une valeur 255 du bleu clair, une valeur 255*256 du vert clair, et une valeur 255*256*256 du rouge clair. En multipliant le pgcd par 256*256, on obtient une valeur de rouge d'autant plus claire que le pgcd est élevé. Mais lorsque celui-ci dépasse 255, il est de nouveau représenté par une valeur sombre (visible en bas de la diagonale ci-dessous). Le script pour représenter en rouge les pgcd des nombres de 0 à 399 est ici:
function pgcd(a,b){//on crée une instruction "pgcd" en JavaScript
 var x=a, y=b;
 while(y>0){//implémentation itérative
 t=x%y;
 x=y;
 y=t;
 return(x);
var N=400;//taille de l'image
dessin = IJ.createImage("pgcd", "RGB", N, N, 1);//instanciation de la classe "image"
ip = dessin.getProcessor();//ip est un "processeur d'images"
for(var i=0;i<N;i++){
 IJ.showProgress(i, N-1);//affichage de l'état du programme
 for(var j=0;j<N;j++){
 c=pgcd(i,j)*256*256;//niveau de rouge
 ip.putPixel(i, j, c);//pour le pgcd
dessin.show();//mise à jour de l'image
Comme il y a beaucoup de nombres premiers entre eux, l'image obtenue est sombre:
Représentation dans l'espace.
ImageJ permet aussi de convertir une surface donnée par ses lignes de niveau comme ci-dessus, en une représentation graphique sous forme d'une surface dans l'espace. De même que ci-dessus, on va donc créer une image où les niveaux de gris représentent une fonction formula_2 et la faire convertir par ImageJ en dessin de la surface correspondante, vue en perspective. La fonction formula_2 considérée est la somme des distances entre le point de coordonnées (i,j) et les points de coordonnées respectives A(200;50), B(100;350) et C(350;350) ("Attention": L'axe des "y" est orienté vers le bas). On sait depuis Fermat et Torricelli que cette fonction atteint son minimum en un point appelé "point de Fermat-Torricelli" du triangle dont les sommets sont donnés ci-dessus.
Comme on fait de la géométrie, il est assez efficace de créer des objets "point" et "triangle" qui vont considérablement simplifier le code JavaScript (avec des méthodes adéquates) qui suivra. Tout d'abord, l'objet "point" ne possède que deux propriétés, ses coordonnées, et une méthode, son affichage. On a besoin d'une fonction "distance" (entre deux points) et d'une procédure "triangle" (ayant pour variables ses trois sommets):
function point(x,y){//objet point
 this.x=x;
 this.y=y;//les coordonnées sont des propriétés du point
 this.plot=function(proc){//l'affichage est une méthode du point
 proc.putPixel(x,y,255*256+255*256*256);
function distance(a,b){//a et b sont deux points
 return Math.sqrt(Math.pow(a.x-b.x,2)+Math.pow(a.y-b.y,2));
function triangle(a,b,c,proc){//a, b et c sont les sommets du triangle, 
 // proc est là où on le dessine
 proc.moveTo(a.x,a.y);// on part du premier sommet
 proc.lineTo(b.x,b.y);// tracé du côté [ab]
 proc.lineTo(c.x,c.y);// puis du côté [bc]
 proc.lineTo(a.x,a.y);// et du côté [ca]
On note "A.x" l'abscisse de A ("x" est une propriété de l'objet "A"). La création de l'image se fait comme ci-dessus, avec la méthode "createImage" de l'objet "IJ". On obtient alors un objet "plan" qui est une image (le chevalet initialement blanc). La méthode "getProcessor" permet alors d'avoir les filtres de cet objet (flou, Voronoi etc.). Ce processeur ne servira ici que pour ses méthodes de tracé de pixel et de segments (le triangle):
var N=400;//taille de l'image
plan=IJ.createImage("Fermat", "16-bit", N, N, 1);//création de l'image en noir et blanc
ip = plan.getProcessor();//les filtres du plan dans ip
A=new point(N/2,N/8);
B=new point(N/4,7*N/8);
C=new point(7*N/8,7*N/8);//les sommets du triangle sont des points
for(var i=0;i<N;i++){
 IJ.showProgress(i, N-1);//on regarde où on en est
 for(var j=0;j<N;j++){
 P=new point(i,j);
 d=distance(P,A)+distance(P,B)+distance(P,C);
 c=Math.round(d*64-32*256);//le niveau de blanc est la somme des distances 
 ip.putPixel(i, j, c);//normalisée empiriquement
triangle(A,B,C,ip);//dessin du triangle
plan.show();//affichage une seule fois, pour gagner du temps
L'exécution du script produit alors l'image suivante:
Comme c'est une image en noir et blanc, ImageJ permet automatiquement de tracer la surface, avec "Analyse>Surface Plot":
Dessin de fractales.
Tout ce qui précède peut servir à construire des images fractales en JavaScript:
Sierpinski.
Version itérative.
Le triangle de Sierpinski, version "IFS", est placé dans un objet appelé "flocon", qui est une instance de la classe "image":
flocon = IJ.createImage("Sierpinski", "RGB", 400, 400, 1);
ip = flocon.getProcessor();//cet objet contient les filtres
var x=Math.random()*400;
var y=Math.random()*400;
var xx, yy, c;
for(var i=0;i<100000;i++){
 de=Math.ceil(Math.random()*3);
 switch(de){
 case 1: {
 xx=(x+200)/2;
 yy=(y+50)/2;
 c=255*256*256;//rouge
 break;
 case 2: {
 xx=(x+350)/2;
 yy=(y+350)/2;
 c=255*256;//vert
 break;
 case 3: {
 xx=(x+50)/2;
 yy=(y+350)/2;
 c=255;//bleu
 break;
 x=xx;
 y=yy;
 ip.putPixel(Math.round(x), Math.round(y), c);
flocon.show();//affichage du flocon une fois calculé
Le fichier obtenu est celui-ci (en quelques secondes):
Version récursive.
On peut aussi utiliser l'objet "triangle" utilisé ci-dessus pour dessiner le triangle de Sierpinski comme un polygone. Seulement on aura besoin non pas de la distance entre deux points, mais de leur milieu (un point):
function point(x,y){//objet point
 this.x=x;
 this.y=y;//les coordonnées sont des propriétés du point
 this.plot=function(proc){//l'affichage est une méthode du point
 proc.putPixel(x,y,255*256+255*256*256);
function milieu(a,b){//a et b sont deux points
 return new point((a.x+b.x)/2,(a.y+b.y)/2);
function triangle(a,b,c,proc){//a, b et c sont les sommets du triangle, 
 // proc est là où on le dessine
 proc.moveTo(a.x,a.y);// on part du premier sommet
 proc.lineTo(b.x,b.y);// tracé du côté [ab]
 proc.lineTo(c.x,c.y);// puis du côté [bc]
 proc.lineTo(a.x,a.y);// et du côté [ca]
Avec ces objets, la définition récursive du polygone de Sierpinski est assez courte:
function sierpinski(a,b,c,proc,n){//définition récursive du triangle de S.
 if(n<1){//ordre nul: on ne dessine que le triangle
 triangle(a,b,c,proc);
 } else {
 sierpinski(a,milieu(a,b),milieu(a,c),proc,n-1);
 sierpinski(milieu(a,b),b,milieu(b,c),proc,n-1);
 sierpinski(milieu(a,c),milieu(b,c),c,proc,n-1);
Pour obtenir le dessin, il suffit d'appeler la fonction "sierpinski" ci-dessus avec les trois points A, B et C et l'ordre 6 (à l'ordre 0, seul un triangle est tracé; en fait à l'ordre formula_4 il y a exactement formula_5 triangles):
var N=400;//taille de l'image
img=IJ.createImage("SierpinskiTrianglesImageJ", "8-bit", N, N, 1);
construction = img.getProcessor();
A=new point(N/2,N/8);
B=new point(N/4,7*N/8);
C=new point(7*N/8,7*N/8);
sierpinski(A,B,C,construction,6);
img.show();
L'image obtenue est incontestablement un triangle de Sierpinski:
Comme le triangle ci-dessus est en noir et blanc, on peut utiliser la méthode statistique "Fractal Box Count" d'ImageJ pour estimer sa dimension fractale. Le diagnostic d'ImageJ est alors celui-ci:
La valeur exacte formula_6 est légèrement supérieure à celle estimée par ImageJ. Ce biais est sans doute dù à ce que les détails les plus fins se fondent dans les pixels. Refaire le tout avec une plus grande résolution est également laissé en exercice.
Julia.
Une double boucle parcourant tous les pixels permet de dessiner un ensemble de Julia par balayage (méthode moins rapide que l'itération inverse mais plus colorée). Le script permettant de dessiner l'ensemble de Julia de formula_7 ci-dessous ("x" allant de -1 à 1 et "y" aussi) est laissé en exercice.
En voyant cette image, comment ne pas avoir le coup de foudre pour les ensembles de Julia?
Mandelbrot.
La même méthode permet de dessiner l'ensemble de Mandelbrot en donnant à chaque pixel une couleur proportionnelle au nombre d'itérations qu'il a fallu pour que le terme de la suite formula_8, formula_9 sorte du disque de centre l'origine et de rayon 2. Comme précédemment, on crée une image "cardio" qui est un objet, une instance de la classe "IJ", et on y place l'un après l'autre des pixels dont la couleur est calculée dans une boucle:
var N=400;//taille de l'image
cardio=IJ.createImage("Mandelbrot", "RGB", N, N, 1);//l'image est en couleurs et fait N fois N pixels
ip = cardio.getProcessor();//l'objet "ip" est un retoucheur d'image
for(var i=0;i<N;i++){
 IJ.showProgress(i, N-1);//on regarde le café chauffer
 for(var j=0;j<N;j++){
 x0=(i-3*N/4)/N*4;//x va de -3 à 1
 y0=(j-N/2)/N*4;//y va de -2 à 2: image carrée
 x=x0;
 y=y0;//le premier terme de la suite est c=x0+iy0
 var k=0; var m=0;//k mesure le temps d'échappement
 while(k<256 && m<4){//soit le nombre d'itérations pour avoir m>4
 xx=x*x-y*y+x0;//partie réelle de z*z+c
 yy=2*x*y+y0;//partie imaginaire de z*z+c
 m=xx*xx+yy*yy;//m est le carré du module
 x=xx;
 y=yy;
 k++;//on passe au terme suivant de la suite
 ip.putPixel(i, j, 8*k);//le bleu est d'autant plus intense qu'on diverge lentement
cardio.show();//affichage de la cardioïde à la fin
En observant la barre de progrès, on constate que la partie gauche de l'ensemble de Mandelbrot est beaucoup plus rapide à calculer que la partie droite. Ce phénomène illustre la notion de vitesse de divergence.