Andres is a DZone Zone Leader and has posted 143 posts at DZone. You can read more from them at their website. View Full User Profile

Java2D the Groovy way

01.17.2008
| 10965 views |
  • submit to reddit

A small tutorial that will show you how to create eye-catching Java2D drawings without the hassle, thanks to one of Groovy's builders: GraphicsBuilder.

 

Introduction

Java2D has been available for years, ever since Swing came out, still creating eye-catching and well-performant drawings is not an easy task for anyone (unless you happen to be a frenchman that goes by the nickname Gfx). That is precisely the problem GraphicsBuilder is aiming to solve. In case you were living under a rock these past years (or unplugged from the net) dynamic languages have taken center stage, one of such languages is Groovy. Groovy is one of many languages that run on the JVM, but what makes it stand over the others is it seamless integration with the Java language and the Java platform itself.

Because Groovy is a dynamic language you may take advantage of optional typing, in other words, duck typing is a reality, thus reducing a significant amount of lines of code if you know what you are doing, but if you are still too attached to types, don't worry, Groovy is happy with types too. Another interesting bit about Groovy is that it has meta programming capabilities, meaning that you can extend the behavior of a class at runtime, even if it is a final class like java.lang.String (don't worry its safe). Due to these two facts and some other features Groovy allows the builder pattern to be implemented in a simple yet elegant way, and this is where GraphicsBuilder comes into play.

GraphicsBuilder is in a sense a collection of nodes, where each node represents a drawing operation like shapes, paints, transformations and images. A drawing is then composed from a group of operations put together by the builder in the order they where defined, taking into account that some operations accept nesting of others, thus enabling a nice structured hierarchy of operations. This may sound a little bit familiar coming from Swing, as every component is a link in the hierarchy chain. Java2D does all its magic with a Graphics (or Graphics2D) instance, which allows you to draw shapes, set colos and paints, clip the drawing surface to an specific shape or bounds and more; what GraphicsBuilder provides is a thin abstraction layer over those basic operations.

Let's get started, our goal is to draw the following image

 

Drawing the background

First step would be to draw the background, notice that it is a rectangle with rounded corners, which happens to be one of the basic shapes that Java2D provides. Let me show you the code for it

// define some useful variables
def width = 300
def height = width*3/4
def gb = new GraphicsBuilder()
// group all operations in the same set
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') )
}
}

A couple of things need to be explained here. First we define some useful variables for the image's width and height as they will be reused later, then we group all operations in the same set, making them easier to handle. A global transformation is applied to the whole group, so all nested operations will have an offset of 10 pixels in x-axis and 10 pixels on the y-axis. Antialias is turned on to get smooth edges, then the base shape of the background is created but is not drawn right away (thanks to asShape=true), this is because we will use that shape twice: firstly to clip the drawing area and secondly to draw the actual background. You may notice that the borderColor of the bakground shape has a value of false (it could have been 'none' too), that prevents the border to be drawn, as it may produce some undesired rendering artifacts as can be seen in the following images, the first one has borderColor=false and the second one has the default borderColor (usually black).

 

Drawing radial lines

On to the next step, drawing the radial lines. It is obvious that we would have to do some math with angles and radii as Java2D doesn't provide a quick shape or facility to do this kind of drawing out of the box, but don't worry GraphicsBuilder does. A rays shape requires a center (cx,cy) and a radius as minimum properties, but you can set others as the number of rays to be drawn (rays), the extent of the angle per ray (extent) and the starting angle (useful for quick rotations). Append the following code after the background draw operation

   rays( cx: width/2, cy: height/2, radius: width*2, rays: 30, extent: 0.4, borderColor: false ){
colorPaint( color('black').derive(alpha:0.5) )
}

Which yields the following image

 

 

Notice that the final image has a hint of color change coming from the center, we will use a radial gradient to achieve that effect, which serves as an introduction to multipaints. As the name implies, multipaints are a collection of paints that can be applied at the same time to a single shape, in this case we would like a base color and a radial gradient applied to the rays. A radial gradient requires a center (cx,cy), a radius, and at least two stop definitions; a stop takes care of setting where in the gradient a color will be used. We update the rays shape with the following code

   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 ) }
}
}
}

Which give us the following result

 

 

 

Drawing a star

Ah stars, saddly Java2D doesn't provide a base shape for them, wouldn't it be nice if GraphicsBuilder did? sure thing, it does! Stars have similar properties as rays, the only difference being how many points would you like and the inner and outer radii. The next code should do the trick

   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 ) }
}
}
}

 

 

 

Multipaints are used again to paint a base white color and a cyan/gray based radial gradient, now on to the radial highlitghts of the star, we will use a rays shape again, with a longer extent and a radial gradient as paint. The highlights must be drawn before the star, effectively rendering them behind the star.

   // 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 ) }
}
}

Notice the extent of the rays is set to 0.75 (it ranges from 0 to 1) and that the angle has been changed too.

 

 

 

Drawing the text

We are in the final step, drawing the words "Groovy" and "Zone", they have nice gradients again and a white border. But there is a trick here, instead of setting a border width value, the white border is actually the same text drawn behind, this is because the font draws the whole outlines of the letters and we are only interested in the contour of the word. So let's draw the base text first

   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) )
}
}
}

 

 

 

Now to put the final touch, let's draw the white text before the blue one and we are done. Just insert the next snippet betwen the font definition and the first text operation

   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')

 

 

 

And that's it! Feel free to play with colors and other shapes. GraphicsBuilder includes a basic application named GraphicsPad that will help you create your own drawings. Those drawings can be exported into standalone scripts, similar to the one used to create the pictures you see on this tutorial. GraphicsBuilder's doc site includes more information on the available shapes, paints, operations and other stuff you can do with GraphicsBuilder right now.

You'll find the complete script in the resources section, just remember to remove the .txt extension or load it up with GroovyConsole and run it.

I hope you enjoyed reading this tutorial as much as I did writing it.

 

Legacy
Article Resources: 
Published at DZone with permission of its author, Andres Almiray.