====== 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**.
* [[highlevel:java:xtra#archivos_.jar|Leer una imágen de un .jar]]
=== 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(, 0,0,this)// - Dibuja una imágen en la posición indicada (0,0).
* **drawImage**: //drawImage(,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
- Creamos un objeto bufferedImage, usando por ejemplo el siguiente constructor: **java.awt.image.BufferedImage (400, 300, java.awt.image.BufferedImage.TYPE_4BYTE_ABGR)**
- Deberemos recordar llamar al método repaint para repintar cuando veamos necesario.
- Recogemos el Graphics del objeto BufferedImage: **java.awt.Graphics bg = .getGraphics();**
- Y dibujamos sobre él como de un Graphics de Frame se tratara.
- Luego, al Graphics del Frame le mandamos dicho objeto: **g.drawImage(, 0, 0,this);**
- 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:
- Un BufferImage
- Un Color el cual asignaremos como alpha
- Saber como coger el color de un pixel (.getRGB (X, Y)) y como asignarlo (.setRGB(x,y,color)).
- 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
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:
- 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
- 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:
- Crearemos un BufferedImage de la imágen grande.
- 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).
- 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.
- Iremos inicializando cada elemento del array.
- 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();
}