8

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);
    }


}
mKorbel
  • 109,525
  • 20
  • 134
  • 319
Simon Andrews
  • 317
  • 1
  • 5
  • 10
  • I can't find any issue with your code. Looks pretty smooth on my desktop. ps. You can do this a lot more efficiently by only having one pixelMap and using it as a cyclic buffer, and protecting access to it with a synchronised block. As it stands creating a new structure each update should be OK. – Adam Mar 25 '12 at 09:56
  • +1 for [sscce](http://sscce.org/). It shimmers pretty badly on my platform. – trashgod Mar 25 '12 at 10:00
  • Having played a bit more it seems that some of the problem may be due to the pixelMap datastructure being modified during the course of a paint, so I'm getting tearing between different parts of the screen. Doing a clone() of the pixelMap at the start of the paint method seems to improve things, but the flickering is still there so I don't think this is the whole answer. – Simon Andrews Mar 25 '12 at 10:07

2 Answers2

8
  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.
Andrew Thompson
  • 168,117
  • 40
  • 217
  • 433
  • 1
    Yes; even though the `sleep()` occurs on a different thread and `repaint()` is thread-safe, `advance()` would have to be synchronized. – trashgod Mar 25 '12 at 10:03
  • OK, that's a good point, but I don't think it has any direct effect on the flickering - it appears the same whichever method I override. – Simon Andrews Mar 25 '12 at 10:08
  • 1
    @trashgod I decided to remove the 1st sentence of point 2 *"Don't block the EDT (Event Dispatch Thread) - the GUI will 'freeze' when that happens."* Since while true, it does not seem to apply here. – Andrew Thompson Mar 25 '12 at 10:09
  • Advance is synchronized, but this may still be part of the problem since pixelMap can end up being replaced in the middle of a paint, which causes shearing. – Simon Andrews Mar 25 '12 at 10:09
7

JPanel is double buffered by default, and "Swing programs should override paintComponent() instead of overriding paint()."—Painting in AWT and Swing: The Paint Methods. Contrast this example that overrides paintComponent() with this example that overrides paint().

Addendum: The shearing is caused by creating and copying an entire image on each update. Instead, accumulate points on a GeneralPath and draw() the Shape on each iteration, as shown here.

Community
  • 1
  • 1
trashgod
  • 203,806
  • 29
  • 246
  • 1,045
  • I've tried overriding paintComponent and also setting the panel to not be double buffered, but neither seems to make any difference here, the flickering still occurs. – Simon Andrews Mar 25 '12 at 10:04
  • This related [example](http://stackoverflow.com/a/5048863/230513) doesn't flicker. Edit: It relies on `javax.swing.Timer` to update on the EDT. – trashgod Mar 25 '12 at 10:04
  • I've elaborated above. Also consider [`nextGaussian()`](http://docs.oracle.com/javase/7/docs/api/java/util/Random.html#nextGaussian%28%29) for noise. – trashgod Mar 25 '12 at 10:22