Una figura inmóvil que cambia de aspecto con el tiempo

prev.gif (997 bytes)chapter.gif (1105 bytes)home.gif (1232 bytes)next.gif (998 bytes)

Subprocesos (threads)

Pasos para crear una animación

Cargando y mostrando las imágenes

La clase MediaTracker


Aunque 12 imágenes por segundo son suficientes para producir la ilusión de movimiento, en el cine se emplean del orden de 24 y en la televisión del orden de 30. El número de imágenes por segundo que se pueden producir y mostrar con un ordenador dependen de su potencia de cálculo.

 

Pasos para crear una animación

Resumimos los pasos necesarios para crear una animación.

  1. La clase que describe el applet implementa el interface Runnable.
public class AnimaApplet4 extends Applet implements Runnable {
//...
}
  1. Se crea un subproceso, un objeto de la clase Thread y se pone en marcha llamando a su función miembro start. Dicho subproceso se puede crear y poner en marcha cuando aparece la página que contiene al applet en el navegador, o en respuesta a la pulsación de un botón titulado Empieza.
    if (anima == null) {
        anima = new Thread(this);
        anima.start();
    }
  1. Se para el subproceso llamando a su función miembro stop. Esta tarea se puede realizar cuando la página que contiene el applet desaparece del navegador, o en respuesta a la pulsación de un botón titulado Para.
    if (anima != null) {
        anima.stop();
        anima = null;
    }
  1. Ya que la clase que describe el applet implementa el interface Runnable, ha de definir la función miembro run, que tiene la siguiente definición
  public void run() {
    long t=System.currentTimeMillis();
    while (true) {
	//tarea a realizar ....mover una pelota, etc.
        try{
            t+=retardo;
            Thread.sleep(Math.max(0, t-System.currentTimeMillis()));
        }catch(InterruptedException ex){
            break;
        }
    }
  }
  1. Para evitar el parpadeo se redefine la función update, que emplea la técnica conocida por double-buffer. Esta técnica requiere cuatro pasos:
  public void update(Graphics g) {
    if (gBuffer == null) {
        imag = createImage(ancho, alto);
        gBuffer = imag.getGraphics();
    }

    gBuffer.setColor(getBackground());
    gBuffer.fillRect(0, 0, ancho, alto);
    gBuffer.setColor(Color.black);

//aquí se dibuja sobre el contexto gráfico en memoria gBuffer
    //....

    g.drawImage(imag, 0, 0, null);
  }

 

Cargando y mostrando las imágenes

disco.gif (1035 bytes)anima4: AnimaApplet4.java, 0.gif, 1.gif, 2.gif, 3.gif, 4.gif, 5.gif, 6.gif, 7.gif, 8.gif, 9.gif

En este ejemplo, vamos a ver lo específico de cada tipo de animación. En este caso es un contador. Los números del cero al nueve, se representan por pequeñas imágenes que se han guardado en archivos .GIF.

El primer paso, será cargar las imágenes mediante getImage y guardarlas en un array de objetos de la clase Image. Las imágenes las hemos situado en un subdirectorio denominado contadorGif por debajo de la ubicación del archivo HTM que guarda la página que contiene el applet. El nombre de los archivos como podemos apreciar en la porción de código es 1.gif, 2.gif ... 9.gif

archivosGIF.gif (1564 bytes)

    for (int i = 0; i < 10; i++) {
        numeros[i] = getImage(getDocumentBase(), "contadorGif/" + i + ".gif");
    }

En el bucle while de la función miembro run se incrementa el contador de imágenes. Cuando el contador indice sobrepasa el número nueve comienza con el cero, y así se repite el ciclo.

  public void run() {
    while (true) {
        if (++indice> numeros.length){
            indice=0;
        }
        repaint();
	//... 
    }
  }

Cuando hay subprocesos corriendo, no podemos sustituir las sentencias

        if (++indice> numeros.length){
            indice = 0;
        }

Por las sentencias

	indice++;
        if (indice>= numeros.length){
            indice = 0;
        }

Sino por las sentencias

   synchronized (this) {
 	indice++;
 	if (indice>= numeros.length) {
 		indice=0;
 	}
 }
El significado de la palabra reservada synchronized lo hemos estudiado en este capítulo.

Finalmente, repaint llama a update para dibujar la imagen guardada en el elemento indice del array numeros en el contexto gráfico en memoria gBuffer empleando la técnica del double-buffer.

  public void update(Graphics g) {
    //...
    gBuffer.drawImage(numeros[indice], 0, 0, this);
    //...
  }

 

La clase MediaTracker

Como podemos apreciar al visitar una página web, una imagen estática grande se va mostrando a medida que se va cargando. En una animación con un conjunto de imágenes esto no es posible, tenemos que disponer de todas las imágenes antes de correr (run) la animación.

El retraso en la transmisión es el tiempo que transcurre hasta que un determinado objeto ha sido transferido a través de la red Internet. La clase MediaTracker nos ayuda a resolver los problemas relacionados con el retraso en la transmisión de imágenes, sonido u otros elementos multimedia.

Para disponer de todas las imágenes antes de empezar la animación, en init creamos un objeto tracker de la clase MediaTracker.

    tracker = new MediaTracker(this);

A medida que vamos cargando las imágenes mediante getImage, las vamos añadiendo al objeto tracker, mediante addImage. El segundo argumento, 0, es el identificador de un grupo de imágenes.

    for (int i = 0; i < 10; i++) {
        numeros[i] = getImage(getDocumentBase(), "contadorGif/" + i + ".gif");
        tracker.addImage(numeros[i], 0);
    }

Nos aseguramos de que todas las imágenes, cuyo identificador es 0, se ha cargado mediante las siguientes líneas de código

    try {
        tracker.waitForID(0);
    }catch (InterruptedException ex) {
        return;
    }

El código fuente completo, es el siguiente

public class AnimaApplet4 extends Applet implements Runnable {
    Image[] numeros = new Image[10];
    Thread  anima;
    MediaTracker  tracker;
    int retardo = 800;
    int indice = 0;
//doble buffer
    Image imag;
    Graphics gBuffer;

  public void init() {
// carga las imágenes
    tracker = new MediaTracker(this);
    for (int i = 0; i < 10; i++) {
        numeros[i] = getImage(getDocumentBase(), "contadorGif/" + i + ".gif");
        tracker.addImage(numeros[i], 0);
    }
  }

  public void start() {
    if (anima == null) {
        anima = new Thread(this);
        anima.start();
    }
  }

  public void stop() {
    if (anima != null) {
        anima.stop();
        anima = null;
    }
  }

  public void run() {
//han de estar completamente cargadas las imágnes antes de empezar a correr la animación
    try {
        tracker.waitForID(0);
    }catch (InterruptedException ex) {
        return;
    }
// corre la animación
    long t = System.currentTimeMillis();
    while (true) {
	synchronized (this) {
 		indice++;
 		if (indice>=numeros.length) {
 			indice=0;
 		}
 	}
       repaint();
   
        try {
            t+=retardo;
            Thread.sleep(Math.max(0, t - System.currentTimeMillis()));
        }catch(InterruptedException e) {
            break;
        }
    }
  }

  public void update(Graphics g) {
    int ancho=getSize().width;
    int alto=getSize().height;
    if (gBuffer == null) {
        imag = createImage(ancho, alto);
        gBuffer = imag.getGraphics();
    }

    gBuffer.setColor(getBackground());
    gBuffer.fillRect(0, 0, ancho, alto);
    gBuffer.setColor(Color.black);
// muestra las imágenes
    gBuffer.drawImage(numeros[indice], 0, 0, this);
// Transfiere la imagen off a al contexto gráfico del applet
    g.drawImage(imag, 0, 0, null);
  }

  public void paint(Graphics g) {
    Font font = new Font("TimesRoman", Font.BOLD, 16);
    g.setFont(font);
    FontMetrics fm = g.getFontMetrics(font);
    String texto;
    if ((tracker.statusID(0, true) == MediaTracker.LOADING)) {
//se están cargando las imágenes
      texto="Cargando las imágenes ....";
      g.drawString(texto, (getSize().width - fm.stringWidth(texto)) / 2, ((getSize().height - fm.getHeight()) / 2) + fm.getAscent());
    }
  }
}