Images PGM et PPM en Java


Le but est de développer des programmes de traitement d'images en Java pouvant lire et écrire des fichiers au format PGM.

En Java, il n'existe pas de type compact permettant de stocker des valeurs de 0 à 255.
Si on privilégie la compacité, on peut utiliser le type byte qui code les valeurs de -128 à 127. Le gris moyen (valeur 128) est alors codé par le byte -128 et le blanc (valeur 255) est codée par le byte -1.
Si l'on doit faire de l'arithmétique sur les images ou, simplement, préserver la relation d'ordre naturelle sur les pixels, il est préférable d'utiliser un type plus "large". Par exemple, le type short qui code les valeurs de -32768 à 32767 est généralement suffisant et il ne coûte que deux octets par pixel. À l'extrème, pour de l'arithmémique flottante, on peut utiliser le type double qui coûte huits octets par pixel.

Utilisation des images au format PGM (niveaux de gris)

On dispose de la classe Pixmap qui regroupe les fonctions de lecture et d'écriture ainsi que des définitions générales. C'est une classe abstraite.

Pour l'instanciation, on utilisera l'une de ses sous-classes BytePixmap, ShortPixmap ou DoublePixmap. Ces sous-classes sont spécialisées par le type qui est utilisé pour le codage des pixels mais elles sont toutes définies selon le même modèle et, en particulier, elles ont 4 constructeurs. Par exemple :

  public ShortPixmap(int w, int h, short[] pixels);
  public ShortPixmap(int w, int h);
  public ShortPixmap(Pixmap src);
  public ShortPixmap(String fileName);
Le premier constructeur crée un ShortPixmap de dimensions w.h et dont le stockage des valeurs de pixels utilisera le tableau de short passé en paramètre.
Le second constructeur crée un ShortPixmap de dimensions w.h avec un nouveau tableau de type short, dont tous les éléments sont initialisés à 0.
Le troisième constructeur crée une copie du Pixmap argument en utilisant un nouveau tableau de pixels de type short. Il y a conversion automatique du type des valeurs de pixels, si nécessaire.
Le dernier constructeur crée un ShortPixmap à partir du fichier PGM dont le nom est passé en paramètre.

Les dimensions d'un Pixmap sont accessibles dans ses champs width et height. Le champ size contient le produit des deux précédents.
Les valeurs des pixels sont accessibles dans le tableau data du type approprié. La référence au pixel de coordonnées (i,j) (avec (0,0) en haut à gauche) est data[j*width+i].
Quelque soit le "type" d'un Pixmap, on peut obtenir une copie de ses valeurs de pixels dans un nouveau tableau, par l'une des méthodes (selon le type voulu) :

  public byte[] getBytes();
  public short[] getShorts();
  public double[] getDoubles();
Enfin, la méthode :
  public void write(String fileName);
permet de sauver un Pixmap dans un fichier PGM dont le nom est passé en paramètre.

Utilisation des images au format PPM (couleur)

On dispose de la classe abstraite RGBPixmap qui regroupe 3 objets Pixmap de même "type", un pour chaque composante R,G,B.
Pour l'instanciation, on utilisera, par exemple, ByteRGBPixmap qui dispose de 5 constructeurs :
  public ByteRGBPixmap(int w, int h, 
                       byte[] rPixels, byte[] gPixels, byte[] bPixels);
  public ByteRGBPixmap(int w, int h);
  public ByteRGBPixmap(Pixmap r, Pixmap g, Pixmap b);
  public ByteRGBPixmap(RGBPixmap p);
  public ByteRGBPixmap(String fileName);
Tous ces constructeurs créent 3 nouveaux BytePixmap à partir des paramètres fournis.
Pour le dernier constructeur, le fichier doit être au format PPM.

Les dimensions d'un RGBPixmap sont accessibles dans ses champs width et height. Le champ size contient le produit des deux précédents.
Les 3 Pixmap composantes, de sous-classe appropriée, sont accessibles par les champs r, g et b. Par exemple, la référence à la composante rouge du pixel de coordonnées (i,j) est r.data[j*width+i].

Enfin, la méthode :

  public void write(String fileName);
permet de sauver un RGBPixmap dans un fichier PPM dont le nom est passé en paramètre.

Exemples

Voici un exemple simple de programme qui produit à la fois un négatif et une reproduction d'un fichier PGM :
import java.io.*;

public class DemoPixmap {
  
  public static void main(String[] args) {
    BytePixmap p1;
    try {
      p1 = new BytePixmap(args[0]);         // lecture du fichier
    } catch (IOException e) { p1 = null; System.exit(0); }
    for ( int i=0; i<p1.size; i++)           // pour tous les pixels :
      p1.data[i] = (byte)(255 - p1.data[i]); // oui cela marche tout de même !
    ShortPixmap p2 = new ShortPixmap(p1);  // clonage
    for ( int i=0; i<p2.size; i++)           // pour tous les pixels :
      p2.data[i] = (short)(255 - p2.data[i]);// là c'est plus évident !
    p1.write("neg_"+args[0]);                // écriture de l'image en négatif
    p2.write("copy_"+args[0]);               // écriture de l'image d'origine
  }

}
Voici un autre exemple de programme qui réalise une permutation des trois composantes d'un fichier PPM :
import java.io.*;

public class DemoRGBPixmap {
  
  public static void main(String[] args) {
    ByteRGBPixmap rgb1;
    try {
      rgb1 = new ByteRGBPixmap(args[0]);     // lecture du fichier
    } catch (IOException e) { rgb1 = null; System.exit(0); }
    ByteRGBPixmap rgb2 = new ByteRGBPixmap(rgb1.g, rgb1.b, rgb1.r);
    rgb2.write("rot"+args[0]);
  }

}

Visualisation des images

Voici deux exemples de programmes qui réalisent l'affichage d'un fichier PGM ou PPM dans un Frame de l'AWT.

Et voici, de même, pour afficher un fichier PGM ou PPM dans un DrawingFrame de MacLib.


URL: https://www.enseignement.polytechnique.fr/profs/informatique/Philippe.Chassignet/PGM/pgm_java.html

Pour toutes suggestions, commentaires ou remarques, email : Philippe.Chassignet@polytechnique.fr