Create glass effect buttons using nothing but Java code and JTexGen, a procedural texture library for Java. First create a new project in your favorite IDE and add the JTextGen jar file, or create a new maven project and add the JTexgen dependency if you installed it from the source distribution into your local repository.

Our glass button will be round-ish, and will have a margin at the edge where we will render the shadow. We’ll start by writing our main method and creating the glass button class and rendering it. First create a new class called GlassButton.java and make it extend AbstractTexture. We’ll add a constructor that takes the base color of the button and returns it in the getColor method.

public class GlassButton extends AbstractTexture {

	private final RGBAColor color;
	
	public GlassButton(RGBAColor color) {
		this.color = new RGBAColor(color);
	}
	
	public void getColor(double u, double v, RGBAColor value) {
		value.setColor(color);
	}	
}

Next we’ll create our main class and add some code to create and render our texture.

public class Main {
	
	public static void main(String[] args) {
		RGBAColor color = RGBAColor.red();
		Texture texture = new Background(new GlassButton(color),RGBAColor.white());
		TextureViewer.show(texture);		
	}
}

Ok, a fairly simple start, we create our texture with a red color over a white background and pass it to the viewer. If you save and run this, you should get a window with a start button, which will render the red texture when clicked. Let’s add a method to our texture to calculate where on the button the textures u,v co-ordinates are. We do this by calculating the distance from the center of the texture the U,V co-ordinates are. The value returned will range from 0..1 inside the circle and outside the circle, it will return 1+ and grow larger the further it gets from the center. This lets us determine what should appear for any given point on the texture. Since we want a border around the edge of the button, we will have to scale the u,v values so when the result ramps up to 1 before getting to the edge of the texture. Add the following method to the GlassButton class.

protected double calculateCircleRange(double u,double v) {
	//displace point
	u = u - 0.5;
	v = v - 0.5;
	//calculate distance
	double distance = Math.sqrt((u*u)+(v*v));
	//double it since the range will only be from 0 to 0.5
	return distance * 2;
}

We can verify this works by changing our get value method to return this value in the alpha channel.

public void getColor(double u, double v, RGBAColor value) {
	double range = calculateCircleRange(u, v) * 1.2;
	value.setColor(color);
	value.setAlpha(1-range);
}


We inverted the signal (1-range) so the color would be stronger at the center and weaker at the edge. Since the range goes beyond 1, then the value is truncated to the range 0..1 which is why the pattern repeats at the edge of the circle.

What we need to do is calculate the color based on the return value of this function. Values greater than 1 will result in rendering the shadow with a strength inversely proportional to the distance from the edge. For 0 to 1 values, we will render the button color and overlay the highlight onto it. We will also add an inner shadow in the range 0.7 to 1 with a strength based on the distance from the edge. The button color will consists of a base color which slowly transforms into a darker version of that base color the further down the button we go. The highlight is added as a lighter version of the base color with the alpha channel decreasing the further down the button we go. This has the effect of fading out the highlight the further down we go. Let’s flesh out a simple version to indicate where each piece is going.

public void getColor(double u, double v, RGBAColor value) {
	double range = calculateCircleRange(u, v) * 1.2;
	
	if (range > 1) {
		//render shadow
		value.setColor(RGBAColor.black());
		return;
	}		
		
	value.setColor(color);
		
	if (range > 0.7) {
		value.setColor(RGBAColor.yellow());
	}				
}


This gives us a fairly predictable result, you can see where the button ends and the shadow starts and you can see which parts the inner shadow affects. We multiply the range value by 1.2 so we can shrink the circle down a bit to give us our margin around the edge for the shadow to sit in.

Time to add some actual colors to our template starting with the shadow. Our shadow is essentially the color black with the alpha ranging from 0.9 to 0 based on how far the point is from the edge of the button. We make the shadow fall off exponentially so the shadow becomes much lighter faster. Bear in mind that the range value for the shadow area is 1+ so we need to adjust the value into the 0..1 range.

public void getColor(double u, double v, RGBAColor value) {
	double range = calculateCircleRange(u, v) * 1.2;
		
	if (range > 1) {
		//render shadow
		value.setColor(RGBAColor.black());
		double shadow = Math.pow(2-range-0.1, 8);
		value.setAlpha( shadow);
		return;
	}		
		
	value.setColor(color);
		
	if (range > 0.8) {
		value.setColor(RGBAColor.yellow());
	}				
}


We specified a white background in our main class and the shadow has a varying alpha value so the white background show through. We can also change the background to see how it looks on different colors.

Now would be a good time to set up the different colors we’ll be using in our button. We have our base color passed in to the constructor, and from that, we can calculate the darker button color and the lighter highlight color. Let’s add those in to the constructor first.

public class GlassButton extends AbstractTexture {

	private final RGBAColor color;
	private final RGBAColor darkerColor;
	private final RGBAColor highlightColor;
	
	public GlassButton(RGBAColor color) {
		this.color = new RGBAColor(color);
		
		//make a copy and darken it
		this.darkerColor = new RGBAColor(color);
		this.darkerColor.merge(RGBAColor.black(),0.5);
		
		//make a copy and lighten it
		this.highlightColor = new RGBAColor(color);
		this.highlightColor.merge(RGBAColor.white(),0.8);
	}

With our colors in place, we can start adding them to the final button image. We’ll start with the main button color which ranges from the base color to dark color as the v value increases and we evaluate pixels further down the image. This can be done with one line of code since we take the base color and merge the darker color based on the v value after we assign the base color to the value.

value.setColor(color);
value.merge(darkerColor,(v-0.1)*1.2);


We shift and scale the v value to try and make the color transformation apply across the whole face of the button which is scaled down. This gives as a gentle transformation across the face of the button.

The button has an inner shadow which gives us the impression that the edges of the button are bending away from us. The code for this looks at the circle range value and if greater than 0.8 adds the inner shadow based on the distance from the edge. To calculate this, we subtract the 0.8 and scale it to get a value in the range of 0 to 0.4 which is perfect since we don’t want a strong shadow.

		//add the inner shadow
		if (range > 0.8) {
			double shadow = (range-0.8)*2;			
			value.merge(RGBAColor.black(),shadow);
		}


The last piece we need to add is the highlight which is rendered in the shape of an smaller circle that is offset upwards slightly. This means we can reuse our calculateCircleRange method to determine which points are in the smaller circle. Once we have determined whether we are in the highlight circle or not we will merge the highlight color based on the v value so the highlight fades out as we move down the button.

Here is the complete getColor method with the code to add the highlight at the end.

public void getColor(double u, double v, RGBAColor value) {
	double range = calculateCircleRange(u, v) * 1.2;

	if (range > 1) {
		// render shadow
		value.setColor(RGBAColor.black());
		double shadow = Math.pow(2 - range - 0.1, 8);
		value.setAlpha(shadow);
		return;
	}

	// set the base color
	value.setColor(color);

	// add the darker color transform
	value.merge(darkerColor, (v - 0.1) * 1.2);

	// add the inner shadow
	if (range > 0.8) {
		double shadow = (range - 0.8) * 2;
		value.merge(RGBAColor.black(), shadow);
	}

	// finally add the highlight
	double highlightRadius = calculateCircleRange(u, v + 0.05) * 1.5;
	if (highlightRadius < 1) {
		double highlight = v * 2;
		value.merge(highlightColor, Gradient.clip(1 - highlight));
	}
}


This is our final button image which is a round red glass button. However, because this is a procedural texture, we can scale the image in both the u and v directions without loss of quality and we can also create oval buttons. Below you can see a number of different variations.

I’ve also added an enhancement with regards to the inner shadow. Rather than making it a fixed black color, I have used a ColorGradient so the inner shadow transforms from dark to light from the top to the bottom of the button. We did this by adding this private member

private final ColorGradient shadowColor = ColorGradient.buildBlackAndWhite();

and in our inner shadow code, we use this gradient color instead of the fixed black color :

	// add the inner shadow
	if (range > 0.8) {
		double shadow = (range - 0.8) * 2.5;			
		value.merge(shadowColor.interpolate((v+0.3)), shadow);
	}

Also, for the last image, instead of using a solid color, I used a ColorGradient from the highlight color to the base color and then the darker color to give it that radial gradient. I plan on writing a generalized version of this texture for inclusion in the next release that will let you set parameters and even pass Texture objects for the main color and the inner shadow.