How to eliminate java animation flickering

Simon Andrews picture Simon Andrews · Mar 25, 2012 · Viewed 9k times · Source

I'm writing a java application which needs to scroll a waveform smoothly over the screen. I've spend ages going through various tutorials to figure out how to make this animation as smooth as possible. As far as I can see I've done all the normal things to eliminate flickering (drawing to offscreen buffer and rendering in a single step, plus overriding update so the screen isn't blanked), but my animation still flickers, and the screen looks like it's being blanked before each update.

I'm sure there's something fundamental (and probably simple) I'm missing, but I'm out of ideas. I'll post a class below which illustrates the problem. Any help would be much appreciated.

import java.awt.*;
import javax.swing.*;

public class FlickerPanel extends JPanel implements Runnable {

    private float [] pixelMap = new float[0];

    /** Cached graphics objects so we can control our animation to reduce flicker **/
    private Image screenBuffer;
    private Graphics bufferGraphics;

    public FlickerPanel () {
        Thread t = new Thread(this);
        t.start();
    }

    private float addNoise () {
        return (float)((Math.random()*2)-1);
    }

    private synchronized void advance () {
        if (pixelMap == null || pixelMap.length == 0) return;
        float [] newPixelMap = new float[pixelMap.length];
        for (int i=1;i<pixelMap.length;i++) {
            newPixelMap[i-1] = pixelMap[i];
        }

        newPixelMap[newPixelMap.length-1] = addNoise();     

        pixelMap = newPixelMap;
    }

    public void run() {
        while (true) {
            advance();
            repaint();

            try {
                Thread.sleep(25);
            } catch (InterruptedException e) {}

        }
    }

    private int getY (float height) {
        double proportion = (1-height)/2;
        return (int)(getHeight()*proportion);
    }

    public void paint (Graphics g) {

        if (screenBuffer == null || screenBuffer.getWidth(this) != getWidth() || screenBuffer.getHeight(this) != getHeight()) {
            screenBuffer = createImage(getWidth(), getHeight());
            bufferGraphics = screenBuffer.getGraphics();
        }

        if (pixelMap == null || getWidth() != pixelMap.length) {
            pixelMap = new float[getWidth()];
        }

        bufferGraphics.setColor(Color.BLACK);

        bufferGraphics.fillRect(0, 0, getWidth(), getHeight());

        bufferGraphics.setColor(Color.GREEN);

        int lastX = 0;
        int lastY = getHeight()/2;

        for (int x=0;x<pixelMap.length;x++) {
            int y = getY(pixelMap[x]);
            bufferGraphics.drawLine(lastX, lastY, x, y);
            lastX = x;
            lastY = y;
        }

        g.drawImage(screenBuffer, 0, 0, this);
    }

    public void update (Graphics g) {
        paint(g);
    }

    public static void main (String [] args) {
        JFrame frame = new JFrame("Flicker test");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setContentPane(new FlickerPanel());
        frame.setSize(500,300);
        frame.setVisible(true);
    }


}

Answer

Andrew Thompson picture Andrew Thompson · Mar 25, 2012
  1. In a JPanel, override paintComponent(Graphics) rather than paint(Graphics)
  2. Instead of calling Thread.sleep(n) implement a Swing Timer for repeating tasks or a SwingWorker for long running tasks. See Concurrency in Swing for more details.