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);
}
}
JPanel
, override paintComponent(Graphics)
rather than paint(Graphics)
Thread.sleep(n)
implement a Swing Timer
for repeating tasks or a SwingWorker
for long running tasks.
See Concurrency in Swing for more details.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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With