Herramientas de usuario

Herramientas del sitio


highlevel:java:2d

Programación gráfica 2d en Java

Graphics

  • Para dibujar sobre un Frame en java sustuiremos el método: public void paint (java.awt.Graphics g)
  • El método repaint hace que el Frame vuelva a repintarse.
  • Para acceder al área de cliente, saber donde empieza a verse el dibujo creado (ventana sin barra de título), utilizaremos el método getRootPane de un objeto JFrame. Este JRootPane (lo que devuelve el método) también contiene un método getGraphics.

Imágenes

  • La clase que corresponde a una imágen es java.awt.Image. Podemos leer una imágen de un fichero mediante el método java.awt.Toolkit.getDefaultToolkit().getImage.
  • La clase javax.imageio.ImageIO contiene métodos estáticos que te permiten leer (read) de un archivo una imágen o escribir (write), usando objetos java.io.File.

Ejemplos

  • Cargar una imágen a partir de un array de bytes
BufferedImage image = ImageIO.read (new ByteArrayInputStream (rawImageBytes));
  • Guardar una imágen en un array de bytes
ByteArrayOutputStream baos = new ByteArrayOutputStream( 1000 );
ImageIO.write( aBufferedImage, "jpeg", baos );
baos.flush();
byte[] resultImageAsRawBytes = baos.toByteArray();
baos.close();
  • Cargar una BufferedImage a partir de un fichero
BufferedImage image = ImageIO.read( new File( "rabbit.jpg" ) );
  • Guardar una BufferedImagen en un fichero
ImageIO.write( aBufferedImage, "jpeg", new File ("snap.jpg"));
  • Convertir una imágen en BufferedImage
BufferedImage bufferedImage = new BufferedImage ( imageWidth,
                                                  imageHeight,
                                                  BufferedImage.TYPE_INT_BGR  );
bufferedImage.createGraphics().drawImage( image, 0, 0, this);
  • Cargar una imágen a partir de una URL
BufferedImage image = null;
try
   {
   image = ImageIO.read( url );
   }
catch ( IOException e )
   {
   System.out.println( "image missing" );
   }

Clase java.awt.Graphics

Métodos útiles:

  • drawImage: drawImage(<Image>, 0,0,this) - Dibuja una imágen en la posición indicada (0,0).
  • drawImage: drawImage(<Image>,posX, posY, tamW, tamH, this); - Dibuja una imágen en posX,posY con el tamaño tamW, tamY.
  • clearRect: clearRect(0,0,this.getWidth(), this.getHeight()) - Limpia una zona del frame
  • setFont y setColor: Asignan fuente y color respectivamente para los elementos que posteriormente se dibujarán.
  • drawString: Nos permite dibujar un String en una posición concreta. Pero antes de ello debemos haber asignado la fuente y el color.

java.awt.GraphicsEnvironment

Mediante esta clase podemos recopilar información del sistema de gráficos de la máquina donde se ejecuta el programa, para ello debemos coger el GraphicsEnvirontment local llamando al método getLocalGraphicsEnvirontment. Por ejemplo, para recoger las fuentes existentes en la máquina:

GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
	for (String s : ge.getAvailableFontFamilyNames())
		System.out.println(s);

DobleBuffer

  • La técnica del doble buffer en java consiste en dibujar sobre una imágen lo que va a ir a la pantalla, y cuando ya esté todo dibujado en ella, mostrar dicha imágen sobre el Frame.
  • Fases
    1. Creamos un objeto bufferedImage, usando por ejemplo el siguiente constructor: java.awt.image.BufferedImage (400, 300, java.awt.image.BufferedImage.TYPE_4BYTE_ABGR)
    2. Deberemos recordar llamar al método repaint para repintar cuando veamos necesario.
    3. Recogemos el Graphics del objeto BufferedImage: java.awt.Graphics bg = <obj bufferedimage>.getGraphics();
    4. Y dibujamos sobre él como de un Graphics de Frame se tratara.
    5. Luego, al Graphics del Frame le mandamos dicho objeto: g.drawImage(<objeto BufferedImage>, 0, 0,this);
    6. Ten cuidado con el tipo de imágen que eliges, si no vas a usar Alpha no la crees como antes la hemos creado, si no bajará el rendimiento a tu programa, usa RGB normal (TYPE_INT_RGB).

Adición de un color Alpha a una imágen

Hola amigos!! Hoy vamos a asignar un color transparente para la imágen, para ello necesitamos:

  1. Un BufferImage
  2. Un Color el cual asignaremos como alpha
  3. Saber como coger el color de un pixel (<bufferimage>.getRGB (X, Y)) y como asignarlo (<bufferimage>.setRGB(x,y,color)).
  4. Ya'stá.

Escogeremos el color que será el alpha y recorreremos la imágen pixel a pixel, si ese pixel es el color en cuestión le diremos que no tiene opacidad:

	java.awt.Color colorAlpha = new Color(buffer.getRGB(0,0));
	for (int i=0; i<buffer.getHeight(); i++)
		for (int j=0; j<buffer.getWidth(); j++)
			if (new Color(buffer.getRGB(j, i)).equals(colorAlpha))
				buffer.setRGB(j,i,0&0&0&0);

Donde colorAlpha ha sido el color elegido (el que tiene el primer pixel de arriba a la izquierda) y buffer es la BufferImage.

Graphics2D

  • La clase java.awt.Graphics2D hereda de la clase Graphics y proporciona características avanzadas para el dibujo en un control.
  • Si la ventana permite la api avanzada de gráficos 2d, en el método paint en vez de venir como parámetro un objeto de la clase Gráficos viene uno de la clase Gráficos2d, que hereda de esta. Para usarlo hacemos forzamos un cast: Graphics2D g2 = (Graphics2D)g;

Propiedaddes del Graphics2D

  • setStroke:

Estilo de lápiz. Para usarla lo que hacemos es pasarle un objeto java.awt.BasicStroke. El primer parámetro de este es la anchura. Los siguientes son constantes estáticas de la clase BasicStroke, por ejemplo BasicStroke.CAP_ROUND redondearía los bordes. Un Stroke como el siguiente haría una línea discontinua:

	float dash[] = {10.0f, 5.0f};
	g2.setStroke (new BasicStroke (8.0f, BasicStroke.CAP_BUTT,
              		 BasicStroke.JOIN_MITER, 10.0f, dash, 0.0f));
  • setPaint

Estilo de relleno. Podemos usar distintos tipos de relleno, pasandole un objeto color nos crea un relleno plano, pero si le pasamos un objeto java.awt.GradientPaint podemos conseguir rellenos con degradados.

	java.awt.GradientPaint redtowhite = new java.awt.GradientPaint
		(0,0,Color.RED,300,300,Color.WHITE);
        	G.setPaint(redtowhite);

TexturePaint nos será útil para crear figuras rellenas con una BufferedImage como textura.

  • setComposite

Estilo de composición de dibujos. Lo usamos para hacer operaciones con shapes ya dibujadas sobre el Graphics (unión, intersección…). Debemos usarla sobre el Graphics de un objeto BufferedImage.

  • setTransform

Transformación de rotación, escala…

  • setClip

Restricción de dibujo. Una vez pasemos a este método del objeto Graphics una shape, todo lo dibujado quedará enmascarado por ese polígono.

  • setFont

Fuente que se usará.

  • setRenderingHints

Forma de renderizar.

setTransform y AffineTransform

El método setTransform aplica una transformación (escalado, traslación, rotación…) al objeto Graphics. Esta transformación se hace mediante el objeto AffineTransfrom, este tiene métodos para cada transformación permitida. Y una vez pasado al setTransform de un graphics todo lo que se dibuje sobre él a partir de entonces será transformado.
Si sólo queremos transformar una imágen tenemos dos formas de hacerlo:

  1. Dibujar la imágen con la transformación ya aplicada. Existe un método sobreescrito del drawImage al que le pasas: imágen, transformación y el lugar donde se muestra, si llamamos a este la imágen nos aparecerá transformada. Hemos de pensar que se dibujará en el punto 0,0, para moverla hemos de trasladarla
  2. Aplicar la transformación al Graphics y dibujar la imágen, luego resetear la transformación y la volvemos a aplicar al Graphics (¿? no se pasan los objetos por referencia?). Para resetearla únicamente hemos de llamar a su método setToIdentity.

Para hacer un espejo horizontal haremos un: scale(-1,1). Al método de escalado se le pasan dos doubles donde 1,1 es el tamaño actual, 0.5,0.5 la mitad… Si la aplicamos a todo el Graphics este se nos girará y probablemente no veamos parte de la pantalla.
Otra forma de aplicar las transformaciones es: nos quedamos con la transformación actual, creamos la nuestra y aplicamos nuestras acciones. Dibujamos. Y volvemos a aplicar la inicial:

	AffineTransform origAT = g2d.getTransform( );
	AffineTransform rot = new AffineTransform( );
	rot.rotate( Math.toRadians(angle), img.getWidth( )/2, img.getHeight( )/2);
	g2d.transform(rot);
	g2d.drawImage(src, 0, 0, null);
	g2d.setTransform(origAT); 

java.awt.geom

En esta clase encontramos métodos para dibujar elementos geométricos de forma avanzada. (Curvas, Puntos, Rectángulos (hasta con punta redondeada!! (RoundRectangle2D)), elipses…). Para dibujarlas llamamos al método draw del objeto Graphics2D, a él se le pasa una Shape (un objeto de los anteriores). Tengamos en cuenta que generalmente para hacer una nueva línea, rectángulo…. NO usaremos la clase Line2D Rectangle2D, sino una clase interna “Double” o “Float”: G.draw(new Line2D.Double(new Point2D.Double(0, 0), new Point2D.Double(400, 300)));

  • Si en vez de querer que se dibuje queremos que se rellene usaremos el método 'fill' en vez del 'draw'.
  • Para dibujar tipos de línea con curvas usaremos las clases: Cubic y QuadCurve2D.
  • Si quisiesemos dibujar texto con texturas usaremos: java.awt.font.TextLayout
  • Un objeto Shape también contiene un método llamado contains al cual se le pasan dos números que son las coordenadas respecto a la ventana, te devolverá true o false según estén dentro de la shape, mediante este podemos intentar interactuar con el usuario para saber si se ha clicado sobre un lugar en concreto.
  • La clase java.awt.geom.Area nos permite crearla con un objeto Shape. Podemos crear varios objetos Shape y, sobre ellos aplicar operaciones booleanas: Unión, intersección, substracción… Teniendo varios objetos lo que hacemos es, sobre uno, llamar al método de la operación deseada pasandole el otro y mandar a dibujar el area que hemos escogido.
		Rectangle rect1 = new Rectangle(20,50,100,100);
		Area a = new Area(rect1);
		Rectangle rect2 = new Rectangle(90,90,100,100);
		Area b = new Area(rect2);
		b.intersect(a);
		G.fill(b);

Anti-Alising en el texto

Podemos configurar el objeto de Graphics 2d para que al dibujo de texto no sea pixelado, sino suavizado:

	... (Graphics g)
	if (g instanceof Graphics2D) {
            	Graphics2D g2 = (Graphics2D)g;
	            g2.setRenderingHint(
            	    RenderingHints.KEY_TEXT_ANTIALIASING,
	                RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
	}
	g.drawString("Hola", 50, 100);

Medir un String

Para medir un texto usaremos la clase java.awt.FontMetrics, no podremos crear un objeto de esta clase mediante un constructor (ya que son protegidos), pero sí que podremos pedir a un objeto Graphics que nos devuelva el FontMetrics que tiene asociado, mediante el método getFontMetrics, y usarlo. Para que coja un string usaremos el método stringWidth, que ya te devuelve el ancho, y una vez con un string cogido podemos pedir el alto: getHeight.
El código siguiente te centra un String (str) en el frame:

		FontMetrics met = G.getFontMetrics();
		int w = met.stringWidth(str);
		int h = met.getHeight();
		int xFin = (this.getWidth() / 2) - (w / 2);
		int yFin = (this.getHeight() / 2) - (h / 2);
		G.drawString(str, xFin,yFin);

Hemos de recordar que si cogemos el FontMetrics y luego cambiamos la fuente el FontMetrics del Graphics cambia pero el nuestro no (no debería ser una referencia???? ¿?¿?), por lo que lo tendríamos que volver a recoger.

Creación de Sprites

Llegados a un punto, querremos hacer una pequeña animación, para ello podremos tener varias imágenes y las iríamos mostrando de una en una. Pero tal vez queramos organizarlo de otra forma y montar todas las imágenes en una y, cuando vayamos iniciemos el programa, este leerá la imágen grande y la convertirá en pequeñas. Para ello haremos lo siguiente:

  1. Crearemos un BufferedImage de la imágen grande.
  2. Calcularemos cuanto será el tamaño de cada imágen pequeña, para ello haremos el ancho de la imágen (getWidth()) dividido por el número de imágenes pequeñas que caben en horizontal (y tendremos el ancho de las pequeñas). También dividiremos el alto de la grande por el número de imágenes que hay en vertical (y ya tenemos el alto).
  3. Ahora crearemos la estructura donde guardaremos las imágenes pequeñas, si escogemos un array este deberá ser de tamaño “número de figuras en horizontal” * “número de figuras en vertical”. Yo escojo un array de BufferImage.
  4. Iremos inicializando cada elemento del array.
  5. Dibujaremos en cada BufferImage del array la porción de imágen que le toque, para ello cogeremos su Graphics y sobre él haremos un drawImage. Usaremos el método al cual se le pasan como parámetros: 1) Una imágen, 2) par de ints correspondientes al punto donde se empieza a dibujar, 3) par de ints correspondientes al tamaño, 4) par de ints donde se empieza a coger de la imágen destino, 5) par de ints hasta donde se cogen de la imágen destino, 6) el ImageObserver.

El código siguiente llena el array de ImageBuffer (strip) que lee del BufferImagen orígen (img). Sabemos que una imágen pequeña tiene tamaño width\height. La imágen orígen son 6 imágenes (numImgs) pequeñas puestas en horizontal, sólo hay una fila de sprites:

for (int i=0; i < numImgs; i++) {
	strip[i] = new BufferedImage(width, height, BufferedImage.TYPE_4BYTE_ABGR);
	Graphics g = strip[i].getGraphics();
	g.drawImage(img, 0,0, width, height, width * i, 0, (width * i)+width, height, null);
	g.dispose();
}
highlevel/java/2d.txt · Última modificación: 2020/05/09 09:25 (editor externo)