Skip to main content

Java2D Gradients Performance

Posted by gfx on September 26, 2006 at 5:07 PM PDT

I feel weird. My hands are sweaty and my throat is sore. My heart's beating too fast... What's happening to me?! Wait, I know. I haven't blogged here for the past 7 months! Dammit. Sorry for that folks.

Back to gradients. I use Java2D gradients a lot. I mean, A LOT. Most of the time, gradients work perfectly fine and performance are more than enough, especially when you honor the clipping rectangle. In some rare occasions, a gradients does take quite some time to be painted. This happens when you are painting large areas with a gradients as in the following screenshot:

Aerith

In this picture, the background gradient is about 700x500 pixels large. That's a lot of pixels. Since we were drawing full frame animations (60fps) on top of this gradient, the JVM wa a tad CPU hungry. We optimized this by painting the gradient in a buffer image that we use instead every time a repaint event occurs. Problem solved.

Or is it? Using a large picture was fine in this case because it was after all just a demo. But is it worth wasting 1,5MB or so of RAM? I think not. There's a very nifty trick you can use to optimize the drawing of large vertical and horizontal gradients: image resizing.

Instead of painting the gradient in a picture large enough to cover the area you want to repaint, paint it in a 1 pixel wide picture for vertical gradients or in a 1 pixel high picture for horizontal gradients. Then, at runtime, paint the picture with drawImage(image, x, y, w, h, null). By passing the size of the component to this method call, you will get the result you wanted quickly and without wasting memory.

Here is an example of how you can use this trick:

    @Override
    protected void instrumentedPaintComponent(Graphics2D g2) {
        if (cache == null || cache.getHeight() != getHeight()) {
            cache = new BufferedImage(2, getHeight(),
                BufferedImage.TYPE_INT_RGB);
            Graphics2D g2d = cache.createGraphics();
           
            GradientPaint paint = new GradientPaint(0, 0, Color.BLACK,
                0, getHeight(), Color.WHITE);
            g2d.setPaint(paint);
            g2d.fillRect(0, 0, 2, getHeight());
            g2d.dispose();
        }
        g2.drawImage(cache, 0, 0, getWidth(), getHeight(), null);
    }

But how does this trick compare to the two previous techniques? Well, here I have made surprising discoveries. I wrote a simple app to test the speed difference between the 3 approaches on Windows and Mac OS X. I first ran the tests on Windows at two different sizes (small gradients, about 200x200 pixels, versus large gradients, about 300x800) with various command line flags to change the rendering pipeline. I then ran the tests on Mac OS X at two different sizes. All tests were using Java SE 6 b88. On the following diagrams, smaller numbers (even negatives due to the logarithmic scales) are better.

Windows Performance (Small)

Windows Performance (Large)

Mac OS X Performance

From these diagrams, two things are for sure on Windows: images are faster than regular gradients and the new OpenGL pipeline in Mustang is freakingly good. On Mac OS X though, all techniques are roughly the same. The main advantages of pictures over regular gradients is that whatever rendering pipeline you use, the results are almost the same on Windows.

Even though using 1 pixel wide/tall pictures is a bit slower than full size pictures, this technique is the best in terms of speed and memory usage. At least for horizontal and vertical gradients.

P.S: By the way, this is only a benchmark that continuously paints gradients using all three techniques for a given number of seconds and finally spits out the time spent in each implementation. It might not (and surely does not) reflect real world use at all :)

Related Topics >>