Page d'accueilFindIt !ContactLa gestion de l'interface utilisateurPlus loin avec Java...

La bibliothèque JavaTM 1.0

Table des matièresHierarchie des classes

CJava

 Les images

La génération d'images
Le chargement des images
La création d'images
Transformer des images avec un filtre
Comment ça marche ?
Gestion d'animations

 

La génération d'images

Les images, instances de la classe Image, peuvent provenir de deux sources différentes :

Le chargement des images

Comme il est expliqué au début de ce chapitre, la méthode getImage () permet créer une instance d'une image. Pour initialiser et surveiller le chargement d'une image et l'utiliser quand elle est partiellement ou entièrement chargée, il existe plusieurs moyens :

De plus, les méthodes checkImage () des classes Component et Toolkit permettent de vérifier l'état du chargement d'une image. Ces méthodes prennent en dernier paramètre une instance d'une classe implémentant l'interface ImageObserver, dont la méthode imageUpdate () est appelée pour lui communiquer l'état de l'image.

La classe java.awt.MediaTracker

Cette classe permet de gérer le chargement d'une ou plusieurs images. Elle est utilisée par les applets BoutonsNavigation, AnimationFleche et Horloge pour charger les images dont elles ont besoin. Les méthodes addImage () permettent de donner un ensemble d'images à charger et les méthodes waitForID () et waitForAll () de lancer le chargement des images.

Champs
 public final static int ABORTED
 public final static int COMPLETE
 public final static int ERRORED
 public final static int LOADING

Les méthodes statusID () et statusAll () renvoie une combinaison de ces constantes pour indiquer l'état du chargement des images (annulé, terminé, erreur ou en cours de chargement).

Constructeur
public MediaTracker (Component comp)

Construit une instance de MediaTracker. comp désigne un composant dans lequel sera visualisé les images à charger.

Méthodes
public void addImage (Image image, int id)
public synchronized void addImage (Image image, int id,
                                    int width, int height)

Ces méthodes permettent d'ajouter une image à charger. width et height permettent d'éventuellement redimensionner l'image dès son chargement. id est un identifiant numérique permettant de rassembler les images, pour les charger par groupe avec les méthodes ...ID () de cette classe.

public void waitForID (int id)
public synchronized boolean waitForID (int id, long ms)
public void waitForAll ()
public synchronized boolean waitForAll (long ms)

Ces méthodes permettent de lancer le chargement des images d'identifiant id ou de toutes les images et met en attente le thread courant jusqu'à la fin de leur chargement ou pendant le laps de temps ms millisecondes.

public int statusID (int id, boolean load)
public int statusAll (boolean load)

Ces méthodes renvoient l'état du chargement des images d'identifiant id ou de toutes les images. La valeur renvoyée est une combinaison des constantes ABORTED, COMPLETE, ERRORED et LOADING. Si load est égal à true, le chargement des images est démarré.

public boolean checkID (int id)
public synchronized boolean checkID (int id, boolean load)
public synchronized boolean checkAll ()
public boolean checkAll (boolean load)

Ces méthodes renvoient true si les images d'identifiant id ou toutes les images sont chargées correctement (état égal à COMPLETE). Si load est égal à true (égal à false par défaut), le chargement des images est démarré.

public synchronized boolean isErrorID (int id)
public synchronized boolean isErrorAny ()

Ces méthodes renvoient true si une erreur est survenue pendant le chargement d'une des images d'identifiant id ou de n'importe quelle image (état égal à ERRORED).

public synchronized Object [ ] getErrorsID (int id)
public synchronized Object [ ] getErrorsAny ()

Ces méthodes renvoient un tableau contenant toutes les images éventuellement d'identifiant id, dont le chargement a produit une erreur.

Exemples

Applets BoutonsNavigation, AnimationFleche et Horloge.

L'interface java.awt.image.ImageObserver

Cette interface est utilisée pour surveiller le chargement d'une image. Une classe implémentant cette interface est requise par la méthode drawImage () de la classe Graphics, les méthodes prepareImage () et checkImage () des classes Component et Toolkit et les méthodes getWidth () et getHeight () de la classe Image. Cette interface est notamment implémentée par la classe Component pour mettre à jour un composant contenant une image au fur et à mesure que celle-ci est chargée.

Champs

Le paramètre infoflags de la méthode imageUpdate () utilise une combinaison des constantes suivantes pour indiquer quelles caractéristiques d'une image sont connues au moment du chargement d'une image :

public final static int WIDTH
public final static int HEIGHT

La largeur ou la hauteur de l'image sont disponibles.

public final static int PROPERTIES

Les propriétés de l'image sont disponibles.

public final static int SOMEBITS
public final static int FRAMEBITS

Une partie de l'image est chargée.

public final static int ALLBITS

Le chargement de l'image est terminé et toutes ses caractéristiques sont disponibles.

public final static int ERROR

Une erreur est survenue pendant le chargement de l'image. Combiné avec ABORT.

public final static int ABORT

Le chargement de l'image a été annulé.

Méthodes
public boolean imageUpdate (Image image, int infoflags,
                              int x, int y,
                              int width, int height)

Cette méthode est appelée au cours du chargement d'une image pour indiquer quelles en sont les caractéristiques connues. infoflags est une combinaison des constantes WIDTH, HEIGHT, PROPERTIES, SOMEBITS, FRAMEBITS, ALLBITS, ERROR et ABORT. x, y, width et height sont significatives suivant la valeur de infoflags.
imageUpdate () doit renvoyer true si elle a besoin d'être encore appelée pour lui communiquer les phases suivantes du chargement d'une image. Généralement, false est renvoyé en cas d'erreur.

L'applet suivante utilise l'interface ImageObserver pour attendre la fin du chargement d'une image et l'afficher :

La création d'images

Comme le montre l'applet MultiImages, une image peut être créée grâce à la méthode createImage (int width, int height) de la classe Component. Cette méthode crée une image vierge dans laquelle vous pouvez dessiner grâce aux méthodes de dessin de la classe Graphics.
Il existe une deuxième version de la méthode createImage () disponible dans les classes Component et Toolkit : createImage (ImageProducer producer). Le paramètre producer doit être d'une classe qui implémente l'interface ImageProducer. Le package java.awt.image fournit deux classes qui implémentent cette interface :

La classe java.awt.image.MemoryImageSource

Cette classe qui implémente l'interface ImageProducer permet de créer une image à partir d'un tableau décrivant la couleur de chacun des points (ou pixels) d'une image.

Constructeurs
public MemoryImageSource (int width, int height,
                            int pix [ ], int offset, int scan)
public MemoryImageSource (int width, int height,
                            int pix [ ], int offset, int scan,
                            Hashtable props)
public MemoryImageSource (int width, int height,
                            ColorModel cm,
                            int pix [ ], int offset, int scan)
public MemoryImageSource (int width, int height,
                            ColorModel cm,
                            int pix [ ], int offset, int scan,
                            Hashtable props)
public MemoryImageSource (int width, int height,
                            ColorModel cm,
                            byte pix [ ], int offset, int scan)
public MemoryImageSource (int width, int height,
                            ColorModel cm,
                            byte pix [ ], int offset, int scan,
                            Hashtable props)

Ces constructeurs permettent de créer une image de largeur width et de hauteur height à partir du tableau pix [ ] de type byte ou int. pix [ ] décrit la couleur de chacun des points de l'image ligne par ligne. offset permet de donner le premier point du tableau à utiliser et scan le nombre de pixels par ligne dans le tableau pix [ ] au cas où cette valeur serait différente de width. cm permet de spécifier un modèle de couleur (par défaut égal au modèle RGB par défaut), et props décrit éventuellement les propriétés associées à l'image.

Méthodes
public synchronized void addConsumer (ImageConsumer ic)
public synchronized boolean isConsumer (ImageConsumer ic)
public synchronized void removeConsumer (ImageConsumer ic)
public void startProduction (ImageConsumer ic)
public void requestTopDownLeftRightResend (ImageConsumer ic)

Implémentation des méthodes de l'interface ImageProducer.

L'applet suivante, comme l'applet MultiImages, crée un nuancier en rouge et vert mais cette fois-ci en utilisant la classe MemoryImageSource :

Transformer des images avec un filtre

Java comporte le concept de filtres qui permettent de transformer une image en une autre. Ces filtres dérivent de la classe ImageFilter, et permettent toute sorte de transformation. Le package java.awt.image fournit deux classes de filtre dérivées de la classe ImageFilter, les classes CropImageFilter qui permet d'extraire une partie d'une image, et RGBImageFilter qui permet de transformer la couleur de chacun des points d'une image ; vous pouvez aussi imaginer toute sorte de filtre.
Une image filtrée est créée grâce à la méthode createImage (ImageProducer producer) des classes Component ou Toolkit, avec le paramètre producer égal à une une instance de la classe FilteredImageSource.
Pour plus d'information sur le fonctionnement du filtrage d'images, voir Comment ça marche ?

La classe java.awt.image.FilteredImageSource

Cette classe qui implémente l'interface ImageProducer permet de créer une image filtrée. Le constructeur de cette classe prend en paramètre une instance d'une classe implémentant l'interface ImageProducer (obtenue par exemple grâce à la méthode getSource () de la classe Image) et une instance d'une classe de filtre. Les applets NegatifImage, Compteur et AnimationFleche utilise cette classe pour créer des images filtrées.

Constructeur
public FilteredImageSource (ImageProducer orig,
                               ImageFilter   imgf)
Méthodes
public synchronized void addConsumer (ImageConsumer ic)
public synchronized boolean isConsumer (ImageConsumer ic)
public synchronized void removeConsumer (ImageConsumer ic)
public void startProduction (ImageConsumer ic)
public void requestTopDownLeftRightResend (ImageConsumer ic)

Implémentation des méthodes de l'interface ImageProducer réalisant le filtrage.

La classe java.awt.image.ImageFilter

Cette classe qui implémente les interfaces ImageConsumer et Cloneable, est la super classe de toutes les classes permettant de réaliser un filtrage. Cette classe n'a aucun effet sur l'image à filtrer (filtre nul). Le package java.awt.image fournit les deux classes de filtre CropImageFilter et RGBImageFilter.

Champ
protected ImageConsumer consumer

Consommateur final d'images de l'image filtrée. Une fois modifiées, les données doivent être renvoyée à ce consommateur.

Constructeur
public ImageFilter ()
Méthodes
public void setDimensions (int width, int height)
public void setProperties (Hashtable props)
public void setColorModel (ColorModel model)
public void setHints (int hints)
public void setPixels  (int x, int y, int w, int h,
                         ColorModel model,
                         byte pixels [ ],
                         int off, int scansize)
public void setPixels  (int x, int y, int w, int h,
                         ColorModel model,
                         int pixels [ ],
                         int off, int scansize)
public void imageComplete (int status)

Implémentation des méthodes de l'interface ImageConsumer pour renvoyer l'image non modifiée à consumer.

public ImageFilter getFilterInstance (ImageConsumer ic)
public void resendTopDownLeftRight (ImageProducer ip)
public Object clone ()
 
Exemple

Applet Compteur.

La classe java.awt.image.CropImageFilter

Cette classe qui dérive de la classe ImageFilter permet d'extraire une partie d'une image. Elle est intéressante pour récupérer différentes images d'une image téléchargée. En effet, il est plus rapide de charger un seul fichier et d'en extraire plusieurs images que de charger plusieurs images, car une seule requête est nécessaire et la taille d'un fichier compressé comportant plusieurs images est plus petite que la somme des tailles des fichiers compressés de ces images.

Constructeur
public CropImageFilter (int x, int y, int width, int height)

Construit un filtre permettant d'extraire une image aux coordonnées (x,y), de largeur width et de hauteur height.

Méthodes
public void setProperties (Hashtable props)
public void setDimensions (int w, int h)
public void setPixels  (int x, int y, int w, int h,
                         ColorModel model,
                         byte pixels [ ],
                         int off, int scansize)
public void setPixels  (int x, int y, int w, int h,
                         ColorModel model,
                         int pixels [ ],
                         int off, int scansize)

Ces méthodes outrepassent celles de la classe ImageFilter pour réaliser les opérations du filtre.

Exemple

Applet AnimationFleche.

La classe java.awt.image.RGBImageFilter

Cette classe abstract qui dérive de la classe ImageFilter permet de créer des classes de filtres modifiant la couleur des points d'une images. Il faut pour cela créer une classe dérivée de cette classe et implémenter la méthode filterRGB (int x, int y, int rgb) pour qu'elle renvoie la nouvelle couleur (modifiée ou non) du point de coordonnées (x,y). A la création de l'image filtrée avec la classe FilteredImageSource, l'ensemble des points de l'image originale est énuméré à travers cette méthode pour récupérer la couleur de chacun des points de la nouvelle image.

Champs
protected ColorModel origmodel
protected ColorModel newmodel
 
protected boolean canFilterIndexColorModel

Si le filtrage de la couleur ne dépend pas des coordonnées des points de l'image, il est conseillé de mettre ce champ à true.

Constructeur
public RGBImageFilter ()
Méthodes
public abstract int filterRGB (int x, int y, int rgb)

Cette méthode doit être outrepassée par les classes dérivées pour renvoyer la couleur du point de coordonnées (x,y) de l'image filtrée, sachant que la couleur de l'image originale est égale à rgb à ce point.

public void filterRGBPixels (int x, int y, int w, int h,
                               int pixels [ ],
                               int off,
                               int scansize)
 
public void substituteColorModel (ColorModel oldcm,
                                     ColorModel newcm)
public IndexColorModel filterIndexColorModel (IndexColorModel icm)
 
public void setColorModel (ColorModel model)
public void setPixels  (int x, int y, int w, int h,
                         ColorModel model,
                         byte pixels [ ],
                         int off, int scansize)
public void setPixels  (int x, int y, int w, int h,
                         ColorModel model,
                         int pixels [ ],
                         int off, int scansize)

Ces méthodes outrepassent celles de la classe ImageFilter pour réaliser les opérations du filtre.


Voici un exemple d'applet utilisant cette classe pour créer le négatif d'une image :

Applet NegatifImage

et le programme Java correspondant (à copier dans un fichier dénommé NegatifImage.java et invoqué à partir d'un fichier HTML) :

import java.applet.Applet;
import java.awt.*;
import java.awt.image.*;
 
public class NegatifImage extends Applet
{
  private Image image,
                negatifImage;
 
  public void init ()
  {      
    // Création d'une image et de son négatif
    image        = getImage (getCodeBase (), "rockfel.jpg");
    negatifImage = createImage (new FilteredImageSource
                                       (image.getSource (),
                                        new FiltreNegatif ()));
  }
 
  public void paint (Graphics gc)
  {
    if (image != null)
    {
      // Affichage des images
      gc.drawImage (image, 0, 0, this);
      gc.drawImage (negatifImage,
                     image.getWidth (this) + 10, 0, this);
    }
  }
}
 
// Classe FiltreNegatif transformant une couleur en son inverse
class FiltreNegatif extends RGBImageFilter
{
  public FiltreNegatif ()
  {
    // La transformation des couleurs ne dépend pas
    // des coordonnées des points de l'image
    canFilterIndexColorModel = true;
  }
 
  public int filterRGB (int x, int y, int rgb)
  {
    int alpha = rgb & 0xFF000000;
    // Transformation des composantes RGB en leur inverse
    int rougeInverse = (rgb & 0xFF0000) ^ 0xFF0000;
    int vertInverse  = (rgb & 0x00FF00) ^ 0x00FF00;
    int bleuInverse  = (rgb & 0x0000FF) ^ 0x0000FF;
    return alpha | rougeInverse | vertInverse | bleuInverse;
  }
}
 

Comment ça marche ?

Les classes décrites précédemment implémentent soit l'interface ImageProducer (classes MemoryImageSource et FilteredImageSource), soit l'interface ImageConsumer (classes PixelGrabber, ImageFilter et les classes qui en dérivent).
Bien qu'il soit entièrement possible d'utiliser ces classes sans connaître ces interfaces, vous vous demandez peut-être à quoi servent les interfaces ImageProducer et ImageConsumer et quel modèle elles respectent.
Tout d'abord, ces deux interfaces ne vont pas l'une sans l'autre. C'est un peu comme pour vous, consommateur d'images (ImageConsumer), et votre magnétoscope, producteur d'images (ImageProducer) : vous mettez en marche le magnétoscope pour voir un film. Vous pouvez être plusieurs à voir un même film et le magnétoscope n'a d'intérêt que si les images qu'ils diffusent sont vues. Souvenez-vous de cette analogie, elle vous aidera à mieux comprendre comment fonctionne le modèle de gestion des images du package java.awt.image.

Généralement en Java, un consommateur d'images est le système graphique de votre ordinateur qui attend qu'on lui transmette les pixels à afficher à l'écran. Un producteur d'images est capable de créer une image à partir d'un fichier GIF ou JPEG (la méthode getImage () de la classe Toolkit renvoie une instance de la classe Image dont le producteur peut être obtenu grâce à la méthode getSource ()) ou à partir d'une zone mémoire (via la classe MemoryImageSource).
Quand le consommateur d'images a besoin d'afficher une image, le producteur d'images est démarré en appelant la méthode startProduction () qu'implémente le producteur. Ce dernier renvoie alors au consommateur tous les renseignements (taille, modèle de couleur, couleur de chacun des pixels) qui lui permettront de construire l'image, en appelant successivement les méthodes setDimensions (), setColorModel (), setPixels () qu'implémente le consommateur.


figure 17. Génération d'une image

Quand toutes les données d'une image ont été transmises au consommateur ou en cas d'erreur, le producteur appelle la méthode imageComplete () qu'implémente le consommateur.
Le producteur peut éventuellement délivrer l'image par morceaux en appelant plusieurs fois la méthode setPixels (), ce qui permet au consommateur d'afficher l'image au fur et à mesure qu'elle est disponible. Par exemple, c'est ce qui se produit à l'affichage d'une image provenant d'un fichier téléchargé sur Internet : comme les données de l'image sont délivrées relativement lentement, on voit l'image qui se dessine petit à petit comme dans l'exemple d'applet ImageSimple.
Le producteur peut être aussi capable de générer plusieurs images pour créer un animation. Dans ce cas, il appelle imageComplete () à chaque fois qu'une image de l'animation (frame en anglais) a été entièrement décrite.
Comme il est possible que plusieurs personnes regardent un même film, un producteur d'images peut avoir plusieurs consommateurs qui lui demandent de leur envoyer les données d'une image.

En appliquant ce modèle de manière plus générale, il est possible d'imaginer toute sorte de classes de producteur ou de consommateur d'images, du moment qu'ils implémentent les interfaces ImageProducer ou ImageConsumer. Il est possible de créer par exemple une classe de consommateur d'images dont le but est d'écrire une image dans un fichier respectant tel ou tel format : c'est ce schéma qu'utilise la bibliothèque fournit par Acme, pour générer des sorties GIF.
La classe PixelGrabber est aussi une classe implémentant l'interface ImageConsumer : elle permet d'interroger la valeur des pixels d'une image. En fait, elle stocke dans un tableau les valeurs transmises par le producteur à l'appel de la méthode setPixels ().

Le filtrage d'images utilise aussi ce système, en intercalant des classes de producteur (FilteredImageSource) et de consommateur (dérivant de la classe ImageFilter qui implémente l'interface ImageConsumer) entre le producteur original et le consommateur final.


figure 18. Filtrage d'une image

Pour créer une image filtrée imageFiltree de classe FilteredImageSource, vous devez passer en paramètre au constructeur de cette classe un producteur d'image et un filtre dont la classe FiltreImage dérive de ImageFilter.
Quand le consommateur d'images appelle la méthode startProduction () sur imageFiltree, l'objet imageFiltree crée une instance spéciale de filtre filtreImageIC qui mémorise le consommateur final puis imageFiltree appele à son tour la méthode startProduction () sur le producteur original en lui passant en paramètre le nouveau consommateur d'images filtreImageIC.
Quand le producteur original produit l'image en appelant successivement les méthodes setDimensions (), setPixels (),... c'est donc sur le consommateur filtreImageIC. filtreImageIC rappelle ces méthodes sur le consommateur final avec des valeurs modifiées en fonction du filtre voulu. Comme c'est filtreImageIC qui transmet toutes les données de l'image au consommateur final, il peut créer tous les effets possibles, comme changer la taille ou les couleurs de l'image, voir même créer une animation sur une image qui était statique à l'origine !

Voici un exemple d'applet qui affiche l'image d'un nombre aléatoire et utilise la classe de filtre ImageFilterCounter dérivant de la classe ImageFilter. Cette classe permet de fabriquer l'image d'un nombre donné à partir d'une image décrivant les 10 chiffres de 0 à 9, dans 10 zones rectangulaires de taille égale. Globalement, quand le producteur appelle une des deux méthodes setPixels (), cette classe mémorise les pixels qu'on lui transmet, puis une fois qu'elle a une image complète, elle retransmet au consommateur final les images de chacun des chiffres du nombre à afficher.
Cette applet prend en paramètre le nom du fichier d'image contenant le dessin de tous les chiffres. Ceci permet de rendre le compteur sous différents aspects, comme le montrent les deux exemples suivants, l'un utilisant l'image JPEG chiffres1.jpg, l'autre le fichier GIF animé chiffres2.gif (l'animation d'un GIF n'est gérée qu'à partir de Java 1.1) :

Applet Compteur

Applet Compteur

Voici le programme Java correspondant (à copier dans un fichier dénommé Compteur.java et invoqué à partir d'un fichier HTML) :

import java.applet.Applet;
import java.awt.*;
import java.awt.image.*;
 
public class Compteur extends Applet
{
  private Image image;
 
  public void start ()
  {
    // Récupération de l'image originale dont le fichier 
    // est indiqué dans le paramètre de l'applet "image"
    Image imageOriginale = getImage (getCodeBase (), getParameter ("image"));    
    // Création d'une image filtrée avec un nombre aléatoire
    image = createImage (new FilteredImageSource
                           (imageOriginale.getSource (),
                            new ImageFilterCounter ((int)(Math.random () * 100000), 5)));
  }
 
  public void paint (Graphics gc)
  {
    if (image != null)
      // Affichage de l'image
      gc.drawImage (image, 0, 0, this);
  }
}
 
// Classe de filtre créant l'image d'un nombre à partir d'une
// image comportant tous les chiffres de 0 à 9
class ImageFilterCounter extends ImageFilter
{
  private int        valeur;
  private int        nbChiffres;
 
  private int        largeurImageOriginale;
  private int        hauteurImageOriginale;
      
  private byte []    bytePixels;
  private int  []    intPixels;
  
  private ColorModel model;
  
  public ImageFilterCounter (int  valeur,
                               int  nbChiffres)
  {
    this.valeur     = valeur;
    this.nbChiffres = nbChiffres;
  }
 
  // Implémentation des méthodes de ImageConsumer  
  public void setDimensions (int width, int height)
  {
    largeurImageOriginale = width;
    hauteurImageOriginale = height;
    
    // Renvoie la dimension de l'image au consommateur final
    consumer.setDimensions (nbChiffres * largeurImageOriginale / 10, height);
  }
  
  public void setHints (int hints)
  {
    // Positionnemennt de RANDOMPIXELORDER uniquement pour dire que 
    // les pixels dont renvoyés dans un ordre aléatoire
    consumer.setHints (  (  hints
                           | RANDOMPIXELORDER)
                       & ~(TOPDOWNLEFTRIGHT | COMPLETESCANLINES));
  }
 
  public void setColorModel (ColorModel model)
  {
    this.model = model;
    consumer.setColorModel (model);
  }
 
  public void setPixels (int x, int y, int width, int height,
                          ColorModel model, byte pixels [], int offset, int scansize)
  {
    if (bytePixels == null)
      bytePixels = new byte [largeurImageOriginale * hauteurImageOriginale];        
    copyPixels (x, y, width, height, pixels, offset, scansize, bytePixels);
  }
 
  public void setPixels (int x, int y, int width, int height,
                           ColorModel model, int pixels[], int offset, int scansize)
  {
    if (intPixels == null)
      intPixels = new int [largeurImageOriginale * hauteurImageOriginale];    
    copyPixels (x, y, width, height, pixels, offset, scansize, intPixels);
  }
 
  // Recopie la portion d'image chargee dans le tableau destPixels
  private void copyPixels (int x, int y, int width, int height,
		               Object pixels, int offset, int scansize,
		               Object destPixels)
  {		                  
    for (int i = 0; i < height; i++)
      System.arraycopy (pixels, offset + (scansize * i), 
                         destPixels, x + (y + i) * largeurImageOriginale, width);
  }
  
  public void imageComplete (int status)
  {
    if (   status == STATICIMAGEDONE
|| status == SINGLEFRAMEDONE) { int largeurChiffre = largeurImageOriginale / 10; // Renvoie vers le consommateur final les chiffres du compteur un à un for (int puissanceDix = nbChiffres - 1; puissanceDix >= 0; puissanceDix--) { // Recherche du chiffre à afficher int chiffre = (valeur / (int)Math.pow (10, puissanceDix)) % 10; if (bytePixels != null) consumer.setPixels ((nbChiffres - puissanceDix - 1) * largeurChiffre, 0, largeurChiffre, hauteurImageOriginale, model, bytePixels, chiffre * largeurChiffre, largeurImageOriginale); else consumer.setPixels ((nbChiffres - puissanceDix - 1) * largeurChiffre, 0, largeurChiffre, hauteurImageOriginale, model, intPixels, chiffre * largeurChiffre, largeurImageOriginale); } } consumer.imageComplete (status); } }  

L'interface java.awt.image.ImageProducer

Cette interface est implémentée par les classes qui sont capables de produire des images. A l'appel de la méthode startProduction (), la classe qui implémente cette interface doit commencer à produire une image vers un consommateur, qui lui doit implémenter l'interface ImageConsumer. Ceci doit se traduire par l'appel des méthodes de l'interface ImageConsumer pour transmettre au consommateur les informations décrivant l'image.
Un producteur est capable de produire des images pour un ou plusieurs consommateurs. Les méthodes addConsumer (), isConsumer () et removeConsumer () doivent être implémentées pour gérer cet ensemble de consommateurs.

Méthodes
public synchronized void addConsumer (ImageConsumer ic)
public synchronized void removeConsumer (ImageConsumer ic)

Ces méthodes doivent ajouter ou retirer le consommateur d'images ic, de l'ensemble des consommateurs enregistrés par ce producteur.

public synchronized boolean isConsumer (ImageConsumer ic)

Doit renvoyer true si ic appartient à l'ensemble des consommateurs d'images enregistrés par ce producteur.

public void startProduction (ImageConsumer ic)

Cette méthode méthode doit enregistrer ic comme consommateur d'images, et commencer la production de(s) image(s) en appelant les différentes méthodes de l'interface ImageConsumer sur chacun des consommateurs d'images enregistrés.

public void requestTopDownLeftRightResend (ImageConsumer ic)

Cette méthode doit renvoyer les données de l'image vers le consommateur d'images ic avec les pixels transmis de haut en bas et de gauche à droite, pour que le traitement des pixels par ce consommateur soit de meilleure qualité. Par conséquent, le producteur doit appeler la méthode setHints () de l'interface ImageConsumer avec comme paramètre TOPDOWNLEFTRIGHT.

L'interface java.awt.image.ImageConsumer

Cette interface est implémentée par les classes qui ont besoin des données d'une image. Un producteur d'image, dont la classe doit implémenter l'interface ImageProducer, invoque chacune des différentes méthodes de cette interface pour transmettre au consommateur d'images tous les renseignements décrivant une image.

Champs
public final static int RANDOMPIXELORDER
public final static int TOPDOWNLEFTRIGHT
public final static int COMPLETESCANLINES

La méthode setHints () est appelée avec comme paramètre l'une de ces trois constantes combinée éventuellement avec l'une des deux qui suivent, pour transmettre au consommateur dans quel ordre seront transmis les pixels de l'image pendant les appels successifs à la méthode setPixels (). Le producteur peut envoyer ces pixels dans un ordre aléatoire, de haut en bas et de gauche à droite, ou par ligne entière mais dans un ordre indéterminé.

public final static int SINGLEPASS

La méthode setHints () peut recevoir en paramètre cette constante, pour signifier au consommateur que l'image sera générée par le producteur en une seule passe.

public final static int SINGLEFRAME

La méthode setHints () peut recevoir en paramètre cette constante, pour signifier au consommateur qu'une seule image sera générée par le producteur. Dans le cas contraire, un ensemble d'images peuvent être transmises au consommateur pour fabriquer une animation et chaque fois qu'une image est complète imageComplete () est appelée avec SINGLEFRAMEDONE en paramètre ou SINGLEFRAMEDONE quand l'animation est terminée.

public final static int IMAGEERROR
public final static int IMAGEABORTED
public final static int STATICIMAGEDONE
public final static int SINGLEFRAMEDONE

La méthode imageComplete () est appelée avec comme paramètre l'une de ces quatre constantes, pour indiquer au consommateur si la génération de l'image a rencontré une erreur, si elle a été interrompue, si l'image final est terminée ou si la génération d'une image d'un ensemble en comportant plusieurs est complète.

Méthodes
public void setDimensions (int width, int height)

Cette méthode est appelée par le producteur d'images pour transmettre au consommateur la largeur width et la hauteur height de l'image produite.

public void setProperties (Hashtable props)

Cette méthode est appelée par le producteur d'images pour transmettre au consommateur les propriétés props de l'image.

public void setColorModel (ColorModel model)

Cette méthode est appelée par le producteur d'images pour transmettre au consommateur le modèle de couleurs le plus courant utilisé pour décrire l'image.

public void setHints (int hints)

Cette méthode est appelée par le producteur d'images pour transmettre au consommateur les propriétés hints de l'image. hints est une combinaison des constantes RANDOMPIXELORDER, TOPDOWNLEFTRIGHT ou COMPLETESCANLINES, SINGLEPASS et SINGLEFRAME et permet au consommateur de préparer et d'optimiser son environnement en fonction de la manière dont sera générée l'image.

public void setPixels  (int x, int y, int width, int height,
                         ColorModel model,
                         byte pixels [ ],
                         int off, int scansize)
public void setPixels  (int x, int y, int width, int height,
                         ColorModel model,
                         int pixels [ ],
                         int off, int scansize)

L'une de ces deux méthodes est appelée par le producteur d'images pour transmettre au consommateur les valeurs des pixels de la portion d'image de taille width et height au point (x,y). La première méthode reçoit les pixels dans un tableau de type byte, l'autre dans un tableau de type int. Ces pixels utilisent le modèle de couleur model. Les données du tableau pixels sont à prendre en compte à partir de l'indice offset, et comprennent un nombre de scansize pixels par ligne.

public void imageComplete (int status)

Cette méthode appelée par le producteur d'images doit être implémentée pour prendre en compte le statut status de la génération d'images (égal à IMAGEERROR, IMAGEABORTED, STATICIMAGEDONE ou SINGLEFRAMEDONE).

Exemple

Applet Compteur.

Gestion d'animations

L'utilisation des threads et des images permet de réaliser rapidement des animations en Java. Comme le montrent les trois exemples suivant, le principe de programmation d'une animation est presque toujours le même : Vous créez un thread dont la méthode run () utilise une boucle qui à chaque tour affiche une nouvelle image puis arrête le thread courant pendant un laps de temps avec la méthode sleep () de la classe Thread.
Bien que comme au cinéma, une animation sera en apparence bien fluide à 25 images par seconde (équivalent à un laps de temps entre chaque image de 40 millisecondes), évitez un laps de temps de rafraîchissement aussi court, car les machines ont souvent du mal à suivre.

Applet AnimationFleche

Voici le programme Java correspondant (à copier dans un fichier dénommé AnimationFleche.java et invoqué à partir d'un fichier HTML) :

import java.applet.Applet;
import java.awt.*;
import java.awt.image.*;
 
public class AnimationFleche extends Applet implements Runnable
{
  private Thread threadAnimation     = null;
  private Image  imagesAnimation [ ] = new Image [8];
  private int    imageCourante       = 0;
 
  public void init ()
  {
    try
    {
      // Création de l'image globale mémorisant 8 dessins de flèches
      Image multiImages = getImage (getCodeBase (), "fleches.gif");
      // Création d'un MediaTracker pour récupérer les 8 images
      MediaTracker imageTracker = new MediaTracker (this);
      for (int i = 0; i < imagesAnimation.length; i++)
      {
        // Chacune des 8 images est extraite de l'image principale
        imagesAnimation [i]
             = createImage (new FilteredImageSource
                                    (multiImages.getSource (),
                                     new CropImageFilter (i * 50, 0,
                                                            50, 50)));
        imageTracker.addImage (imagesAnimation [i], 0);
      }
 
      imageTracker.waitForID (0);
      // En cas d'erreur, déclenchement d'une exception
      if (imageTracker.isErrorAny ())
        throw new IllegalArgumentException ("Images non chargees");
    }
    catch (Exception e)
    { }
  }
 
  public void start ()
  {
    // Création et démarrage d'un thread d'animation
    threadAnimation = new Thread (this);
    threadAnimation.start ();
  }
 
  public void stop ()
  {
    threadAnimation.stop ();
  }
 
  public void run ()
  {
    while (threadAnimation.isAlive ())
      try
      {
        // Redessin de l'applet et passage à l'image suivante
        repaint ();
        imageCourante = ++imageCourante % 8;
        // Attente de 70 ms avant de passer à l'image suivante
        Thread.sleep (70);
      }
      catch (InterruptedException exception)
      { }
  }
 
  // Méthode outrepassée pour qu'elle dessine directement l'image
  public void update (Graphics gc)
  {
    paint (gc);
  }
 
  public void paint (Graphics gc)
  {
    // Dessin de l'image courante
    gc.drawImage (imagesAnimation [imageCourante], 0, 0, this);
  }
}
 

Utilisation du double buffering

Cette applet montre l'intérêt d'utiliser le système du double buffering pour gérer l'animation d'une image générée par un programme.
Le principe en est simple : au lieu de dessiner directement à l'écran un dessin qui évolue à chaque laps de temps, vous utilisez une image dans laquelle vous dessinez puis que vous transférez à l'écran. Ceci évite l'effet de clignotement d'une animation démontré par l'applet suivante, qui affiche un texte défilant horizontalement :

Applet ScrollText

Voici le programme Java correspondant (à copier dans un fichier dénommé ScrollText.java et invoqué à partir d'un fichier HTML) :

import java.applet.Applet;
import java.awt.*;
 
public class ScrollText extends Applet implements Runnable
{
  private String      texte;
  private Thread      threadAnimation;
  private Dimension   tailleApplet;
  private int         positionTexte;
  private FontMetrics metrics;
  private int         largeurTexte;
 
  public void start ()
  {
    // Mise en blanc du fond de l'applet
    setBackground (Color.white);
    tailleApplet = size ();
    // Récupération du texte à afficher
    texte = getParameter ("Texte");
    positionTexte = tailleApplet.width;
    // Création et démarrage du thread d'animation
    threadAnimation = new Thread (this);
    threadAnimation.start();
  }
 
  public void stop ()
  {
    threadAnimation.stop ();
  }
 
  public void run ()
  {
    try
    {
      while (threadAnimation.isAlive ())
      {
        // Redessin de l'applet et calcul d'une nouvelle position
        repaint ();
        if (positionTexte > -largeurTexte)
          positionTexte -= tailleApplet.height / 2;
        else
          positionTexte = tailleApplet.width;
        // Arrête le compteur pendant 2/10 de secondes (200 ms)
        Thread.sleep (200);
      }
    }
    catch (InterruptedException e)
    { }
  }
 
public void paint (Graphics gc)
  {
    gc.setColor (Color.black);
    // Création d'une police de caractères et récupération de sa taille
    gc.setFont (new Font ("Helvetica", Font.BOLD, tailleApplet.height - 4));
    if (metrics == null)
    {
      metrics = gc.getFontMetrics ();
      largeurTexte = metrics.stringWidth (texte);
    }
 
    // Utilisation d'un rectangle de clipping
    // pour créer une bordure au bord de l'applet
    gc.clipRect (2, 0, tailleApplet.width - 4, tailleApplet.height);
    // Dessin du texte
    gc.drawString (texte, positionTexte + 2,
                            tailleApplet.height - metrics.getDescent () - 2);
  }
}

Si vous consultez le source de ce fichier HTML , vous pourrez voir que le paramètre Texte de cette applet permet de modifier le texte affichée :

<APPLET CODE="ScrollText" CODEBASE="../classes"
        ALT="ScrollText" WIDTH=250 HEIGHT=40 ALIGN=middle>
   <PARAM NAME="Texte" VALUE="Texte d&eacute;filant horizontalement...">
</APPLET>
 

Maintenant, voici la même applet modifiée pour éviter l'effet de clignotement :

Applet ScrollText

La modification apportée porte sur la manière de programmer la méthode paint () de l'applet ScrollText :

// ...
 
public class ScrollText extends Applet implements Runnable
{
  // start (), stop () et run () : même code que précédemment
 
  private Image imageTexte;
 
  public void paint (Graphics gc)
  {
    // Création d'une image de la taille de l'applet
    if (imageTexte == null)
      imageTexte = createImage (tailleApplet.width, tailleApplet.height);
    Graphics gcImage = imageTexte.getGraphics ();
     
    // Même dessin mais cette fois-ci dans l'image
    gcImage.setColor (Color.white);
    gcImage.fillRect (0, 0, tailleApplet.width, tailleApplet.height);
 
    gcImage.setColor (Color.black);
    gcImage.setFont (new Font ("Helvetica", Font.BOLD, tailleApplet.height - 4));
    if (metrics == null)
    {
      metrics = gcImage.getFontMetrics ();
      largeurTexte = metrics.stringWidth (texte);
    }
    gcImage.clipRect (2, 0, tailleApplet.width - 4, tailleApplet.height);
    gcImage.drawString (texte, positionTexte + 2,
                             tailleApplet.height - metrics.getDescent () - 2);
 
    // Dessin de l'image à l'écran       
    gc.drawImage (imageTexte, 0, 0, this);
  }
 
  // Méthode outrepassée pour éviter de dessiner le fond
  public void update (Graphics gc)
  {
    paint (gc);
  }
}
 

Horloge avec image de fond

Cette applet est aussi un exemple d'utilisation du système du double buffering et montre la possibilité de dessiner par dessus une image chargée à partir d'un fichier. Au début du programme, le fond de l'horloge est téléchargé, puis toutes les secondes une nouvelle image est mise à jour en copiant ce fond dans le double buffer puis en dessinant les aiguilles par dessus à leur nouvelle position :

Applet Horloge

Voici le programme Java correspondant (à copier dans un fichier dénommé Horloge.java et invoqué à partir d'un fichier HTML) :

import java.applet.Applet;
import java.awt.*;
import java.awt.image.*;
import java.util.Date;
 
public class Horloge extends Applet implements Runnable
{
  private Thread  threadHorloge;
  private Image   fondHorloge;
 
  public void init ()
  {
    try
    {
      // Chargement du fond  de l'horloge avec un MediaTracker
      fondHorloge = getImage (getCodeBase (), "starclock.gif");
      MediaTracker imageTracker = new MediaTracker (this);
      imageTracker.addImage (fondHorloge, 0);
      imageTracker.waitForID (0);
 
      if (imageTracker.isErrorAny ())
        fondHorloge = null;
    }
    catch (Exception e)
    { }
  }
 
  public void start ()
  {
    // Lancement d'un thread pour mettre à jour l'horloge
    threadHorloge = new Thread (this);
    threadHorloge.start ();
  }
 
  public void stop ()
  {
    threadHorloge.stop ();
  }
 
  public void run ()
  {
    try
    {
      while (threadHorloge.isAlive ())
      {
        // Récupération du temps courant pour calculer
        // le durée d'arrêt du thread en fonction du temps
        // d'exécution de la méthode afficher ()
        long tempsCourant = System.currentTimeMillis ();
        afficher (new Date (tempsCourant));
        long dureeArret = 1000 - (  System.currentTimeMillis ()
                                  - tempsCourant);
        if (dureeArret > 0)
          Thread.sleep (dureeArret);
      }
    }
    catch (InterruptedException exception)
    { }
  }
 
  private Image horloge;
 
  private void afficher (Date tempsCourant)
  {
    // Création d'une image aux dimensions de l'applet
    Dimension taille = size ();
    if (horloge == null)
      horloge = createImage (taille.width, taille.height);
    
    // Récupération des heures, minutes et secondes
    int heures   = tempsCourant.getHours ();
    int minutes  = tempsCourant.getMinutes ();
    int secondes = tempsCourant.getSeconds ();
  
    // Pour chaque aiguille, conversion du temps en radian
    // et récupération du polygone utilisant cet angle
    double angleHeure = (180 - heures * 60 - minutes) * Math.PI / 360;
    Polygon pointsHeure
              = calculerAiguille (angleHeure,
                                  taille.width / 4.,
                                  taille.height / 4.);
    double angleMinute = (900 - minutes * 60 - secondes) * Math.PI / 1800;
    Polygon pointsMinute
              = calculerAiguille (angleMinute,
                                  3. * taille.width / 8.,
                                  3. * taille.height / 8.);
 
    double angleSeconde = (15 - secondes) * Math.PI / 30;
    Polygon pointsSeconde
              = calculerAiguille (angleSeconde,
                                  2. * taille.width / 5.,
                                  2. * taille.height / 5.);
 
    Graphics gcImage = horloge.getGraphics ();
    if (fondHorloge != null)    
      // Dessin de l'image de fond d'horloge avec redimensionnement
      gcImage.drawImage (fondHorloge, 0, 0,
                         taille.width, taille.height, this);
    else
    {
      // Si l'image de fond d'horloge est absente
      // remplissage avec une couleur et dessin d'un cercle
      gcImage.setColor (new Color (0x638494));
      gcImage.fillRect (0, 0, taille.width, taille.height);
      gcImage.setColor (Color.white);
      gcImage.drawOval (0, 0, taille.width - 1, taille.height - 1);
    }
    // Translation au centre de l'horloge
    gcImage.translate (taille.width / 2, taille.height / 2);
    // Dessin des aiguilles (les secondes utilisent une ligne)
    gcImage.setColor (Color.white);
    gcImage.drawPolygon (pointsHeure);
    gcImage.drawPolygon (pointsMinute);
    gcImage.drawLine (pointsSeconde.xpoints [0],
                       pointsSeconde.ypoints [0],
                       pointsSeconde.xpoints [2],
                       pointsSeconde.ypoints [2]);
    // Mise à jour à l'écran
    repaint ();
  }
 
  // Dessin d'une aiguille à 3 heures (0 rad)
  private int []   xAiguille = {-15, 33, 100,  33, -15};
  private int []   yAiguille = {  0, 10,   0, -10,   0};
  private Polygon  pointsAiguille =
           new Polygon (xAiguille, yAiguille, xAiguille.length);
 
  private Polygon calculerAiguille (double angle,
                                       double width,
                                       double height)
  {
    Polygon points = new Polygon ();
    // Calcul de la rotation de chaque point de l'aiguille
    for (int i = 0; i < pointsAiguille.npoints; i++)
      points.addPoint ((int)(  (    pointsAiguille.xpoints [i]
                                  * Math.cos (angle)
                                +   pointsAiguille.ypoints [i]
                                  * Math.sin (angle))
                             * width / 100),
                       (int)(   (     pointsAiguille.xpoints [i]
                                   * -Math.sin (angle)
                                 +   pointsAiguille.ypoints [i]
                                   * Math.cos (angle))
                             * height / 100));
    return points;
  }
 
  public void update (Graphics gc)
  {
    paint (gc);
  }
 
  public void paint (Graphics gc)
  {
    // Dessin de l'image à l'écran       
    if (horloge != null)
      gc.drawImage (horloge, 0, 0, this);
  }
}

Cette applet pourrait être améliorée en lui permettant de récupérer en paramètre l'image de fond, le choix d'afficher les secondes ou non, l'ajout d'effets sonores toutes les secondes ou toutes les heures en utilisant l'interface AudioClip,...


Page d'accueilFindIt !ContactLa gestion de l'interface utilisateurPlus loin avec Java...Début de la page
© Copyrights 1997-2023 Emmanuel PUYBARET / eTeks
- Tous droits réservés -
Table des matièresHiérarchie des classes