site sans réclame PythonCont@ctS'abonner

Description et génération des images PNM et PAM

P

NM (Portable aNy Map) est un format de fichier «image» inventé par Jef Poskanzer dans les années 80. Les formats P5 (nuances de gris, données binaires) et P6 (couleurs, données binaires) sont reconnus par l'interface graphique Tkinter.

Bien que pouvant portant l'extension générique .pnm, trois autres sont plus spécifiques:

Les lignes des fichiers ASCII ne peuvent normalement comporter plus de 70 caractères, probablement en raison de la norme RFC822 concernant les courriers électroniques (P1, P2 et P3 ont été créés pour cela). Cette limitation ne semble pas gêner les systèmes actuels.

Il ne s'agit en aucun cas de formats compressés, mais ces images sont faciles à générer. Un fichier PNM comprend successivement une signature pour le type de l'image (P1 à P6), les éventuels commentaires (une ou plusieurs ligne(s) commençant par le dièse '#'), largeur et hauteur séparées par un espace, la valeur maximale pour les images en valeur de gris ou en couleur, un espace ou une fin de ligne avant la suite des valeurs (ou de triplets de valeurs) pour chaque pixel:

P3
# ceci n'est que le début d'un fichier
# au format PNM en couleurs et en ASCII
55 46 4
0 1 3  2 4 3  1 4 0  4 1 3  2 3 1  4 2 1  4 3 2
...

En GNU/Linux Debian GNOME ou Mate-Desktop, les 6 formats PNM sont lisibles par le système. +

P1: Noir/Blanc, fichier ASCII

Les fichiers images en noir et blanc comprennent la signature P1, la largeur et la hauteur de l'image exprimées en pixels, puis une suite de valeurs pour chaque pixels exprimée en caractères ASCII (0 pour le blanc ou 1 pour le noir). L'exemple suivant a été organisé pour être facilement lisible par un humain:

P1
6 4
1 1 1 1 1 1
1 0 0 0 0 1
1 0 0 0 0 1
1 1 1 1 1 1

L'image fait 6 pixels de large et 4 de hauteur: il faura l'agrandir pour la visualiser. Il faut deux caractères (0 ou 1 et l'espace). Une image de 1000x1000 pixels générera un fichier d'un peu plus de deux mégaoctets. Le fichier ci-dessus aurait pu être écrit:

P1 6 4 111111100001100001111111

...un fichier de 1000x1000 écrit en une seule ligne pesant alors un million et quelques octets.

Le script qui suit génère l'image d'un cercle noir sur fond blanc, à partir du rayon désiré exprimé en pixel. Il teste les pixels un à un pour savoir s'ils sont situés à la distance "rayon" du point central: racine carrée de la somme des carrés de la différence des abscisses et des ordonnées avec le point central. Un fichier rond-diametre.pnm (cela aurait pu être .pbm) est généré.

#! /usr/bin/python -Qnew

rayon=input("rayon: ")  # transformer en int() si python3!
diametre=rayon*2+1
diam=str(diametre) # chaine reprenant l'information sur le diametre
fichier="P1 %s %s\n" %(diam,diam) # entete de fichier

for i in range(diametre):
  for j in range(diametre):
    if round((abs((i-rayon)**2)+abs((j-rayon)**2))**.5)==rayon:
      fichier+="1"
    else:
      fichier+="0"
  fichier+="\n"

han=open("rond"+diam+".pnm","w")
han.write(fichier)
han.close()

P2: images en nuances de gris, fichier ASCII

Les fichiers d'image en valeurs de gris commencent par la signature P2, suivie de la largeur et la hauteur de l'image exprimées en pixels puis de la valeur maximale (jusqu'à 255) et enfin de la valeur de chaque pixel en ASCII. Les valeurs pour cet exemple vont de 0 (noir) à 15 (blanc), avec les 14 nuances de gris intermédiaires.

P2 9 8 15
15 14 13 12 11 10 9 8 7
14 13 12 11 10  9 8 7 6
13 12 11 10  9  8 7 6 5
12 11 10  9  8  7 6 5 4
11 10  9  8  7  6 5 4 3
10  9  8  7  6  5 4 3 2
 9  8  7  6  5  4 3 2 1
 8  7  6  5  4  3 2 1 0

Notes

P3: images en couleurs, fichier ASCII

Les fichiers d'image en couleurs commencent par la signature P3, suivie de la largeur et la hauteur de l'image exprimées en pixels puis de la valeur maximale (jusqu'à 255), chaque pixel étant codé par trois valeurs représentant les lumières fondamentales: rouge, vert et bleu, chacune de 0 (rien) à la valeur maximale précisée:

P3 3 3 255
  0   0 0  255 255 255  0   0   0
255 255 0  255   0 255  0 255 255
255   0 0    0 255   0  0   0 255

Le nombre d'espaces entre chaque valeur n'a pas d'importance, mais il n'est pas possible de les supprimer pour les fichiers d'images en couleur. Néanmoins, pour cet exemple à deux valeurs 0 ou 255, la limitation des couleurs fondamentales aux valeurs 0 et 1 (1 en fin de première ligne) est plus économique, le fichier affichant une image strictement équivalente à la précédente:

P3 3 3 1
0 0 0 1 1 1 0 0 0
1 1 0 1 0 1 0 1 1
1 0 0 0 1 0 0 0 1

Rappelons que chaque pixel est défini par trois valeurs.

P4: images en noir et blanc, données brutes

Les fichiers commençant par la signature P4 concernent les images en noir et blanc, où huit pixels sont codés sur un octet.

P4 16 16 
 ...données sous forme d'octets (0 à 255), sans espace ni retour à la ligne

Huit premiers pixels blanc-noir-noir-blanc-blanc-noir-noir-blanc donne le nombre binaire 01100110, soit l'octet 102, qui correspond au caractère f.

P4 8 1
f

Certaines valeurs ne correspondent pas à des caractères imprimables ; il faudra utiliser une routine pour agréger les bits et chr() pour former la chaîne des données brutes. Le format P1 est plus pratique pour coder une image PNM/PBM en noir et blanc, mais il est 8 fois plus lourd (un octet par pixel).

P5: images en nuances de gris, données brutes

Les fichiers commençant par les signatures P5 concernent les images en nuances de gris, chaque octet codant un pixel, ce qui est finalement plus pratique que le format P4.

P5
# le '255' final est la valeur maximale:
20 20 255
... et un octet par pixel

Contrairement aux formats noir/blanc, 0 est l'absence de lumière, le noir, et 255 est le blanc. Il peut donc y avoir 254 nuances de gris. Ces octets s'inscrivent avec chr(valeur). N'oubliez pas un saut de ligne ou un espace entre l'en-tête (première ligne) et les données. Un exemple

#! /usr/bin/python
import random

entete= "P5 30 20 255 " # l'espace final est necessaire
matrice=[] # les octets s'ajouteront dans une liste...
for i in range(30*20):
  matrice+=[chr(random.randrange(256))]

fichier=entete+"".join(matrice) # transformation de la liste de caracteres en chaine

han=file("essai.pgm","w")
han.write(fichier)
han.close()

Ce script a génère une image que l'interface graphique Tkinter associée à python peut afficher.

P6: images en couleurs, données brutes

Les fichiers commençant par les signatures P6 concernent les images en couleurs, chaque pixel étant codé par trois octets, contenant respectivement les quantités de rouge, vert et bleu.

P5
# 255 est pour ce fichier la valeur maximale pour les trois couleurs, suivie d'un espace:
20 20 255 
... et trois octets par pixel

0 est l'absence de couleur, et 255 respectivement le rouge, le vert et le bleu. Entre les deux, 254 nuances de rouge, de vert ou de bleu. Ces octets s'inscrivent avec chr(valeur). N'oubliez pas un saut de ligne ou un espace entre l'en-tête et les données. Un petit exemple:

#! /usr/bin/python
import random

entete= "P6 30 20 255 " # l'espace final est necessaire
matrice=[]
for i in range(30*20*3):
  matrice+=[chr(random.randrange(256))]

fichier=entete+"".join(matrice) # transformation de la liste de caracteres en chaine

han=file("essai.ppm","w")
han.write(fichier)
han.close()

Ce script génère une image formée de pixels colorés au hasard, que Tkinter peut charger et afficher.

P7: Portable Arbitrary Map (PAM)

Un format récapitule les trois formats précédents (P4, P5 et P6), en ajoutant la possibilité de la transparence et même de coder chaque composante de couleur sur deux octets (65536 nuances). Il est encore assez peu reconnu par les applications graphiques, mais il est possible de visualiser une image PAM avec ImageMagic et la transformer en PNG (format universel admettant la transparence). Si ImageMagic est présent sur votre système UNIX, la commande convert peut convertir une image PAM:

convert votreimage.pam votreimage.png

L'en-tête est plus long, plus souple et mieux défini:

P7
# premier lieu pour un commentaire
WIDTH 443
HEIGHT 87
DEPTH 4
MAXVAL 255
TUPLTYPE RGB_ALPHA
# en cas de dernier commentaire, nécessairement avant ENDHDR
ENDHDR
...données au format binaire chr(0) -> chr(255)...

Les paramètres du format de l'image sont à écrire, ligne après ligne, entre P7 et ENDHDR

Note 2016.05.29

Format expérimenté avec RGB_ALPHA, GRAYSCALE_ALPHA et BLACKANDWHITE_ALPHA, tous trois MAXVAL 255.

Le format noir et blanc est paradoxal: BLACKANDWHITE (avec ou sans _ALPHA) ne peut être écrit de façon économe avec 8 pixels par octet comme le permet le format P4, ce qui revient à une organisation des données selon le format GRAYSCALE_ALPHA avec les deux seules valeurs 0 (noir) et celle définie par MAXVAL (blanc).

Par ailleurs, pour une image en noir et blanc avec des nuances dans la transparence, il faut utiliser GRAYSCALE_ALPHA, avec par exemple MAXVAL à 255, en codant le noir avec 0 et le blanc avec la valeur de MAXVAL.