Création d'images mathématiques dans un shell Linux
On part d'un fichier texte contenant les valeurs numériques des 3 canaux de couleur (ROUGE, VERT et BLEU), et ce pour chaque pixel de l'image avec un pixel par ligne. Un tel fichier est facile à générer avec les langages de programmation classique (Perl, Python, C, Java, etc.) et portera ici l'extension .rvb :
155 , 67 , 222
156 , 67 , 223
157 , 67 , 224
158 , 67 , 225
159 , 67 , 226
160 , 67 , 227
161 , 67 , 228
162 , 67 , 229
163 , 67 , 230
164 , 67 , 231
165 , 67 , 232
166 , 67 , 233
167 , 67 , 234
168 , 67 , 235
169 , 67 , 236
170 , 67 , 237
171 , 67 , 238
172 , 67 , 239
etc.
On convertit le fichier texte .rvb en fichier binaire .rgb :
cat toto.rvb | sed 's/,/\n/g' | perl -e 'while ($ch=<STDIN>) { printf("%c",$ch) }' > toto.rgb
On compresse le fichier binaire .rgv en image .png :
convert -size 10x10 -depth 8 toto.rgb toto.png
Enfin on peut effacer les deux fichiers temporaires volumineux devenus inutiles afin de ne garder que le fichier .png :
rm toto.rvb toto.rgb
Les mires de couleur sont des images surfaciques où chaque pixel de coordonnées (x,y) reçoit une valeur numérique directement calculée en fonction de x et de y.
Exemple 1 : on part du programme suivant en Python qui envoie sur sa sortie standard la valeur des 3 canaux de couleur ROUGE, VERT et BLEU :
res_x=640
res_y=480
for y in range(1,res_y+1):
for x in range(1,res_x+1):
print x % 255,",",y % 255,",",(x+y) % 255
On récupère l'ensemble des valeurs des pixels dans le fichier texte mire.rvb :
python mire.py > mire.rvb
Voici un extrait du fichier texte mire.rvb :
155 , 67 , 222
156 , 67 , 223
157 , 67 , 224
158 , 67 , 225
159 , 67 , 226
160 , 67 , 227
161 , 67 , 228
162 , 67 , 229
163 , 67 , 230
164 , 67 , 231
165 , 67 , 232
166 , 67 , 233
167 , 67 , 234
168 , 67 , 235
169 , 67 , 236
170 , 67 , 237
171 , 67 , 238
172 , 67 , 239
etc.
On convertit le fichier texte mire.rvb en fichier binaire mire.rgb grâce à un filtre en Perl :
cat mire.rvb | sed 's/,/\n/g' | perl -e 'while ($ch=<STDIN>) { printf("%c",$ch) }' > mire.rgb
On convertit le fichier binaire mire.rgb en image PNG grâce à ImageMagick puis on affiche l'image grâce à Eye of GNOME (eog) :
convert -size 640x480 -depth 8 mire.rgb mire.png ; eog mire.png
Et voici le résultat de cette image surfacique :
Exemple 2 : on part cette fois du programme suivant en Python qui envoie sur sa sortie standard la valeur numérique en décimal de chaque pixel, sans séparer les 3 canaux de couleur. Chaque valeur est comprise entre 0 et 16 777 215 en décimal, soit entre 0x0 et 0xFFFFFF en hexadécimal :
res_x=640
res_y=480
for y in range(1,res_y+1):
for x in range(1,res_x+1):
n=x*y
print(n)
On récupère l'ensemble des valeurs des pixels dans le fichier texte mire.pix :
python mire.py > mire.pix
Voici un extrait du fichier texte mire.pix :
12558
12696
12834
12972
13110
13248
13386
13524
13662
13800
13938
14076
14214
14352
14490
14628
14766
14904
15042
15180
15318
15456
15594
15732
15870
16008
16146
16284
16422
16560
16698
etc.
On convertit le fichier texte mire.pix en fichier binaire mire.rgb grâce à un filtre en Perl qui sépare les 3 octets dans les valeurs numériques lues :
cat mire.pix | perl -e 'while($n=<STDIN>) {printf("%c%c%c",($n>>16) & 255,$n & 255,($n>>8) & 255)}' > mire.rgb
On convertit le fichier binaire mire.rgb en image PNG grâce à ImageMagick puis on affiche l'image grâce à Eye of GNOME (eog) :
convert -size 640x480 -depth 8 mire.rgb mire.png ; eog mire.png
Et voici le résultat de cette image surfacique :
Ces deux premiers exemples ont prouvé qu'il été très simple de créer des images mathématiques en ligne de commande dans un shell Linux par une succession de petits scripts. Dans une image mathématiques, chaque pixel de coordonées (x,y) est associé à une valeur numérique entière n calculée en fonction de x et de y. Comme le modèle de couleur utilisé ici est le RVB (3 canaux de couleur enregistré chacun dans un octet), la valeur numérique de l'entier n doit être comprise entre 0 et 224-1, soit entre 0 et 16 777 215 en décimal, c'est-à-dire entre 0x0 et 0xFFFFFF en hexadécimal.
Il y a 2 manières de calculer la valeur numérique n correspondant à la couleur du pixel, ce qui conduit à 2 types d'images mathématiques :
Voici le programme julia.py qui envoie les valeurs numériques de chaque pixel sur sa sortie standard sous forme de texte :
x_min=-1.97
x_max=1.97
y_min=-1.56
y_max=1.40
res_x=640
res_y=480
for py in range(1,res_y+1):
for px in range(1,res_x+1):
n=0
x=x_min+px*(x_max-x_min)/res_x
y=y_max-py*(y_max-y_min)/res_y
z=complex(x,y)
z2=complex(-0.0986,-0.65186)
while(abs(z)<2):
z=z*z+z2
n+=1
if n>=255:
break
print(n)
Calcul de l'image et création du fichier texte julia.rvb :
python julia.py > julia.rvb
Conversion du fichier texte julia.rvb en fichier binaire julia.rgb (en 255 niveaux de vert) :
cat julia.rvb | perl -e 'while ($ch=<STDIN>) { printf("%c%c%c",0,$ch,0) }' > julia.rgb
Création de l'image PNG julia.png :
convert -size 640x480 -depth 8 julia.rgb julia.png
Affichage de l'image PNG julia.png :
eog julia.png
Et voici le résultat :
On constate que 255 itérations est insufisant. Recalculons cet ensemble de julia avec 4000 itérations :
x_min=-1.97
x_max=1.97
y_min=-1.56
y_max=1.40
res_x=640
res_y=480
for py in range(1,res_y+1):
for px in range(1,res_x+1):
n=0
x=x_min+px*(x_max-x_min)/res_x
y=y_max-py*(y_max-y_min)/res_y
z=complex(x,y)
z2=complex(-0.0986,-0.65186)
while(abs(z)<2):
z=z*z+z2
n+=1
if n>=4000:
break
print(n)
Calcul de l'image et création du fichier texte julia.rvb :
python julia.py > julia.rvb
Conversion du fichier texte julia.rvb en fichier binaire julia.rgb en séparant les 3 canaux de couleur :
cat julia.rvb | perl -e 'while($n=<STDIN>) {printf("%c%c%c",($n>>16) & 255,$n & 255,($n>>8) & 255)}' > julia.rgb
Création de l'image PNG julia.png :
convert -size 640x480 -depth 8 julia.rgb julia.png
Et voici le résultat :
Mais on remarque qu'avec plus de 255 itérations il y a des sauts de couleurs sur l'image : lorsque l'octet vert passe de 255 à 0 l'image passe brutalement du vert au noir. Pour éviter les sauts de couleurs la solution est la suivante :
Une telle opération permet de "linéariser" les couleurs afin qu'il n'y ait que des variations douces entre deux couleurs consécutives. Le filtre en Perl suivant permet de linéariser l'image julia.rvb lors de la conversion en julia.rgb :
cat julia.rvb | perl -e 'while($n=<STDIN>) {$r=($n>>16)&255;$v=$n&255;$b=($n>>8)&255;if ($b % 2==1) {$v=255-$v;};if ($r % 2==1) {$b=255-$b;};printf("%c%c%c",$r,$v,$b)}' > julia.rgb
convert -size 640x480 -depth 8 julia.rgb julia.png ; display julia.png
Et voici le résultat de l'image linéarisée après avoir été calculée avec 16 000 000 d'itérations :
Remarque : en modifiant la valeur du nombre complexe constant z2 dans le programme julia.py on change d'ensemble de Julia.
Programme mandelbrot.py qui envoie les valeurs numériques sur sa sortie standard sous forme de texte :
x_min=-2.61
x_max=1.33
y_min=-1.56
y_max=1.40
res_x=640
res_y=480
for py in range(1,res_y+1):
for px in range(1,res_x+1):
n=0
x=x_min+px*(x_max-x_min)/res_x
y=y_max-py*(y_max-y_min)/res_y
z=complex(x,y)
z2=z
while(abs(z)<2):
z=z*z+z2
n+=1
if n>=255:
break
print(n)
Calcul de l'image et création du fichier texte toto.rvb :
python mandelbrot.py > toto.rvb
Conversion du fichier texte toto.rvb en fichier binaire toto.rgb (en 255 niveaux de vert) :
cat toto.rvb | perl -e 'while ($ch=<STDIN>) { printf("%c%c%c",0,$ch,0) }' > toto.rgb
Création de l'image PNG toto.png :
convert -size 640x480 -depth 8 toto.rgb toto.png
Affichage de l'image PNG toto.png :
eog toto.png
Et voici le résultat :
Remarque : la seule différence entre les programmes julia.py et mandelbrot.py est le complexe z2 :
Nous venons d'obtenir une image en 255 niveaux de vert, car nous avons utilisé seulement le canal vert du modèle de couleur RVB. En utilisant un autre ou plusieurs canaux de couleur on peut obtenir une image :
En 255 niveaux de bleu : cat toto.rvb | perl -e 'while ($ch=<STDIN>) { printf("%c%c%c",0,0,$ch) }' > toto.rgb
En 255 niveaux de rouge : cat toto.rvb | perl -e 'while ($ch=<STDIN>) { printf("%c%c%c",$ch,0,0) }' > toto.rgb
En 255 niveaux de jaune : cat toto.rvb | perl -e 'while ($ch=<STDIN>) { printf("%c%c%c",$ch,$ch,0) }' > toto.rgb
En 255 niveaux de turquoise : cat toto.rvb | perl -e 'while ($ch=<STDIN>) { printf("%c%c%c",0,$ch,$ch) }' > toto.rgb
En 255 niveaux de magenta : cat toto.rvb | perl -e 'while ($ch=<STDIN>) { printf("%c%c%c",$ch,0,$ch) }' > toto.rgb
En 255 niveaux de gris : cat toto.rvb | perl -e 'while ($ch=<STDIN>) { printf("%c%c%c",$ch,$ch,$ch) }' > toto.rgb
#include <stdio.h>
float x_min,x_max,y_min,y_max,x,y,x2,y2,zx,zy,z2x,z2y;
int res_x,res_y,px,py,n;int main()
{
x_min=-2.61;
x_max=1.33;
y_min=-1.56;
y_max=1.40;
res_x=320;
res_y=200;for (py=1;py<=res_y;py++)
for (px=1;px<=res_x;px++)
{
n=0;
x=x_min+px*(x_max-x_min)/res_x;
y=y_max-py*(y_max-y_min)/res_y;
zx=x;
zy=y;
z2x=zx;
z2y=zy;
x2=zx*zx;
y2=zy*zy;
do
{
zy=2*zx*zy;
zx=x2-y2;
zx=zx+z2x;
zy=zy+z2y;
x2=zx*zx;
y2=zy*zy;
n++;
if (n>=255)
break;
}
while(x2+y2<=4);
n--;
printf("%d\n",n);
}
}
Compilation : gcc mandelbrot.c
Exécution : ./a.out > toto.rvb
Remarque : en modifiant la valeur des constantes x_min, x_max, y_min et y_max il est très facile d'effectuer des zooms dans l'ensemble de Mandelbrot.
#include <iostream>
using namespace std;
float x_min,x_max,y_min,y_max,x,y,x2,y2,zx,zy,z2x,z2y;
int res_x,res_y,px,py,n;
int main(void) {
x_min=-2.61;
x_max=1.33;
y_min=-1.56;
y_max=1.40;
res_x=800;
res_y=600;for (py=1;py<=res_y;py++)
for (px=1;px<=res_x;px++)
{
n=0;
x=x_min+px*(x_max-x_min)/res_x;
y=y_max-py*(y_max-y_min)/res_y;
zx=x;
zy=y;
z2x=zx;
z2y=zy;
x2=zx*zx;
y2=zy*zy;
do
{
zy=2*zx*zy;
zx=x2-y2;
zx=zx+z2x;
zy=zy+z2y;
x2=zx*zx;
y2=zy*zy;
n++;
if (n>=255)
break;
}
while(x2+y2<=4);
n--;
cout << n << "\n";
}return 0;
}
Compilation : g++ main.cpp
Exécution : ./a.out > toto.rvb
program mandelbrot
real:: x_min,x_max,y_min,y_max,x,y,x2,y2,zx,zy,z2x,z2y
integer:: res_x,res_y,px,py,nx_min=-2.61
x_max=1.33
y_min=-1.56
y_max=1.40
res_x=320
res_y=200do py=1,res_y
do px=1,res_x
n=0
x=x_min+px*(x_max-x_min)/res_x
y=y_max-py*(y_max-y_min)/res_y
zx=x
zy=y
z2x=zx
z2y=zy
x2=zx*zx
y2=zy*zy
do while (x2+y2<=4)
zy=2*zx*zy
zx=x2-y2
zx=zx+z2x
zy=zy+z2y
x2=zx*zx
y2=zy*zy
n=n+1
if (n>=255) then
exit
end if
end do
print *,n
end do
end do
end
Compilation : gfortran mandelbrot.f90
Exécution : ./a.out > toto.rvb
class mandelbrot {
public static void main(String [] arguments)
{
float x_min,x_max,y_min,y_max,x,y,x2,y2,zx,zy,z2x,z2y;
int res_x,res_y,px,py,n;x_min=-2.61f;
x_max=1.33f;
y_min=-1.56f;
y_max=1.40f;
res_x=320;
res_y=200;for (py=1;py<=res_y;py++)
for (px=1;px<=res_x;px++)
{
n=0;
x=x_min+px*(x_max-x_min)/res_x;
y=y_max-py*(y_max-y_min)/res_y;
zx=x;
zy=y;
z2x=zx;
z2y=zy;
x2=zx*zx;
y2=zy*zy;
while(x2+y2<=4)
{
zy=2*zx*zy;
zx=x2-y2;
zx=zx+z2x;
zy=zy+z2y;
x2=zx*zx;
y2=zy*zy;
n++;
if (n>=255)
break;
}
System.out.println(n);
}
}
}
Compilation : javac mandelbrot.java
Exécution : java mandelbrot > toto.rvb
scale=20
x_min=-2.61
x_max=1.33
y_min=-1.56
y_max=1.40
res_x=320
res_y=200for (py=1;py<=res_y;py++)
for (px=1;px<=res_x;px++)
{
n=0
x=x_min+px*(x_max-x_min)/res_x
y=y_max-py*(y_max-y_min)/res_y
zx=x
zy=y
z2x=zx
z2y=zy
x2=zx*zx
y2=zy*zy
while(x2+y2<=4)
{
zy=2*zx*zy
zx=x2-y2
zx=zx+z2x
zy=zy+z2y
x2=zx*zx
y2=zy*zy
n=n+1
if (n>=255)
break
}
n
}
quit
Exécution : bc -q mandelbrot.bc > toto.rvb
L'avantage d'utiliser BC pour calculer une image fractale par rapport à un langage de programmation classique (comme C, Python, Perl, Java, Fortran, etc.) est de pouvoir très facilement augmenter la précision des nombres réels grâce à la commande scale= au début du programme. Avec un langage classique la précision est de 16 chiffres après la virgule, avec BC elle peut être quelconque (exemple : scale=1000 pour obtenir 1000 chiffres après la virgule).
En cas d'un nombre d'itérations supérieur à 255, BC peut se contenter d'envoyer sur sa sortie standard la valeur de n (comprise entre 0 et 16777215 en décimal), puis Perl, qui convertit le fichier texte à colonne unique en fichier binaire pourra se charger de décomposer n en 3 octets. Voici la ligne de commande utilisant Perl permettant de convertir un fichier .rvb possédant une seule colonne contenant la valeur en décimal de chaque pixel (entre 0 et 16777215 en décimal, soit entre 0x0 et 0xFFFFFF en hexadécimal) :
cat toto.rvb | perl -e 'while ($n=<STDIN>) { printf("%c%c%c",($n>>16)&255,($n>>8)&255,$n&255) }' > toto.rgb
La ligne de commande ci-dessus utilise le canal bleu comme couleur dominante. Si on souhaite obtenir une image avec la couleur verte comme couleur dominante on tapera :
cat toto.rvb | perl -e 'while ($n=<STDIN>) { printf("%c%c%c",($n>>16)&255,$n&255,($n>>8)&255) }' > toto.rgb
Grâce à BC qui permet de faire des calculs avec des nombres à virgule flotante d'une précision quelconque, il est désormais possible de calculer des images fractales au delà de la profondeur maximale autrisée par les langages de programmation classiques, et avec jusqu'à 16 millions d'itérations (entier sur 3 octets).
Enfin, voici 12 interpréteurs ou compilateurs disponibles sous Linux pour réaliser des petits programmes permettant de calculer des images mathématiques dans différents langages de programmation :
Compilateur |
|||
|
|||
|
|
||
|
|
||
|
|
ImageMagick est une suite logicielle proposant un ensemble de programmes permettant d'effectuer toutes sortes de traitements sur les images en ligne de commande. Parmi les commandes il y a :
convert : convertit une image dans un autre format ou en lui ajoutant certaines caractéristiques (taille, couleur, texte, etc.)
mogrify : modifit directement un fichier image (sans en faire une copie dans un second fichier)
identify : donne des informations multiples sur une image
display : affiche une image à l'écran
Nous n'allons pas voir ici l'étendue des possibilités (pratiquement infinies ...) d'ImageMagick, mais seulement les actions utiles pour améliorer les images mathématiques créée ci-dessus dans un shell Linux. Les 2 actions principales dont on a besoin ici sont :
Pour ajouter du texte à une image on utilise :
mogrify -fill "rgb(255,0,0)" -pointsize 24 -annotate +20+460 'www.gecif.net' julia.png
Pour ajouter du texte pendant la conversion on ajoute les options sur la ligne de commande de convert :
convert -size 640x480 -depth 8 -fill "rgb(255,0,0)" -pointsize 24 -annotate +20+460 'www.gecif.net' julia.rgb julia.png ; display julia.png
Pour obtenir des informations sur une image on utilise identify :
identify julia.png
julia.png PNG 640x480 640x480+0+0 8-bit DirectClass 95KB 0.000u 0:00.000
En filtrant la commande suivante on obtient toutes sorte d'infomations, dont des statistiques sur les couleurs :
identify -verbose julia.png
L'option -format du programme identify permet de demander une liste d'informations sous forme d'une chaine formatée (comme printf) :
identify -format "%f,%w,%h" julia.png
julia.png,640,480
Les options de la chaine formatée sont :
\n newline
\r carriage return
< less-than character.
> greater-than character.
& ampersand character.
%% a percent sign
%b file size of image read in
%c comment meta-data property
%d directory component of path
%e filename extension or suffix
%f filename (including suffix)
%g layer canvas page geometry (equivalent to "%Wx%H%X%Y")
%h current image height in pixels
%i image filename (note: becomes output filename for "info:")
%k CALCULATED: number of unique colors
%l label meta-data property
%m image file format (file magic)
%n number of images in current image sequence
%o output filename (used for delegates)
%p index of image in current image list
%q quantum depth (compile-time constant)
%r image class and colorspace
%s scene number (from input unless re-assigned)
%t filename without directory or extension (suffix)
%u unique temporary filename (used for delegates)
%w current width in pixels
%x x resolution (density)
%y y resolution (density)
%z image depth (as read in unless modified, image save depth)
%A image transparency channel enabled (true/false)
%C image compression type
%D image GIF dispose method
%G original image size (%wx%h; before any resizes)
%H page (canvas) height
%M Magick filename (original file exactly as given, including read mods)
%O page (canvas) offset ( = %X%Y )
%P page (canvas) size ( = %Wx%H )
%Q image compression quality ( 0 = default )
%S ?? scenes ??
%T image time delay (in centi-seconds)
%U image resolution units
%W page (canvas) width
%X page (canvas) x offset (including sign)
%Y page (canvas) y offset (including sign)
%Z unique filename (used for delegates)
%@ CALCULATED: trim bounding box (without actually trimming)
%# CALCULATED: 'signature' hash of image values
Création d'une palette entre 2 couleurs RVB :
convert -size 100x100 gradient:"rgb(255,0,0)-rgb(0,0,255)" palette.png
L'option hald crée aussi des palettes :
convert -size 50x1000 hald:'rgb(0,255,0)-rgb(0,0,0)' -rotate 90 palette.png
Récupère les couleurs uniques dans une image pour en faire une palette d'1 pixel de haut :
convert image.png -unique-colors palette.png
Pour colorer une image avec une palette il faut utiliser l'option -hald-clut de convert :
convert julia.png palette.png -hald-clut julia_coloree.png
Ou l'option -clut qui agit différemment :
convert julia.png palette.png -clut julia_coloree.png
L'image palette.png sera lu de gauche à droite seulement sur la première ligne (elle peut avoir une hauteur de 1 pixel seulement).
Si la palette est un segment pris dans le cube chromatique (segment entre 2 points RVB), elle peut être créée par convert grâce à gradient :
convert -size 100x100 gradient:'rgb(200,100,0)-rgb(50,0,200)' -rotate 90 palette.png
Par défaut les variations de couleur réalisées par gradient sont verticales, d'où l'option -rotate 90 pour créer une palette (variations horizontales).
Pour une palette plus complexe on la créera dans l'éditeur de dégradé de The Gimp, puis on crée une palette en niveau de vert qu'on colorise avec The Gimp :
convert -size 50x1000 gradient:'rgb(0,255,0)-rgb(0,0,0)' -rotate 90 palette.png
La largeur de l'image (1000 ci-dessus) donne le nombre de couleurs de la palette. Si la palette contient 1000 couleurs mais que l'image à colorer ne contient que 200 couleurs différentes, elle utilisera seulement les 200 premières couleurs de la palette (faible contraste).
Quelles sont les différentes options de convert ?
En lançant convert sans paramètre on obtient déjà les options principales :
convert
L'option -list de convert permet de connaître la liste des paramètres utilisables pour une option donnée. Par exemple, pour connaître tous les noms des couleurs utilisables avec l'option -color on tape :
convert -list color
Pour connaître la liste utilisable avec -list on tape :
convert -list list
Dans cette liste on trouve colorspace. L'option -colorspace de convert permet de définir le modèle de colorisation à utiliser. Si on tape convert -list colorspace on obtient tous les modèles de colorisation utilisables. Parmi eux on retrouve le modèle RGB utilisé ici.
Pour préciser qu'on utilise le modèle de colorisation RVB (vraies couleurs sur 3 octets Rouge Vert Bleu), on pourra ajouter -colorspace RGB sur la ligne de commande de convert.
De même l'option -channel de convert permet d'agir sur un seul canal de couleur. Mais comment sont nommées les canaux de couleur pour convert ? Pour le savoir on tapera :
convert -list channel
Les autres options de convert à expérimenter et liées à la colorisation des images sont :
-colors
-colorize
-colormap
-color-matrix
-cycle
-remap