|
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
Les images, instances de la classe Image, peuvent provenir de deux sources différentes :
Les méthodes drawImage () de la classe Graphics permettent d'afficher une image à un point donné en la redimensionnant éventuellement, comme le montre l'applet suivante, qui crée une image d'un nuancier, et l'affiche à 4 tailles différentes :
Voici le programme Java correspondant (à copier dans un fichier dénommé MultiImages.java et invoqué à partir d'un fichier HTML) :
import java.applet.Applet; import java.awt.*; public class MultiImages extends Applet { private Image image; public void reshape (int x, int y, int width, int height) { // Effectuer le comportement par défaut du changement de taille super.reshape (x, y, width, height); // Création de l'image que quand le peer de l'applet existe if (getPeer () != null) { Dimension taille = size (); taille.width /= 3; taille.height /= 3; // Création d'une image de dimensions 3 fois plus petites que l'applet image = createImage (taille.width, taille.height); Graphics gc = image.getGraphics (); // Remplissage d'une image avec un nuancier de rouge et vert for (int i = 0; i < taille.height; i++) for (int j = 0; j < taille.width; j++) { gc.setColor (new Color ((float)i / taille.height, // Rouge (float)j / taille.width, // Vert 0)); // Bleu gc.fillRect (i, j, 1, 1); } } } public void paint (Graphics gc) { if (image != null) { // Dessin de l'image à sa taille et agrandie gc.drawImage (image, 0, 0, this); gc.drawImage (image, image.getWidth (this), 0, image.getWidth (this) * 2, image.getHeight (this), this); gc.drawImage (image, 0, image.getHeight (this), image.getWidth (this), image.getHeight (this) * 2, this); gc.drawImage (image, image.getWidth (this), image.getHeight (this), image.getWidth (this) * 2, image.getHeight (this) * 2, this); } } }
|
Soit un composant comp sur lequel vous invoquez la méthode createImage (int width, int height) de la classe Component (comp peut être une applet ou un autre composant). comp.createImage () ne crée une image que si le peer du composant comp existe. Le peer n'étant pas créé dans le constructeur d'un composant (de classe dérivée de Applet, Canvas, ou autre), cette caractéristique vous amène à créer une image pas forcément dans la méthode à laquelle vous pensiez au départ... Vous devez appeler createImage () qu'au moment où ce peer existe, par exemple dans la méthode init () de la classe Applet, dans la méthode paint () d'une classe dérivée de Canvas, ou en outrepassant la méthode addNotify () de la manière suivante : //... Image image; public void addNotify () { super.addNotify (); // Le peer du composant existe, on peut créer une image image = createImage (largeur, hauteur); } //... Souvenez-vous donc que si un appel à comp.createImage () vous renvoie null, demandez vous d'abord si le peer du composant comp existe à ce moment. |
Cette classe abstract permet de manipuler les images en Java.
public final static Object UndefinedProperty
public Image ()
public abstract int getWidth (ImageObserver observer) public abstract int getHeight (ImageObserver observer)
Ces méthodes renvoient la largeur ou la hauteur d'une image. La classe Component implémentant l'interface ImageObserver, observer peut être égal à un composant. Si l'image n'est pas encore chargée, la valeur -1 est renvoyée.
public abstract Graphics getGraphics ()
Renvoie une instance de la classe Graphics, grâce à laquelle vous pouvez dessiner dans l'image.
public abstract ImageProducer getSource ()
Renvoie une instance de la classe implémentant l'interface ImageProducer. Cet objet est notamment utilisé par les classes réalisant un filtrage.
public abstract Object getProperty (String name, ImageObserver observer) public abstract void flush ()
Applets BoutonsNavigation, MultiImages, ImageSimple, ChargementImage, ImageTableau, ImageNoirEtBlanc, NegatifImage, AnimationFleche, ScrollText et Horloge.
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 :
- Soit à l'appel de l'une des méthodes drawImage () de la classe Graphics avec en paramètre une image img. Si img n'est encore pas chargée, la méthode drawImage () débute le chargement de l'image de manière asynchrone et rend la main. Le dernier paramètre de drawImage () doit être une instance d'une classe implémentant l'interface ImageObserver comme par exemple la classe Component. Cette interface ne déclare qu'une seule méthode imageUpdate () et l'implémentation de cette méthode dans la classe Component redessine le composant pour mettre à jour le dessin de l'image au fur et à mesure de son chargement.
Donc, si vous donnez en dernier paramètre le composant dans lequel l'image est affichée, l'image sera affichée automatiquement aussitôt qu'elle est disponible.
L'applet suivante utilise la méthode drawImage () pour charger et afficher une image :
Voici le programme Java correspondant (à copier dans un fichier dénommé ImageSimple.java et invoqué à partir d'un fichier HTML) :
import java.applet.Applet; import java.awt.*; public class ImageSimple extends Applet { private Image image; public void init () { // Création d'une image image = getImage (getCodeBase (), "monval.jpg"); } public void paint (Graphics gc) { if (image != null) // Affichage de l'image (image chargée automatiquement) gc.drawImage (image, 0, 0, this); } }- Soit à l'appel des méthodes prepareImage (Image img, ImageObserver observer) ou prepareImage (Image img, int width, int height, ImageObserver observer) des classe Component et Toolkit. Ces méthodes permettent de débuter le chargement de l'image img par un autre thread de manière asynchrone et rend la main. Les paramètres width et height permettent de redimensionner l'image dès son chargement. Le dernier paramètre observer doit être une instance d'une classe implémentant l'interface ImageObserver et permet de surveiller l'état du chargement de l'image. La classe Component implémentant cette interface, vous pouvez utiliser un composant comme paramètre.
L'applet ChargementImage est un exemple d'utilisation de la méthode prepareImage () et de l'interface ImageObserver.- Soit en utilisant la classe MediaTracker, qui permet de surveiller et d'attendre la fin du chargement d'une image. Cette classe qui utilise la méthode prepareImage () et l'interface ImageObserver, simplifie la programmation du chargement d'une image.
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 LOADINGLes 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 HEIGHTLa largeur ou la hauteur de l'image sont disponibles.
public final static int PROPERTIESLes propriétés de l'image sont disponibles.
public final static int SOMEBITS public final static int FRAMEBITSUne partie de l'image est chargée.
public final static int ALLBITSLe chargement de l'image est terminé et toutes ses caractéristiques sont disponibles.
public final static int ERRORUne erreur est survenue pendant le chargement de l'image. Combiné avec ABORT.
public final static int ABORTLe 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 :
Voici le programme Java correspondant (à copier dans un fichier dénommé ChargementImage.java et invoqué à partir d'un fichier HTML) :
import java.applet.Applet; import java.awt.*; import java.awt.image.*; public class ChargementImage extends Applet implements ImageObserver { private Image image; private boolean chargementTermine = false; public void init () { // Création d'une image et lancement de son chargement image = getImage (getCodeBase (), "brookbr.jpg"); prepareImage (image, this); } public void paint (Graphics gc) { // Si le chargement de l'image est terminé, affichage de l'image // sinon affichage d'une chaîne de caractères d'attente if (chargementTermine) gc.drawImage (image, 0, 0, this); else gc.drawString ("Chargement en cours...", 10, size ().height - 10); } // Méthode appelée pour communiquer les étapes du chargement de l'image public boolean imageUpdate (Image image, int infoFlags, int x, int y, int width, int height) { // Si le chargement est terminé, redessin de l'applet if ((infoFlags & ALLBITS) != 0) { chargementTermine = true; repaint (); } return (infoFlags & (ALLBITS | ABORT)) == 0; } }
A la différence de l'applet ImageSimple, l'image de cette applet n'est affichée qu'une fois que l'image est entièrement chargée.
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 MemoryImageSource permet de créer une image initialisée avec un tableau décrivant la couleur de chacun des points d'une image.
- La classe FilteredImageSource permet de créer une image qui est le résultat de l'application d'un filtre sur une image existante.
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 :
Voici le programme Java correspondant (à copier dans un fichier dénommé ImageTableau.java et invoqué à partir d'un fichier HTML) :
import java.applet.Applet; import java.awt.*; import java.awt.image.*; public class ImageTableau extends Applet { private Image image; public void init () { Dimension taille = size (); int [ ] tableauPixels = new int [taille.width * taille.height]; // Remplissage du tableau avec un nuancier de rouge et vert // les pixels sont décrits ligne par ligne par leur code RGB for (int i = 0; i < taille.height; i++) for (int j = 0; j < taille.width; j++) tableauPixels [i * taille.width + j] = 255 << 24 // Transparence | ((j * 255 / taille.width) << 16) // Rouge | ((i * 255 / taille.height) << 8) // Vert | 0; // Bleu // Création d'une image à partir du tableau image = createImage (new MemoryImageSource (taille.width, taille.height, tableauPixels, 0, taille.width)); } public void paint (Graphics gc) { if (image != null) gc.drawImage (image, 0, 0, this); } }
Remplir le tableau oblige ici à positionner les bits des composantes Rouge, Vert, Bleu d'une couleur. Quelle est la principale différence entre cette applet et l'applet MultiImages ? A partir de mesures de temps réalisées sur ces deux applets, la création d'une image de même dimension prend au moins 100 fois plus de temps par l'applet MultiImages que sur l'applet ImageTableau ! Donc, si la création d'une image n'a pas besoin des méthodes de dessin de la classe Graphics, n'hésitez pas à utiliser la classe MemoryImageSource.
|
Le modèle de couleur RGB par défaut implique que chaque couleur d'un point est codée sur un entier 32 bits dont les bits 16 à 23 représentent le Rouge, les bits 8 à 15 le Vert et les bits 0 à 7 le Bleu, ET dont les bits 24 à 31 représentent le canal alpha. Cette dernière valeur correspond à la transparence (0 pour transparent à 255 pour opaque) : si vous oubliez de mettre une valeur pour ces bits dans le code couleur, l'image sera du coup transparente et donc invisible ! |
Applets ImageNoirEtBlanc.
Cette classe abstract est la super classe des modèles de couleurs. Un modèle de couleur décrit comment retrouver la couleur d'un point (ou pixel) d'une image à partir de sa valeur entière. Le modèle de couleur RGB par défaut est obtenu par la méthode static getRGBDefault (). Les classes DirectColorModel et IndexColorModel qui dérivent de cette classe permettent d'utiliser des modèles de couleur différents, pour par exemple décrire une image créée avec la classe MemoryImageSource.
protected int pixel_bits
Nombre de bits par pixel.
public ColorModel (int bits)
public static ColorModel getRGBdefault ()
Renvoie le modèle de couleur RGB par défaut utilisant 32 bits avec les bits 24 à 31 pour le canal alpha, les bits 16 à 23 pour le Rouge, les bits 8 à 15 pour le Vert et les bits 0 à 7 pour le Bleu.
public int getPixelSize ()
Renvoie le nombre de bits utilisé par un modèle de couleur pour coder la couleur d'un pixel.
public abstract int getRed (int pixel) public abstract int getGreen (int pixel) public abstract int getBlue (int pixel) public abstract int getAlpha (int pixel)
Ces méthodes doivent renvoyer les composantes Rouge, Vert, Bleu et Alpha (comprises entre 0 et 255) pour une valeur pixel.
public int getRGB (int pixel)
Cette classe qui dérive de la classe ColorModel permet de définir un modèle de couleur différent du modèle RGB par défaut, qui n'utilise pas forcément 32 bits pour coder la couleur d'un pixel. Ce modèle permet par exemple de décrire comment coder une couleur sur 16 bits, utilisant les bits 11 à 15 pour le Rouge, les bits 6 à 10 pour le Vert et les bits 0 à 5 pour le Bleu (correspondant à la création d'une instance de la classe DirectColorModel par l'instruction new DirectColorModel (16, 0xF800, 0x07C0, 0x003F)).
public DirectColorModel (int bits, int rmask, int gmask, int bmask) public DirectColorModel (int bits, int rmask, int gmask, int bmask, int amask)
Ces constructeurs permettent de créer un modèle de couleur sur nbits bits. rmask, gmask bmask et amask décrivent les masques de bits utilisés pour coder les composantes Rouge, Vert, Bleu et Alpha. Les masques doivent occupés les bits de poids le plus faible. Par défaut, les couleurs sont opaques.
public final int getRed (int pixel) public final int getGreen (int pixel) public final int getBlue (int pixel) public final int getAlpha (int pixel)
Ces méthodes renvoient les composantes Rouge, Vert, Bleu ou Alpha (comprises entre 0 et 255) pour une valeur de couleur pixel, utilisée avec ce modèle de couleur.
public final int getRGB (int pixel)
Renvoie la couleur RGB utilisant le modèle de couleur RGB par défaut pour une valeur pixel.
public final int getRedMask () public final int getGreenMask () public final int getBlueMask () public final int getAlphaMask ()
Renvoie masques de bits utilisés pour coder les composantes Rouge, Vert, Bleu ou Alpha de ce modèle de couleur.
Cette classe qui dérive de la classe ColorModel permet de définir une palette de plusieurs couleurs. Chaque couleur de cette palette est définie par ses composantes RGB (comprises entre 0 et 255) et Alpha (transparence de 0 transparent à 255 opaque). Une image utilisant une palette de couleurs définit la couleur de chacun de ses points par un entier égal à l'indice d'une couleur de cette palette (comme par exemple les images au format GIF).
public IndexColorModel (int bits, int size, byte r [ ], byte g [ ], byte b [ ]) public IndexColorModel (int bits, int size, byte r [ ], byte g [ ], byte b [ ], byte a [ ]) public IndexColorModel (int bits, int size, byte r [ ], byte g [ ], byte b [ ], int trans)
Ces constructeurs permettent de créer une palette de size couleurs. Les tableaux r [ ], g [ ], b [ ] et a [ ] décrivent les composantes Rouge, Vert, Bleu et Alpha de chacune des couleurs de la palette. Par défaut, chaque couleur est opaque. trans permet de spécifier l'indice de la couleur qui sera considérée comme transparente (comme pour les images au format GIF). bits donne le nombre de bits utilisé par chaque pixel (habituellement égal à log2 size arrondi à l'entier supérieur).
public IndexColorModel (int bits, int size, byte cmap [ ], int start, boolean hasalpha) public IndexColorModel (int bits, int size, byte cmap [ ], int start, boolean hasalpha, int trans)
Ces constructeurs permettent de créer une palette de size couleurs. Le tableau cmap [ ] décrit dans l'ordre les composantes Rouge, Vert, Bleu et Alpha (si hasalpha est égal à true) de chacune des couleurs de la palette. start permet de spécifier l'indice du premier élément du tableau à utiliser. trans et bits sont utilisées de la même manière que pour les constructeurs précédents.
public final int getMapSize ()
Renvoie la taille de la palette.
public final int getTransparentPixel ()
Renvoie l'indice de la couleur transparente ou -1 s'il n'est pas défini.
public final int getRed (int pixel) public final int getGreen (int pixel) public final int getBlue (int pixel) public final int getAlpha (int pixel)
Ces méthodes renvoient les composantes Rouge, Vert, Bleu ou Alpha (comprises entre 0 et 255) à l'indice pixel de la palette.
public final int getRGB (int pixel)
Renvoie la couleur RGB utilisant le modèle de couleur RGB par défaut à l'indice pixel de la palette.
public final void getReds (byte r [ ]) public final void getGreens (byte g [ ]) public final void getBlues (byte b [ ]) public final void getAlphas (byte a [ ])
Ces méthodes remplissent le tableau qui leur est passé en paramètre avec les composantes Rouge, Vert, Bleu ou Alpha de chacune des couleurs de la palette.
L'applet suivante crée une image aléatoire en utilisant la classe MemoryImageSource, et une palette de couleurs noir et blanc :
Voici le programme Java correspondant (à copier dans un fichier dénommé ImageNoirEtBlanc.java et invoqué à partir d'un fichier HTML) :
import java.applet.Applet; import java.awt.*; import java.awt.image.*; import java.util.Random; public class ImageNoirEtBlanc extends Applet { private Image image; public void init () { byte [ ] noirEtBlanc = {0x00, 0x00, 0x00, // Noir (byte)0xFF, (byte)0xFF, (byte)0xFF}; // Blanc ColorModel paletteNoirEtBlanc = new IndexColorModel (1, 2, noirEtBlanc, 0, false); Dimension taille = size (); byte [ ] tableauPixels = new byte [taille.width * taille.height]; Random generateur = new Random (); // Remplissage aléatoire avec les valeurs 0 ou 1 for (int i = 0; i < tableauPixels.length; i++) tableauPixels [i] = (byte)Math.abs (generateur.nextInt () % 2); // Création d'une image à partir du tableau image = createImage (new MemoryImageSource (taille.width, taille.height, paletteNoirEtBlanc, tableauPixels, 0, taille.width)); } public void paint (Graphics gc) { if (image != null) gc.drawImage (image, 0, 0, this); } }
|
Une image créée avec la classe MemoryImageSource et une palette de couleurs de classe IndexColorModel doit utiliser un tableau de type byte pour mémoriser la couleur des points de l'image (ceci limite la palette à 256 couleurs). |
Cette classe qui implémente l'interface ImageConsumer permet de récupérer les points d'une partie d'une image dans un tableau. Le tableau est rempli avec la couleur de chacun de ces points en utilisant le modèle de couleur RGB par défaut.
public PixelGrabber (Image img, int x, int y, int width, int height, int pix [ ], int offset, int scansize) public PixelGrabber (ImageProducer ip, int x, int y, int width, int height, int pix [ ], int offset, int scansize)
Ces constructeurs créent une instance de la classe PixelGrabber pour récupérer dans le tableau pix [ ] une portion rectangulaire d'une image aux coordonnées (x,y), de largeur width et de hauteur height. Le tableau est rempli à partir de l'indice offset, avec un nombre de scansize pixels par ligne.
public boolean grabPixels () throws InterruptedException public synchronized boolean grabPixels (long ms) throws InterruptedException
Ces méthodes démarre la récupération des points de l'image (éventuellement pendant ms millisecondes maximum).
public synchronized int status ()
Renvoie le statut des points à récupérer. La valeur renvoyée est une combinaison des constantes déclarées dans l'interface ImageObserver.
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 stocker l'image dans le tableau passé au constructeur.
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 consumerConsommateur 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 canFilterIndexColorModelSi 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 :
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; } }
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 imageQuand 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 imagePour 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) :
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 COMPLETESCANLINESLa 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 SINGLEPASSLa 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 SINGLEFRAMELa 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 SINGLEFRAMEDONELa 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.
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.
|
Si comme dans certains des exemples qui suivent, vous utilisez la méthode repaint () pour mettre à jour l'image d'une animation,
le redessin du fond du composant effectué par la méthode update () de la classe Component
est inutile si vous devez transférer une image à l'écran occupant toute la surface du composant. |
Cette première applet utilise les 8 images de taille égale contenues dans le fichier fleches.gif. Elles sont extraites de l'image téléchargée grâce à l'utilisation de la classe de filtre CropImageFilter, puis un thread provoque leur affichage l'une après l'autre à intervalle régulier pour donner l'effet animé :
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 :
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éfilant horizontalement..."> </APPLET>Maintenant, voici la même applet modifiée pour éviter l'effet de clignotement :
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 :
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,...
|