Fun with Java2D - Java Reflections

You've all seen it - it's everywhere. No web graphic is complete without a reflection in a shiny surface below. This article show how to achieve the reflection effect using Java2D.

License

The source code on this page is in the public domain. Basically, this means that you are free to do whatever you like with this code, including commercial use, but it's not my fault if your satellite/nuclear power station/missile system fails as a result.

Reflection

Here's the effect we're talking about. The photo is drawn over a black-to-gray background with a reflection, making it look as if the photo is suspended above a shiny surface.

Reflected Image
A reflected image

It's easy to draw a simple reflection. We just take the original image and draw it upside down by scaling it by -1 in the Y direction. The tricky bit is getting the reflection to fade out as it goes down the screen. To do this we need to do some fancy tricks with AlphaComposite.

Firstly, let's just draw the reflection without the fading and work from there. We'll assume we have a BufferedImage called image and a Graphics2D called g2d as well as a parameter gap which gives the gap between the image and its reflection. We can then draw the image and its reflection like this:

    int imageWidth = image.getWidth();
    int imageHeight = image.getHeight();

    g2d.translate( x, y );
    g2d.drawRenderedImage( image, null );
    g2d.translate( 0, 2*imageHeight+gap );
    g2d.scale( 1, -1 );
    g2d.drawRenderedImage( image, null );

Now to add the fading effect. What we want to do is draw the reflection with an opacity which fades from, say, one half to zero as we go down the screen. We'll add two parameters: opacity, the opacity at the top of the reflection and fadeHeight, the distance down the reflection at which the reflection fades altogether.

How do we make an image fade down its height? We could paint a transparent black gradient over the top, but that would not make the image transparent. What we need is some way to paint transparency rather than a color. It so happens that Java2D has a method to do this. The DST_IN variant of AlphaComposite does exactly what we need. What it does is apply the transparency of the source to the destination, so all we need to do is to draw a transparent gradient over the top of the image using the DST_IN composite. However, this won't work when we're drawing directly to our original Graphics object because it would make the background transparent as well, so we need to create an intermediate image to create the transparent reflection and then draw that image using our original Graphics.

Here's how to create the reflection image:

    BufferedImage reflection = new BufferedImage( imageWidth, imageHeight, BufferedImage.TYPE_INT_ARGB );
    Graphics2D rg = reflection.createGraphics();
    rg.drawRenderedImage( image, null );
    rg.setComposite( AlphaComposite.getInstance( AlphaComposite.DST_IN ) );
    rg.setPaint( new GradientPaint( 0, imageHeight*fadeHeight, new Color( 0.0f, 0.0f, 0.0f, 0.0f ), 0, imageHeight, new Color( 0.0f, 0.0f, 0.0f, opacity ) ) );
    rg.fillRect( 0, 0, imageWidth, imageHeight );
    rg.dispose();

I've presented this as how to reflect an image, but, of course, there's no reason to restrict yourself to images. You can apply this technique to any kind of Java2D drawing. The only hard part is working out the size you need for your intermediate image.

The Full Project

Finally, here's the source code for a complete application which demonstrates the technique. You'll have to supply your own image to reflect.

import java.awt.*;
import java.awt.image.*;
import java.io.*;
import javax.imageio.*;
import javax.swing.*;

public class Reflect extends JComponent {

    private BufferedImage image;

    public Reflect() {
        try {
            image = ImageIO.read( new File( "/Users/jerry/Documents/IMG_0016.jpg" ) );
        }
        catch ( Exception e ) {
            e.printStackTrace();
        }
    }
    
    public void paintComponent( Graphics g ) {
        Graphics2D g2d = (Graphics2D)g;
        int width = getWidth();
        int height = getHeight();
        int imageWidth = image.getWidth();
        int imageHeight = image.getHeight();
        int gap = 20;
        float opacity = 0.4f;
        float fadeHeight = 0.3f;

        g2d.setPaint( new GradientPaint( 0, 0, Color.black, 0, height, Color.darkGray ) );
        g2d.fillRect( 0, 0, width, height );
        g2d.translate( (width-imageWidth)/2, height/2-imageHeight );
        g2d.drawRenderedImage( image, null );
        g2d.translate( 0, 2*imageHeight+gap );
        g2d.scale( 1, -1 );

        BufferedImage reflection = new BufferedImage( imageWidth, imageHeight, BufferedImage.TYPE_INT_ARGB );
		Graphics2D rg = reflection.createGraphics();
        rg.drawRenderedImage( image, null );
		rg.setComposite( AlphaComposite.getInstance( AlphaComposite.DST_IN ) );
        rg.setPaint( 
            new GradientPaint( 
                0, imageHeight*fadeHeight, new Color( 0.0f, 0.0f, 0.0f, 0.0f ),
                0, imageHeight, new Color( 0.0f, 0.0f, 0.0f, opacity )
            )
        );
        rg.fillRect( 0, 0, imageWidth, imageHeight );
        rg.dispose();
        g2d.drawRenderedImage( reflection, null );
    }
    
    public Dimension getPreferredSize() {
        return new Dimension( 500, 500 );
    }
    
    public static void main (String args[]) {
        JFrame f = new JFrame();
        Reflect r = new Reflect();
        f.getContentPane().add( r );
        f.pack();
        f.show();
    }
}