DibuJava II

Vamos a retocar un poquito nuestro ejemplo15 para que no se borren los rectángulos cuando queremos dibujar uno nuevo. Aprenderemos algo sobre la clase Vector, perteneciente al paquete java.util.

Vectores en acción

Los vectores nos permiten hacer arreglos de cualquier tipo de objeto, y referirnos individualmente a cualquier elemento del vector, aunque para utilizarlos (debido a que para java el vector contiene objetos genéricos) tendremos que decirle qué clase de objeto es mediante un "cast". Vamos a ver cómo quedan nuestras clases Ejemplo16 (ex Ejemplo15) y miCanvas:

import java.awt.*;
import java.util.*;
import java.applet.Applet;

public class Ejemplo16 extends Applet {
    public void init() {
................ (esta parte no cambia)................
    }
}

class miCanvas extends Canvas {
    Vector v = new Vector();	// inicializamos con tamaño indeterminado
				// Java se encarga de manejar la memoria necesaria!

    public boolean mouseDown(Event e, int x, int y) {
	v.addElement( new Rectangle(x, y, 0, 0) );	// nuevo elemento!
	repaint();
	return false;
    }

    public boolean mouseDrag(Event e, int x, int y) {
	Rectangle r = (Rectangle)v.lastElement();	// cast: v son rectángulos
	r.resize( x - r.x, y - r.y );			// (creé r sólo por claridad)
	repaint();
	return false;
    }

    public boolean mouseUp(Event e, int x, int y) {
	Rectangle r = (Rectangle)v.lastElement();	// cast: v son rectángulos
	r.resize( x - r.x, y - r.y );			// (creé r sólo por claridad)
	repaint();
	return false;
    }

    public void paint(Graphics g) {
	int i;					// contador de rectángulos
	Dimension d = size();
	g.setColor(Color.red);
	g.drawRect(0, 0, d.width-1, d.height-1);
	g.setColor(Color.blue);
	if (v.size() > 0)
      for (i=0; i<v.size(); i++) {
		Rectangle box = cortarRect( (Rectangle)v.elementAt( i ), d);
		g.drawRect(box.x, box.y, box.width-1, box.height-1);
	      }
    }
........................ (el resto no cambia) ........................
}

Les sugiero utilizar un HTML que reserve espacio suficiente para ver todo el applet, como:
<HTML>
<HEAD>
<TITLE>Ejemplo 16 - Ejemplo con canvas</TITLE>
</HEAD>
<BODY>
<applet code="Ejemplo16.class" width=300 height=250>
</applet>
</BODY>
</HTML>


Veamos los pasos ahora. En primer lugar creamos una variable (global a la clase) llamada v, de clase Vector, y sin asignarle un tamaño definido:

Vector v = new Vector();


Al crear un nuevo rectángulo agregamos un elemento (objeto) al vector mediante el método add:

v.addElement( new Rectangle(x, y, 0, 0) );


Para acceder a un atributo de un objeto del vector no basta utilizar directamente el vector, como:

	v.lastElement().x


(lastElement() nos permite acceder al último elemento agregado al vector). Es necesario aclarar explícitamente que el elemento en cuestión es un rectángulo, ya que el vector puede contener objetos de cualquier tipo. Para eso usamos el casting:

	(Rectangle)v.lastElement().x


En nuestro código original reemplazaríamos por:

(Rectangle)v.lastElement().resize( x - (Rectangle)v.lastElement().x, ......



Pero es más claro si usamos una variable local de clase Rectangle, le asignamos el mismo objeto que acabamos de agregar al vector, y lo usamos en su lugar:

	Rectangle r = (Rectangle)v.lastElement();
	r.resize( x - r.x, y - r.y );


Finalmente, en el método paint() no podemos asignar el elemento hasta no saber que existe (originalmente el vector estaba vacío!). Así que un if nos permite verificar que el tamaño del vector es mayor que cero (tiene elementos), y un for nos permite dibujarlos uno por uno.

Se puede acceder a todos los elementos, uno por uno, mediante el método elementAt(x), que nos da el x-ésimo elemento del vector. El método size() nos da la cantidad de elementos (el primero es el número 0, y así):

	if (v.size() > 0)
      for (i=0; i<v.size(); i++) {
		Rectangle box = cortarRect( (Rectangle)v.elementAt( i ), d);
		g.drawRect(box.x, box.y, box.width-1, box.height-1);
	      }


Aquí no hemos creado variables intermedias ya que igualmente es claro (eso creo...).

Flicker molesto!

Bueno, el problema que nos queda es el molesto "flicker", o sea la manera en que titila el dibujo cuando movemos el mouse. Esto es porque cada vez que se llama a paint(), el fondo se borra y se redibuja todo el canvas.

Básicamente, la manera de evitarlo es reescribiendo el método update(), que es el que borra el fondo antes de llamar a paint() para que no lo borre; otro método (que es el que vamos a usar) es dibujar no sobre la pantalla sino sobre un "buffer" gráfico, y luego copiar ese buffer sobre la pantalla (lo que es mucho más eficiente que dibujar sobre la misma).

Para eso vamos a crear un par de objetos:

class miCanvas extends Canvas {
    Vector v = new Vector();
    Image	imgBuff;
    Graphics	grafBuff;
.............................


Image es una clase abstracta, madre de todas las clases que representan imágenes gráficas. Graphics es también abstracta y nos permite obtener un contexto en el cual dibujar.

Lo que vamos a hacer es modificar nuestro método paint() para que simplemente llame a update(), y redefinir el método update():

public void paint(Graphics g) {
	update(g);
}


El método update() es el que hará todo el trabajo y básicamente es como nuestro viejo paint() con algunos agregados:

public void update(Graphics g) {
	int i;
	Dimension d = size();

if (grafBuff == null) {
		imgBuff = createImage(d.width, d.height);
		grafBuff = imgBuff.getGraphics();
}
grafBuff.setColor(getBackground());
grafBuff.fillRect(0, 0, d.width, d.height);
	grafBuff.setColor(Color.red);
	grafBuff.drawRect(0, 0, d.width-1, d.height-1);
	grafBuff.setColor(Color.blue);
	if (v.size() > 0) for (i=0; i<v.size(); i++) {
		Rectangle box = cortarRect((Rectangle)v.elementAt(i), d);
		grafBuff.drawRect(box.x, box.y, box.width-1, box.height-1);
	}
	g.drawImage(imgBuff, 0, 0, this);
}


En negrita hemos indicado los agregados.

Si no está creado todavía (grafBuff==null), creamos nuestro buffer de dibujo. Para crear dicho buffer gráfico (de clase Graphics), primero creamos una imagen que en este caso tiene las mismas dimensiones que el canvas (d.width x d.height), y luego asignamos a grafBuff el contexto de dicha imagen mediante el método getGraphics(). Imagínense que con createImage(...) crean una "pantalla virtual", y getGraphics() nos da una forma de acceder a esa pantalla como si fuera real.

Utilizando dicho contexto, elegimos como color el mismo color de fondo del applet (getBackground()) y dibujamos un rectángulo lleno (fillRect(...)), borrando así cualquier cosa que hubiera estado dibujada.

En itálica hemos indicado las modificaciones a nuestro método anterior. Simplemente, en lugar de usar el contexto de la pantalla (el parámetro g del método), dibujamos sobre nuestro contexto-pantalla virtual.

Finalmente, y para poder visualizar nuestro dibujo, usamos el método drawImage sobre el contexto de la pantalla real (g), que copia nuestro contexto imgBuff en las coordenadas (0,0) sobre la pantalla. Se hace también referencia al canvas (...this): el cuarto parámetro de drawImage es un objeto de clase ImageObserver, una interface que sirve para que el objeto dentro del cual se dibuja reciba mensajes asincrónicos que le indican cómo está siendo construida la imagen, y cuándo está lista.

Animate!

Si bien puede ser un poco más complejo de entender que un dibujo directo sobre la pantalla, notarán que la implementación es directa y no trae ningún problema. Esta misma aproximación puede utilizarse para crear animaciones.

En este ejemplo, para manejar la ejecución cuadro a cuadro de la animación, usamos Threads. No se preocupen por eso, lo veremos pronto. Únicamente tengan en cuenta que nuestro applet debe implementar la clase runnable, y el thread se encarga de ejecutar el método run() que simplemente llama a repaint() y espera 100 milisegundos entre cuadro y cuadro.

El trabajo de cálculo y dibujo lo hace update(). Se los dejo para que lo estudien; no es nada complicado y también usa doble buffering (como el ejemplo anterior).

import java.awt.*;
import java.util.*;
import java.applet.Applet;

public class Ejemplo18 extends Applet implements Runnable {

    Thread	animador;
    Image	imgBuff;
    Graphics	grafBuff;
    double ang = 0.0;

    public void init() {
	resize(new Dimension (200,200));
    }

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

    public void run() {
	while (Thread.currentThread() == animador) {
		repaint();
		try {
			Thread.sleep(100);
		}
		catch (InterruptedException e) {
			break;
		}
	}
    }

    public void update(Graphics g) {
	int i;
	int dx, dy;

	Dimension d = size();

if (grafBuff == null) {
		imgBuff = createImage(d.width, d.height);
		grafBuff = imgBuff.getGraphics();
}
grafBuff.setColor(getBackground());
grafBuff.fillRect(0, 0, d.width, d.height);
	grafBuff.setColor(Color.red);
	grafBuff.drawRect(0, 0, d.width-1, d.height-1);

	grafBuff.setColor(Color.blue);
	dx = (int)(50 * Math.abs(Math.cos(ang)));
	dy = (int)(50 * Math.abs(Math.sin(ang)));
	ang = ang + 0.1;
	if (ang>2*Math.PI) ang = 0.0;
	grafBuff.drawRect(100-dx, 100-dy, 2*dx, 2*dy);

	g.drawImage(imgBuff, 0, 0, this);
    }
}




Como siempre, me despido hasta la clase que viene...

 

<> Página de tutoriales <> Página Anterior <> Siguiente Capitulo <>