Un proyecto de:
ImaginaWorks

Últimos mensajes en los foros

Artículos para tus primeros pasos

Si estás empezando a introducirte en el mundo de Groovy y Grails, no te pierdas nuestros artículos básicos: 

Entrevistas con los expertos
 

Los protagonistas te cuentan de qué van los proyectos más importantes del mundo Groovy:


Sitios Recomendados
 
 
 
 
JavaHispano
 
 
 
 
 
 
 
 

Java2D, a la manera de Groovy

domingo 20/01/2008

Introducción

Java2D es parte fundamental de las librerías de Java desde hace muchos años, desde que Swing se hizo público, sin embargo crear dibujos de manera eficiente y que plazcan a la vista al mismo tiempo no es tarea facil (claro esta a menos que seas un desarrollador francés que use el apodo 'gfx'). Este es precisamente el problema que GraphicsBuilder resuelve.

GraphicsBuilder es en términos llanos una colección de nodos, donde cada nodo representa una instrucción de Java2D como figuras, pinturas, transformaciones e imágenes. Un dibujo se compone por un grupo de operaciones, en el mismo orden en el que fueron declaradas al usar el builder, tomando en cuenta que algunas operaciones aceptan la anidación de otras operaciones, por lo tanto habilitando la creación de una jerarquía de operaciones; si esto te suena familiar es por que asi trabaja Swing, dado que cada componente Swing es un eslabón en la cadena. Java2D realiza tyoda su magia a través de instancias de la clase java.awt.Graphics (o Graphics2D), la cual permite dibujar figuras, aplicar colores y/o pinturas, restringig el area visible (clip) y mas, GraphicsBuilder provee una ligera capa de abstracción sobre esas operaciones básicas

Empezemos pues con un ejemplo, la meta es dibujar la siguiente imagen



 

Dibujando el fondo

El primer paso es dibujar el fondo de la imagen, notarás que es un rectángulo con esquinas redondeadas, resulta que esta figura es una una de las figuras básicas que Java2D provee, por lo tanto soportada por GraphicsBuilder.

// definimos un par de variables útiles
def width = 300
def height = width*3/4
// agrupamos todas las operaciones en el mismo conjunto
def graphicsOperation = gb.group {
// transladamos todo el grupo a una posicion arbitraria
transformations { translate( x: 10, y: 10 ) }
// habilitamos antialias
antialias( 'on' )
// figura base, sera reusada posteriormente
rect( x: 0, y: 0, width: width, height: height, arcWidth: 40, arcHeight: 40,
asShape: true, id: 'background' )
// restringimos la vista al interior de la figura
clip( background )
// dibujamos el rectángulo
draw( background, borderColor: false ){
// un gradiente diagonal desde 'blue' a 'cyan'
gradientPaint( x1: 0, y1: 0, x2: 50, y2: 50, color1: color('blue'), color2: color('cyan') )
}
}

Tomemos un momento para ver que hace el código. Primero definimos un par de variables, la anchura y altura de la imagen, puesto que seran usadas en otras operaciones posteriores, luego agrupamos todas las operaciones en el mismo conjunto, lo cual facilita su manejo. Aplicamos una transformación global al grupo, por lo que las operaciones anidadas se transladarán 10 pixeles en el eje x, y 10 pixeles en el eje y. Habilitamos antialias para obtener bordes suaves, luego la figura del fondo es creada pero no se dibuja inmediatamente (puesto que asShape=true), esto es por que la usaremos en dos lugares distintos: primero para restringir el area de dibujo y segundo para dibujar el fondo per se. Notarás que el atributo borderColor tiene un valor false (tambien podemos usar 'none') que previene se dibujo el borde de la figura puesto que puede producir efectos no deseados en la imagen final, como puede verse en las siguientes imágenes. La primera tiene borderColor=false mientras que la segunda usa el valor por omisión (usualmente el color negro)

 

Dibujando líneas radiales

Continuamos con el siguiente paso, dibujar líneas radiales. Es obvio que tendremos que hacer uso de algo de álgebra y trigonometría para calcular ángulos y radios, dado que Java2D no provee una figura base para este tipo de operación, afortunadamente GraphicsBuilder si lo hace. Rays es una figura que requiere de un centro (cx,cy) y un radio como mínimo, también puedes definir el número de rayos a dibujar, la extensión del ángulo de cada rayo, así como el ángulo de rotación inicial. Basta con anexar el siguiente código después de la operación que dibuja el fondo.

rays( 
	cx: width/2, 
	cy: height/2, 
	radius: width*2, 
	rays: 30, 
	extent: 0.4, 
	borderColor: false ){

colorPaint( color('black').derive(alpha:0.5) )
}

 

El cual nos da el siguiente resultado

 

Notarás que la imagen final tiene un ligero cambio de color proveniente del centro, usaremos un gradiente radial para obtener ese efecto, lo cual es una excusa perfecta para introducir multipaints. Como su nombre sugiere, miltupaints es una colección de pinturas que se aplican a la misma figura al mismo tiempo, en este caso un color base y un gradiente radial. El gradiente require de un centro (cx,cy), un radio, y al menos 2 deficiones de 'paradas'; una parada define la posición y el color a usar. Actualizamos las líneas radiales con el siguiente código

rays( 
	cx: width/2, 
	cy: height/2, 
	radius: width*2, 
	rays: 30, 
	extent: 0.4, 
	borderColor: false ){    
		multiPaint {       
			colorPaint( color('black').derive(alpha:0.5) )       
			radialGradient( 
				cx: width/2, 
				cy: height/2, 
				radius: width/3 ){          
					stop( offset: 0, color: color('white').derive(alpha:0.5) )          
					stop( offset: 1, color: color('white').derive(alpha:0.0) )          
					transformations { translate( x: 10, y: 10 ) 
				}       
			}    
		} 
	} 

 

El cual nos da el siguiente resultado

 

Dibujando la estrella

Ah estrellas..., desgraciadamente Java2D no provee una figura básica para dibujarlas, no sería genial que GraphicsBuilder lo hiciera? por supuesto que si! Star tiene atributos similares a Rays, la diferencia estriba en cuantos puntos tendrá la estrella y que ésta requiere de dos radios, interior y exterior. El siguiente código muestra como dibujar la estrella

star( cx: width/2, cy: height/2, or: 30, ir: 15, borderColor: 'white' ){    
	basicStroke( width: 2 )    
	multiPaint {       
		colorPaint( color('white') )       
		radialGradient( cx: (width/2)+10, cy: (height/2)-10, radius: 50 ){          
			stop( offset: 0, color: color('cyan').derive(alpha:0.4) )          
			stop( offset: 1, color: color('gray').derive(alpha:0.0) )          
			transformations { translate( x: 10, y: 10 ) 
			}       
		}    
	} 
} 


 

Hacemos uso de multipaints de nuevo para pintar un color base (blanco) y un gradiente radial (cyan/gris) en la estrella. Ahora a dibujar el efecto de luz proveniente de la estrella, usaremos Rays de nuevo, pero esta vez con una extensión de angulor mayor y un gradiente radial de color base blanco. El efecto de luz debe dibujarse detrás de la estrella por lo que el siguiente código debe anexarse antes del código que dibuja la estrella

rays( 
	cx: width/2, 
	cy: height/2, 
	radius: height*4/5, 
	rays: 5, extent: 0.75,        
	angle: 45-(360/10*0.5), 
	borderColor: false ){    
		radialGradient( cx: (width/2)+10, 
		cy: (height/2)-10, 
		radius: height/2 ){       
			stop( offset: 0, color: color('white').derive(alpha:0.5) )       
			stop( offset: 1, color: color('gray').derive(alpha:0.0) )       
			transformations { translate( x: 10, y: 10 ) 
		}    
	} 
} 


 

Dibujando el texto

Ya estamos en el paso final, dibujar las palabras "Groovy" y "Zone", las cuales tienen unos agradables gradientes y un border blanco. Tendremos que hacer uso de un truco en este caso, en vez de definir valores para el borde dibujaremos el texto en color blanco detrás del texto en azul, puesto que la fuente que escogí en particular dibuja el borde de todas las letras y lo que nos interesa es solo el contorno de la palabra. Asi que a dibujar el texto base primero

def fontFile = new File("WHOOPASS.TTF") 
font( Font.createFont(Font.TRUETYPE_FONT,fontFile).deriveFont(58.0f) )  
text( text: 'Groovy', x: (width/5)-5, y: height/10, borderColor: false ){    
	multiPaint {       
		colorPaint( color('blue') )       
		linearGradient( x2: 0, y2: 50 ){          
			stop( offset: 0, color: color('cyan').derive(alpha:0.3) )          
			stop( offset: 1, color: color('cyan').derive(alpha:0.8) )       
		}    
	} 
} 
text( text: 'Zone', x: (width/4)+10, y: height*7/10, borderColor: false ){    
	multiPaint {       
		colorPaint( color('blue') )       
		linearGradient( x2: 0, y2: 50 ){          
			stop( offset: 0, color: color('cyan').derive(alpha:0.8) )          
			stop( offset: 1, color: color('cyan').derive(alpha:0.3) )       
		}    
	} 
} 


 

Basta el toque final, dibujar el texto en color blanco y listo. Insertando el siguiente código entre la definición de la fuente y el primer texto azul será suficiente

text( text: 'Groovy', x: (width/5)-5, y: (height/10), borderWidth: 6, borderColor: 'white' ) 
text( text: 'Zone', x: (width/4)+10, y: height*7/10, borderWidth: 6, borderColor: 'white') 


 

Listo! hemos terminado. Una vez familiarizado con las opciones de figuras y pinturas puedes jugar con distintos valores para colores y otras figuras. GraphicsBuilder incluye una aplicación sencilla llamada GraphicsPad, la cual te permite crear tus propios dibujos, los cuales pueden ser exportados a scripts individuales, como el que fue usado para crear este tutorial. El sitio de documentación de GraphicsBuilder inclute mas información sobre figuras, pinturas y otras operaciones.

Espero que hayas disfrutado leer este tutorial tanto como yo al escribirlo.

Código completo del script:

import java.awt.*
import java.awt.geom.*
import javax.swing.*
import org.jdesktop.swingx.geom.*
import groovy.swing.SwingBuilder
import groovy.swing.j2d.*
import groovy.swing.j2d.geom.*

def gb = new GraphicsBuilder()
def helpers = ["Jdk6GraphicsBuilderHelper",
               "SwingXGraphicsBuilderHelper",
               "BatikGraphicsBuilderHelper"]
helpers.each { helper ->
   try{
      Class helperClass = Class.forName("groovy.swing.j2d.${helper}")
      helperClass.registerOperations( gb )
   }catch( Exception e ){
      System.err.println("Couldn't register ${helper}")
   }
}

def width = 300
def height = width*3/4
def graphicsOperation = gb.group {
   // translate the whole group to an arbitrary position
   transformations { translate( x: 10, y: 10 ) }
   // turn on antialiasing
   antialias( 'on' )
   // base background shape, it will be reused for clipping
   rect( x: 0, y: 0, width: width, height: height, arcWidth: 40, arcHeight: 40,
         asShape: true, id: 'background' )
   // clip everything outside of the background shape
   clip( background )
   // draw the actual background
   draw( background, borderColor: false ){
      // a nice downward diagonal gradient from 'blue' to 'cyan'
      gradientPaint( x1: 0, y1: 0, x2: 50, y2: 50, color1: color('blue'), color2: color('cyan') )
   }

rays( cx: width/2, cy: height/2, radius: width*2, rays: 30, extent: 0.4, borderColor: false,
   angle: animate(0..360, interval: 1, startDelay: 1000, duration: 10000,
                   repeatCount: 4, repeatBehavior: RB.REVERSE ) ){
   multiPaint {
      colorPaint( color('black').derive(alpha:0.5) )
      radialGradient( cx: width/2, cy: height/2, radius: width/3 ){
         stop( offset: 0, color: color('white').derive(alpha:0.5) )
         stop( offset: 1, color: color('white').derive(alpha:0.0) )
         transformations { translate( x: 10, y: 10 ) }
      }
   }
}

// star highlights
rays( cx: width/2, cy: height/2, radius: height*4/5, rays: 5, extent: 0.75,
      angle: 45-(360/10*0.5), borderColor: false ){
   radialGradient( cx: (width/2)+10, cy: (height/2)-10, radius: height/2 ){
      stop( offset: 0, color: color('white').derive(alpha:0.5) )
      stop( offset: 1, color: color('gray').derive(alpha:0.0) )
      transformations { translate( x: 10, y: 10 ) }
   }
}

star( cx: width/2, cy: height/2, or: 30, ir: 15, borderColor: 'white' ){
   basicStroke( width: 2 )
   multiPaint {
      colorPaint( color('white') )
      radialGradient( cx: (width/2)+10, cy: (height/2)-10, radius: 50 ){
         stop( offset: 0, color: color('cyan').derive(alpha:0.4) )
         stop( offset: 1, color: color('gray').derive(alpha:0.0) )
         transformations { translate( x: 10, y: 10 ) }
      }
   }
}

def fontFile = new File("WHOOPASS.TTF")
font( Font.createFont(Font.TRUETYPE_FONT,fontFile).deriveFont(58.0f) )
text( text: 'Groovy', x: (width/5)-5, y: (height/10), borderWidth: 6,
    borderColor: 'white' )
text( text: 'Zone', x: (width/4)+10, y: height*7/10, borderWidth: 6,
    borderColor: 'white')

text( text: 'Groovy', x: (width/5)-5, y: height/10, borderColor: false ){
   multiPaint {
      colorPaint( color('blue') )
      linearGradient( x2: 0, y2: 50 ){
         stop( offset: 0, color: color('cyan').derive(alpha:0.3) )
         stop( offset: 1, color: color('cyan').derive(alpha:0.8) )
      }
   }
}
text( text: 'Zone', x: (width/4)+10, y: height*7/10, borderColor: false ){
   multiPaint {
      colorPaint( color('blue') )
      linearGradient( x2: 0, y2: 50 ){
         stop( offset: 0, color: color('cyan').derive(alpha:0.8) )
         stop( offset: 1, color: color('cyan').derive(alpha:0.3) )
      }
   }
}
}

def swing = SwingBuilder.build {
   frame( id: 'frame', title: '', size: [330,275], locationRelativeTo: null ){
      panel( new GraphicsPanel(), graphicsOperation: graphicsOperation )
   }
}

swing.doLater { frame.visible = true }

 

 

Andrés es un Programador Java certificado, Desarrollador de Aplicaciones Web certificado, con más de 7 años de experiencia en diseño y desarrollo de aplicaciones. El ha estado involucrado en muchos desarrollos de aplicaciones web y desktop seleccionando el conjunto de herramientas y tecnologías mas apropiadas. También ha sido profesor de cursos de ciencias de la computación en el instituto educativo más prestigioso de México. Sus interese actuales incluyen arquitectura de software, Developer Testing, Groovy, Spring, AOP y Swing.


Más información: http://groovy.org.es/stories

Contenidos relacionados:



Comentarios

Java2D, a la manera de Groovy

primolarry - lunes 21/01/2008

Hola,

 En primer lugar gracias por el tutorial, me está siendo muy útil. 

Quería preguntarte por los jars necesarios, he incluido el de groovy-all-1.0 y el de swing-0.9.1., pero me falta encontrar el de los dos ultimos imports, llevo un rato googleando pero no estoy teniendo suerte.

Muchas gracias, un saludo

Álvaro 

Re:Java2D, a la manera de Groovy

Andres Almiray - martes 22/01/2008

Alvaro, para utilizar Graphicsbuilder necesitas groovy 1.5.1 El instalador para Windows incluye todas las dependencias que necesitas, encontraras la ultima version de GraphicsBuilder en el sitio del proyecto http://groovy.codehaus.org/GraphicsBuilder puesto que el instalador provee una version anterior.

Re:Re:Java2D, a la manera de Groovy

primolarry - martes 22/01/2008

Hola Andrés,

Gracias por tu respuesta. Utilizo ubuntu, así que instale el .deb. He incluido el jar que venia con el paquee (groovy-all-1.5.1.jar) pero sigo teniendo problemas con los siguientes imports:

import org.jdesktop.swingx.geom.*;

import groovy.swing.j2d.*;
import groovy.swing.j2d .geom.*;

Que no parecen incluidos en el jar. Traté de incluir los otros jars que venían en el paquete, pero eso no resolvió el problema.

Gracias por tu tiempo, un saludo

Álvaro 

Re:Re:Re:Java2D, a la manera de Groovy

Andres Almiray - miércoles 23/01/2008

Alvaro, los paquetes org.jdesktop.swingx los provee swingx-0.9.1.jar, los otros paquetes los provee la version 0.4.4 de GraphicsBuilder (hay una liga para descargarlo en el sitio del proyecto). Ya que estamos entrados en gastos, te recomiendo borrar o comentar la siguiente linea del script

   angle: animate(0..360, interval: 1, startDelay: 1000, duration: 10000,
                   repeatCount: 4, repeatBehavior: RB.REVERSE ) ){

dado que necesitaras tambien timingframework-1.0.jar para ejecutarlo.

Saludos.

Tienes que estar registrado para iniciar sesión y poder publicar tus comentarios