Java2D the Groovy way
Join the DZone community and get the full member experience.
Join For FreeA 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.
Opinions expressed by DZone contributors are their own.
Comments