Over a million developers have joined DZone.
Platinum Partner

GraphicsBuilder Tutorial II: Outlines & Shapes

· Java Zone

The Java Zone is brought to you in partnership with ZeroTurnaround. Discover how you can skip the build and redeploy process by using JRebel by ZeroTurnaround.

Second part of the GraphicsBuilder tutorial series, this time outlines and the missing shapes from the previous part are shown.


On the first part of this series, basic and extra shapes were introduced, now let's continue with outlines and revisit the missing shapes: text and morph. There are 4 basic outlines you can choose from:

  • line: draws a straight line
  • polyline: draws a sequence of connected lines defined by arrays of x and y coordinates.
  • cubicCurve: draws a cubic parametric curve segment.
  • quadCurve: draws a quadratic parametric curve segment.

All outlines share similar properties as shapes, like borderColor, borderWidth and opacity, making the code easily interchangeable if needed be. Before we get into the proper examples you may have noticed that the pics on the first part show some jaggies in the corners of each shape, because we are going to display outlines the jaggies will make the pics look uglier. It is a good thing that Java2D supports antialiasing to get rid of those nasty jaggies and because setting antialiasing options is so common when it comes to drawings, GraphicsBuilder includes a handy shortcut for setting them, just use an antialias() node. The following pics will show what happends before and after applying antialiasing settings to each set of lines.

The Outlines

Let's start with the most basic one, straight lines. They require 2 anchor points and nothing more.


rect( x:0, y: 0, width: 310, height: 60, borderColor: 'none', fill: 'white' )
line( x1: 10, y1: 10, x2: 300, y2: 10, borderColor: 'black', borderWidth: 3 )
line( x1: 10, y1: 20, x2: 300, y2: 40, borderColor: 'red', borderWidth: 3 )
antialias on
line( x1: 10, y1: 40, x2: 300, y2: 20, borderColor: 'blue', borderWidth: 3 )
line( x1: 10, y1: 50, x2: 300, y2: 50, borderColor: 'black', borderWidth: 3 )

You will notice that antialias accepts on as a parameter, it is actually an alias for the boolean true, and yes in case you are wondering off is an alias for false, so are yes and no too. These aliases make your code more readable in some situations. The next outline is polyline, which draws a sequence of linear segments connected by each pair of points.


rect( x:0, y: 0, width: 200, height: 120, borderColor: 'none', fill: 'white' )
polyline( points: [30, 100, 80, 100, 70, 10, 50, 10, 50, 30, 60, 30],
borderColor: 'black', borderWidth: 3 )
antialias on
polyline( points: [120, 100, 170, 100, 160, 10, 140, 10, 140, 30, 150, 30],
borderColor: 'darkGreen', borderWidth: 3 )

No surprises here, though you may be wondering what kind of color is 'none', well it is actually black with full alpha settings, meaning it is a transparent color. You may also set false, off, no to skip drawing the border. Now to the curve segments, quadCurve perhaps being the easiest to configure as it it requires just 3 anchor points


rect( x:0, y: 0, width: 250, height: 120, borderColor: 'none', fill: 'white' )
quadCurve( x1: 20, y1: 20, ctrlx: 300, ctrly: 60, x2: 20, y2: 100,
borderColor: 'red', borderWidth: 4 )
antialias on
quadCurve( x1: 100, y1: 20, ctrlx: 380, ctrly: 60, x2: 100, y2: 100,
borderColor: 'blue', borderWidth: 4 )

The last outline is cubicCurve, it requires 4 anchor points


rect( x:0, y: 0, width: 310, height: 160, borderColor: 'none', fill: 'white' )
cubicCurve( x1: 10, y1: 10, ctrlx1: 10, ctrly1: 100, ctrlx2: 300, ctrly2: 10,
x2: 300, y2: 100, borderColor: 'blue', borderWidth: 2 )
antialias on
cubicCurve( x1: 10, y1: 50, ctrlx1: 10, ctrly1: 140, ctrlx2: 300, ctrly2: 50,
x2: 300, y2: 150, borderColor: 'blue', borderWidth: 2 )

Granted outlines look pretty dull compared to shapes, but once we visit custom strokes and paints they will look much better.

The Missing Shapes

Even though GraphicsBuilder packs many shapes, more than the basic ones that Java2D provides, not counting the ability to create your own shapes using area operations, there are two other shapes you may use: text and morph. Drawing text requires setting a font (unless you want to use the default settings), fonts require at least one of the following properties: face, size, style. Or you can set any java.awt.Font instance available. The following example shows 4 text shapes, each with a different value for it's font's style and size. Notice that the font definition is nested inside the text node, that way it will only be applied to that shape in particular. If for any reason you define a font outside a text node, then all subsequent text nodes will use that font.


def colors = ['red','orange','darkGreen','blue']
def sizes = [24,36,48,60]
def offsets = [48,80,48,60]
def styles = ['plain','bold','italic','bold|italic']
(0..3).each { n ->
text( text: 'GROOVY', x: 10, y: 0 +(sizes[n]*(n+1)/2),
fill: colors[n], borderColor: 'black' ){
font( size: sizes[n], style: styles[n] )

You can use any combination of the Font.* constants to set the style, or go with the literal option, using a pipe '|' char to separate each token. The second shape comes from the Swingx project, it morphs two shapes into a third one, allowing you to control how much of both shapes contribute to the final one with a morph property; the more the value is close to 0 it will look to the first shape, conversely the more that value is close to 1 it will look like the second shape.


So we have two shapes, a rectangle and start posing as the first and second shapes, then we have a third shape that resembles the rectangle and a fourth one that resembles the star. Let's see the code

def rect = rect( x: 20, y: 50, width: 100, height: 100, arcWidth: 20, 
arcHeight: 20, asShape: true )
def star = star( cx: 100, cy: 100, ir: 50, or: 80, count: 5, asShape: true )

morph( start: rect, end: star, morph: 0.0, borderWidth: 3,
borderColor: 'orange', fill: 'yellow' ){
transformations { translate(x: 0, y: 0) }
morph( start: rect, end: star, morph: 1.0, borderWidth: 3,
borderColor: 'blue', fill: 'cyan' ){
transformations { translate(x: 140, y: 0) }
morph( start: rect, end: star, morph: 0.3, borderWidth: 3,
borderColor: 'orange', fill: 'yellow' ){
transformations { translate(x: 20, y: 150) }
morph( start: rect, end: star, morph: 0.7, borderWidth: 3,
borderColor: 'blue', fill: 'cyan' ){
transformations { translate(x: 140, y: 150) }


What is going here? we have clearly 6 shape definitions but only 4 are being drawn. It turns out that shapes (and outlines too) share a property named asShape, when set to true (or on or yes) it will tell GraphicsBuilder not to draw the shape but create the operation only, that way you will be able to reuse the shape or outline definition as many times as you want and in as many places as you need. The code clearly shows that setting the morph value to 0 will draw the first shape unchanged, the same thing happens to the second shape if set to 1. You will notice that all 4 shapes have a transformation/translate node applied, we will look further into transformations at a later installment of this series, but suffices to say that translate moves a shape around to the coordinates you specify.


Now that you now shapes can be stored to be later reused we can put them to work. A recurrent question arose in the comments of the last part of this series: do shapes honor the current clip? the answer is yes they do. The default clip on a GraphicsPanel, the component GraphicsPad uses to display your drawings, is set to the panel's boundaries, but you can set a custom clip if you want, just reuse a shape definition with a clip node.

antialias yes
ellipse( cx: 100, cy: 75, radiusx: 90, radiusy: 40, asShape: yes, id: 'c' )
clip( c )
rect( x:10, y:10, width: 200, height: 120, arcWidth: 40,
arcHeight: 20, arcWidth: 20, borderWidth: 2,
borderColor: 'darkGreen', fill: 'green' )
circle( cx:75, cy:75, radius: 50, borderWidth: 2,
borderColor: 'darkRed', fill: 'red' )
star( cx:150, cy:75, or: 50, ir: 30, borderWidth: 2,
borderColor: 'orange', fill: 'yellow' )


Now you now how to draw shapes and outlines, applying antialiasing options to make your drawings smoother and even reusing shapes and setting custom clips. The next part of this series will show you how to control the color options of borders and fills, and the more versatile and subtle paints and gradients.

The Java Zone is brought to you in partnership with ZeroTurnaround. Discover how you can skip the build and redeploy process by using JRebel by ZeroTurnaround.


{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}