Platinum Partner
groovy,java2d,graphics

GraphicsBuilder Tutorial III: Paints & Colors

Third part of the GraphicsBuilder tutorial series, now it is the turn to demonstrate how you can apply colors and paints (flat colors and gradients) to shapes and outlines, even borders, to your Java2D drawings.

Introduction 

In the first and second part of this series I have shown how Graphicsbuilder can help you draw shapes, outlines and apply an arbitrary clip to your Java2D drawings thanks to GraphicsBuilder. Now is the time to pick up speed and review the options GraphicsBuilder lends you to add some color to your drawings.

Flat Colors 

You may have noticed in the previous parts that two properties were used to give color to shapes and outlines, these are borderColor and fill. The first one (as the name implies) defines the color to be applied to the border (or the stroke as we will see in a later chapter), the second one defines the color that will fill the shape's area. The examples have shown that you may specify a color literal, but as a matter of fact you can use any java.awt.Color instance. The literals are a shortcut for the most common colors (java.awt.Color defines 13 basic colors only), but GraphicsBuilder provides additional color literals for the full range of CSS2 color set like 'aqua', 'bisque', 'indigo', steelBlue' among others.

The color node will take care of translating a color literal into the proper java.awt.color representation, relying on the ColorCache singleton. ColorCache is the one responsible for holding a map of color names and their values, you can use it to store new colors if you want to. Color properties that accept a color literal also use the ColorCache under the covers.

Enough theory, let's see some examples of color properties and the color node in action. The following image shows 5 rectangles, each with a black border and different color as fill, notice that thw white one is translucent.

[img_assist|nid=1344|title=|desc=|link=none|align=middle|width=100|height=100]

 

rect( x: 0, y: 0, width: 50, height: 50, borderColor: 'black', fill: 'red' )
rect( x: 50, y: 0, width: 50, height: 50, borderColor: color('black') ){
colorPaint( 'blue' )
}
rect( x: 0, y: 50, width: 50, height: 50, borderColor: color('#000000'), fill: color('#FF0') )
rect( x: 50, y: 50, width: 50, height: 50, borderColor: color(red:0) ){
colorPaint( green: 1 )
}
rect( x: 25, y: 25, width: 50, height: 50, borderColor: 'black' ){
colorPaint( color('white').derive(alpha:0.5) )
}

Let's examine each rectangle one by one. The red rectangle sets its color properties in the same way the previous examples did, nothing new here. The blue rectangle introduces the color node and the colorPaint node, both of them have the same properties, the difference is that colorPaint returns a PaintProvider, an internal class of GraphicsBuilder that deals with paints, whereas color (as you already know) returns a java.awt.Color. The yellow rectangle shows another feature of the color node, it can translate hexadecimal color literals, either in full or compact mode. The green rectangle shows another set of properties for the color node, it accepts a combination of [red,green,blue,alpha] values, ranging from 0f..1f and 1i..255i; unspecified properties will have a zero (0) assigned to their values, that is why the border's black color can be set with color(red:0). The last rectangle, the white translucent one, shows that colorPaint accepts the output of color, and that java.awt.color has been enhanced (through Groovy MetaClasses) with a new method derive, which will let you create a new color from a base one; this method accepts a combination of [red,green,blue,alpha] properties.

Two-color Gradients 

Flat colors are ok but everybody knows that gradients are better, they add depth and warm to any drawing, so let's see what GraphicsBuilder has to offer. Since the early days of Java2D developers had the option to draw gradients using java.awt.GradientPaint, which will let you draw a two-color linear gradient, there is really not much mystery about it, which is why the gradientPaint node is pretty straight forward, as the following example shows

[img_assist|nid=1345|title=|desc=|link=none|align=middle|width=200|height=100]

 

rect( x: 0, y: 0, width: 100, height: 50, borderColor: 'black'  ){
gradientPaint( x1: 0, y1: 0, x2: 100, y2: 50, color2: 'orange' )
}
rect( x: 100, y: 0, width: 100, height: 50, borderColor: 'black' ){
gradientPaint( x1: 0, y1: 0, x2: 50, y2: 30, color2: 'orange' )
}
rect( x: 0, y: 50, width: 100, height: 50, borderColor: 'black' ){
gradientPaint( x1: 0, y1: 0, x2: 50, y2:30, color2: 'orange', stretch: yes )
}
rect( x: 100, y: 50, width: 100, height: 50, borderColor: 'black' ){
gradientPaint( x1: 0, y1: 0, x2: 50, y2: 30, color2: 'orange', fit: no )
}

GradientPaint requires a line and two colors, it has some sensible defaults (like color1 being 'black'). you will notice that the top rectangles have the same paint even though the line definition for each one is different, this is because gradientPaint will try to fit the gradient in the shape's bounds by default, retaining the aspect ratio. The left bottom rectangle shows another property, stretch, that will also try to fit the gradient in the shape's bounds, but it doesn't retain the aspect ratio. The last rectangle shows how the gradient is drawn when fit is disabled. It is worth noting that stretch and fit are mutually exclusive, if you enable one the other will be disabled automatically.

Linear Gradients

GradientPaint has the limitation of being able to process only two colors, but since jdk6 Java2D has the option of drawing multicolored linear gradients, thanks to LinearGradient. The linearGradient node shares all properties of gradientPaint but requires a nested set of stop nodes that define each color and their position (offset) relative to the gradient's line distance. Let's revisit the gradientPaint example but using linearGradient instead, changing the black/orange set of colors to red/orange/darkGreen/blue. This example will also show how stretch and fit affect the gradient.

[img_assist|nid=1347|title=|desc=|link=none|align=middle|width=200|height=100]

 

rect( x: 0, y: 0, width: 100, height: 50, borderColor: 'black'  ){
linearGradient( x1: 0, y1: 0, x2: 100, y2: 50 ){
stop( offset: 0, color: 'red' )
stop( offset: 0.3, color: 'orange' )
stop( offset: 0.6, color: 'darkGreen' )
stop( offset: 1, color: 'blue' )
}
}
rect( x: 100, y: 0, width: 100, height: 50, borderColor: 'black' ){
linearGradient( x1: 0, y1: 0, x2: 50, y2: 30 ){
stop( offset: 0, color: 'red' )
stop( offset: 0.3, color: 'orange' )
stop( offset: 0.6, color: 'darkGreen' )
stop( offset: 1, color: 'blue' )
}
}
rect( x: 0, y: 50, width: 100, height: 50, borderColor: 'black' ){
linearGradient( x1: 0, y1: 0, x2: 50, y2: 30, stretch: yes ){
stop( offset: 0, color: 'red' )
stop( offset: 0.3, color: 'orange' )
stop( offset: 0.6, color: 'darkGreen' )
stop( offset: 1, color: 'blue' )
}
}
rect( x: 100, y: 50, width: 100, height: 50, borderColor: 'black' ){
linearGradient( x1: 0, y1: 0, x2: 50, y2: 30, fit: no ){
stop( offset: 0, color: 'red' )
stop( offset: 0.3, color: 'orange' )
stop( offset: 0.6, color: 'darkGreen' )
stop( offset: 1, color: 'blue' )
}
}

Stops accept a color property (and opacity), but they also accept [red,green,blue,alpha] as the color and colorPaint nodes do. You may define stops in the order you choose and GraphicsBuilder will sort them in the correct order, of course the preferred way is to define them in proper offset order.

Radial Gradients

 Jdk6 also delivers support for radial gradients, not just linear ones. Radial gradients require a set of stops to define their colors much as linear gradients do too, but that is where the similarities stop, as radial gradients require (at least) a center (cx,cy) and a radius, you may specify an optional focus (fx,fy) to further customize how the gradient looks, by default the focus will be equal to the center.

[img_assist|nid=1348|title=|desc=|link=none|align=middle|width=200|height=200]

radialGradient( cx: 50, cy: 50, radius: 75, id: 'g', asPaint: yes ){
stop( offset: 0, color: 'red' )
stop( offset: 0.5, color: 'orange' )
stop( offset: 1, color: 'black' )
}
rect( x: 0, y: 0, width: 100, height: 100, borderColor: 'black' ){
paint( g )
}
rect( x: 100, y: 0, width: 100, height: 100, borderColor: 'black' ){
paint( g )
}
rect( x: 0, y: 100, width: 100, height: 100, borderColor: 'black' ){
paint( g )
}
rect( x: 100, y: 100, width: 100, height: 100, borderColor: 'black' ){
paint( g )
}
rect( x: 50, y: 50, width: 100, height: 100, borderColor: 'black' ){
paint( g, absolute: yes )
}

As the image shows we have 5 squares with a similar radial gradient applied to them. The center square display its gradient in a different way, because the gradient is anchored in absolute coordinates rather than relative. All gradients will always be translated to relative coordinates in order to fit the shape's bounds unless told otherwise, this is also the case on linear gradients (fit=false). Looking back at the linearGradient example you may notice that the code is a bit verbose, as the gradient definition is repeated over and over again,shouldn't be a better way to reuse gradients or paints for that matter ? Well there is one, and it is shown in this example, there is a paint node that serves as a placeholder for any saved paint. As it is the case with shapes and their asShape property, all paints share a asPaint property that prevents the paint to be applied right away, just remember to assign an id to that paint in order to be able to reference it later. The paint node not also wraps an existing paint but will also let you modify the wrapped paint (it actually clones its paint the first time), that is why the fifth gradient can be set as absolute without altering the previous ones.

Linear and Radial gradients can share their stops definitions, as they are essentially the same, and if needed be, you can add more stops at any time. You can enable this feature by setting a linkTo property on the referencing gradient. let's see an example of this feature.

[img_assist|nid=1349|title=|desc=|link=none|align=middle|width=200|height=200]

 

rect( x: 0, y: 0, width: 100, height: 100, borderColor: 'black'  ){
linearGradient( id: 'linear' ){
stop( offset: 0, color: 'red' )
stop( offset: 0.5, color: 'green' )
stop( offset: 1, color: 'black' )
}
}
rect( x: 100, y: 0, width: 100, height: 100, borderColor: 'black' ){
radialGradient( cx: 50, cy: 50, radius: 75, linkTo: linear )
}
rect( x: 0, y: 100, width: 100, height: 100, borderColor: 'black' ){
linearGradient( y2: 50, linkTo: linear ){
stop( offset: 0.5, color: 'blue' )
}
}
rect( x: 100, y: 100, width: 100, height: 100, borderColor: 'black' ){
radialGradient( cx: 50, cy: 50, radius: 75, linkTo: linear ){
stop( offset: 0.25, color: 'orange' )
stop( offset: 0.75, color: 'blue' )
}
}

We define a linearGradient first and let the other three gradients refer to it while redefining one of the stops and adding new ones.

Border Paints

Colors can be applied to borders but paints can be too, as a matter of fact you can assign any paint you want to, just make sure you nest them in a borderPaint node. the following example shows as series of rectangles with a borderWidth > 1 with a flat color paint and gradient paints

[img_assist|nid=1350|title=|desc=|link=none|align=middle|width=220|height=220]

rect( x: 0, y: 0, width: 220, height: 220, fill: 'white' )
rect( x: 20, y: 20, width: 180, height: 180, borderWidth: 10 ){
borderPaint {
colorPaint( 'blue' )
}
}
rect( x: 40, y: 40, width: 140, height: 140, borderWidth: 10 ){
borderPaint {
gradientPaint( color2: 'blue' )
}
}
rect( x: 60, y: 60, width: 100, height: 100, borderWidth: 10 ){
borderPaint {
linearGradient( x1: 50, x2: 0, id: 'g' ) {
stop( offset: 0, color: 'red' )
stop( offset: 0.33, color: 'orange' )
stop( offset: 0.66, color: 'darkGreen' )
stop( offset: 1, color: 'blue' )
}
}
}
rect( x: 85, y: 85, width: 50, height: 50, borderWidth: 20 ){
borderPaint {
radialGradient( cx: 25, cy: 25, radius: 50, linkTo: g )
}
}

You should be able to apply any of the previous paint settings to borders.

Multipaints

The last painting option is rather unique to GraphicsBuilder, it allows you to apply a group of paints to the same shape, each one applied one after the other. The following example is taken from the Filthy Rich Clients book (highly recommended), you can see a similar picture in the chapter where Romain introduces gradients.

[img_assist|nid=1351|title=|desc=|link=none|align=middle|width=220|height=220]

What you see is not what you think it is, there is no sphere but rather a circle with a series of gradients that yield the illusion of a sphere. The multipaint is composed of a base color, an ambient gradient that gives the sensation of depth (the spherical context), an specular gradient (bottom) and the highlight (or shine) at the top. If you think making this drawing with Java2D is complicated you are probably right unless you rely on GraphicsBuilder to ease up the process

// some useful variables
def width = 200
def height = 200
def cx = width/2 + 10
def cy = height/2 + 10
// turn on antialias
antialias on
// paint the background
rect( x: 0, y: 0, width: 220, height: 220, fill: 'white' )
// this is the base shape and color
circle( cx: cx, cy: cx, radius: width/2, borderColor: no, fill: 'blue' )
// sphere-like gradients
circle( cx: cx, cy: cx, radius: width/2, borderColor: no ){
multiPaint {
// ambient
radialGradient( cx: cx, cy: cy, radius: width/2 ) {
stop( offset: 0, color: color(red: 6, green: 76, blue: 160, alpha: 127) )
stop( offset: 1, color: color(alpha: 204) )
}
// specular
def lighting = color(red: 64, green: 142, blue: 203, alpha: 255)
radialGradient( cx: cx, cy: height*1.5,
fx: cx, fy: (height*1.75)+16,
radius: width/2, absolute: yes ) {
stop( offset: 0, color: lighting )
stop( offset: 0.8, color: lighting.derive(alpha:0) )
transformations{ scale(y:0.5) }
}
// shine
radialGradient( cx: cx, cy: cy,
fx: 55, fy: 35, radius: width/1.4 ){
stop( offset: 0, color: color('white').derive(alpha:0.5) )
stop( offset: 0.5, color: color('white').derive(alpha:0) )
}
}
}

Conclusion

There is another paint option we have not seen yet, texturePaint, as it requires knowledge of how images work in GraphicsBuilder, we will revisit that in a later chapter of this series. For now you already know how to change the colors of a shape, outline and even borders, which hopefully will lead you to hours of playful hacking with graphicsPad.

 

{{ tag }}, {{tag}},

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

{{ parent.tldr }}

{{ parent.urlSource.name }}
{{ parent.authors[0].realName || parent.author}}

{{ parent.authors[0].tagline || parent.tagline }}

{{ parent.views }} ViewsClicks
Tweet

{{parent.nComments}}