Home > Swing > Paint faster!

Paint faster!

It’s been a while since I came across this stuff and I already wrote about it on a company blog in the German language, but I thought this could be interesting enough for more people out there, so I’m sitting here translating what I wrote almost 2 years ago, keen on validating it with today’s Java Runtime. So what’s this fuzz about? Simply put: I noticed that you might have wasted a lot of time in swing, if you relied on the Graphics-object you get passed by Swing and the difference was dramatic!

It all started when I got a pixel-exact specification on how to draw some round borders, which I implemented and one of the next reports that we got said that our application’s resizing/repainting behavior showed undesirable lags. As I recently made a lot of changes in the way the look and feel paints the borders, I instantly felt guilty of it and luckily the report was also assigned to me, so I took my profiler to identify the slowest methods. The repainting of the borders took in fact most of the time when you resized the window, if not the most. It turned out that the borders were still painting fast enough. But I produced this little micro benchmark in my eager attempt to optimize that part of the code in order to see if a bit less load would make a difference (I suspected it could be flooding the EDT with lots of short repaints enough to cause that effect, so I thought reducing the time could change something – unfortunately it didn’t). And I got pretty surprised!

Here’s a little demo:

public class DrawingPixels extends JPanel {

  private int index = 0;
  private List<Color> colors;
  private long totalTimes;
  private long measures;

  public DrawingPixels() {
    final Dimension dim = new Dimension( 800, 800 );
    setMinimumSize( dim );
    setPreferredSize( dim );
    setMaximumSize( dim );
    setSize( dim );
    final Timer timer = new Timer();
    timer.scheduleAtFixedRate( new TimerTask() {
      @Override
      public void run() {
        repaint();
      }
    }, 1000, 1000 );

    colors = new ArrayList();
    for ( int t = 0; t < 256; t += 8 ) {
      colors.add( new Color( t, t, t ) );
    }
    for ( int t = 254; t > 0; t -= 8 ) {
      colors.add( new Color( t, t, t ) );
    }
  }

  @Override
  protected void paintComponent( final Graphics g ) {
    super.paintComponent( g );
    index = ( index + 1 ) % colors.size();
    System.out.println( index );
    final Color color = colors.get( index );
    final long start = System.currentTimeMillis();
    paintStuff( g, color );
    final long end = System.currentTimeMillis();
    final long elapsed = end - start;
    totalTimes += elapsed;
    ++measures;
    g.setColor( Color.WHITE );
    g.drawString( 
      String.valueOf( totalTimes / measures ), 100, 100 );
  }
  
  public static void main( String[] args ) {
    EventQueue.invokeLater( new Runnable() {
      public void run() {
        final JFrame f = new JFrame();
        f.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
        f.setContentPane( new DrawingPixels() );
        f.pack();
        f.setVisible( true );
      }
    } );
  }

  private void paintStuff( final Graphics g, final Color color ) {
    g.setColor( color );
    for ( int y = 0; y < getHeight(); ++y ) {
      for ( int x = 0; x < getWidth(); ++x ) {
        g.drawLine( x, y, x, y );
      }
    }
  }
}

You don’t have to understand and read most of the code. The important part is the last method “paintStuff” which does the drawing. It fills this 800×800 pixel panel one pixel at a time. My development-machine back then took a whopping 2300 milliseconds to do that, which was quite frankly insane.

The next try got me a quite interesting result:

  private void paintStuff( final Graphics g, final Color color ) {
    final BufferedImage image = 
      new BufferedImage( getWidth(), getHeight(), BufferedImage.TYPE_INT_ARGB );
    final Graphics imageGfx = image.getGraphics();
    imageGfx.setColor( color );
    for ( int y = 0; y < getHeight(); ++y ) {
      for ( int x = 0; x < getWidth(); ++x ) {
        imageGfx.drawLine( x,y,x,y);
      }
    }
    g.drawImage( image, 0, 0, this );
  }

This code just needed 590 ms, so painting into a buffered image and painting the buffered image onto the panel was faster than painting directly onto the panel and that took me by complete surprise!

Since a BufferedImage allows for setting the RGB-values of a pixel directly, I also tried that aswell and see if that would improve things:

  private void paintStuff( final Graphics g, final Color color ) {
    final BufferedImage image = 
      new BufferedImage( getWidth(), getHeight(), BufferedImage.TYPE_INT_ARGB );
    final Graphics imageGfx = image.getGraphics();
    for ( int y = 0; y < getHeight(); ++y ) {
      for ( int x = 0; x < getWidth(); ++x ) {
        image.setRGB( x, y, color.getRGB() );
      }
    }
    g.drawImage( image, 0, 0, this );
  }

This version did it in just 96 miliseconds – quite an improvement to the 2500 milliseconds the first version needed!

Improvements with JRE 1.6

Now I’m sitting at home at a machine which is not exactly as fast as my development machine these days, but this test is not so much about raw CPU-Power, is more about the graphics subsystems sending data and commands to the graphics adapter, so it is rather testing how well Swing implements its drawing-machanisms. Back then, when I timed it, we were running Java 1.5 and now I am repeating this test with version 1.6.0_18 of SUNs Java virtual machine.
These are the results:

Things got considerably faster with the 1.6 runtimes. The results finally look sane.


The runtime of the first test has improved from 2500 ms to 315 ms. The second test improved from 590 ms to 350 ms. The last test turned out to be equally fast: 97 ms.
The results I got from 1.6 virtual machine finally make sense:
Painting onto the screen with a line-drawing method that can do all the alpha-blending and transformation stuff the Graphics object provides is faster than doing the same thing on a buffered image fist. And just setting some RGB-Values on an image and painting that is faster, because it’s omitting all the fancy stuff – it just sets some values and tells the graphics adapter to display them.
Well, and one last thing: If anyone thinks that filling a canvas one pixel at a time is a stupid idea: He is right, because g.fillrect(0,0,getWidth(),getHeight()); does it in 2ms, which is well below the 10 ms tolerance of System.currentTimeMillis(), so hardly measurable. I’ll just feel I need to leave that figure here for the “Swing is slow”-zealots, before they start rambling about some hundreds of milliseconds for filling a screen – you can fill a screen faster than you’ll ever need it that is for sure, but the bottom line is:
With Java 6 Sun have really improved the graphics subsystem for the most common case, where you just override the paintComponent-method and paint your stuff directly. And the good thing is: you don’t need to change code, recompile and redeploy, it just comes for free.

Advertisements
Categories: Swing Tags: , , ,
  1. April 6, 2015 at 19:58

    Painting to the databuffer of the bufferedimage is faster than to set the rgb-value 😀

  1. No trackbacks yet.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: