Java animation stutters when not moving mouse cursor
Asked Answered
T

4

20

I have a quite simple animation, a text in a big font moving continuously (pixel by pixel) to the left. The text is first converted to an image, then a timer task is started which repeatedly (every 10-20 ms) decrements the x coordinate of the image by 1, and does a repaint().

This program shows a strange behavior on some systems. On my PC with a nVidia card it runs smoothly. On my Vaio notebook, on a BeagleBoneBlack and on a friend's Mac it stutters heavily. It appears to pause for a while, then jump to the left about 10 pixels, pause again and so on.

What stumps me is the fact that on these systems the animation only stutters if you don't touch the mouse. As long as you move the mouse cursor within the window, no matter how slowly, or drag the window itself around, the animation runs perfectly smooth!

Can anybody explain this? Here is the program:

import java.awt.*;
import java.awt.image.*;
import java.awt.event.*;
import javax.swing.*;
import java.io.*;
import java.util.*;

class Textimg extends JComponent
{
    String      str;
    Font        font;
    int         x = 0;
    final int   ytext = 136;
    Image       img;

    public Textimg(String s)
    {
        str = s;
        font = new Font("Noserif", Font.PLAIN, 96);
        setLayout(null);
    }

    protected void paintComponent(Graphics g)
    {
        if (img == null)
        {
            img = createImage(4800, 272);
            Graphics gr = img.getGraphics();

            gr.setFont(font);
            gr.setColor(Color.BLACK);
            gr.fillRect(0, 0, 4800, 272);
            gr.setColor(new Color(135, 175, 0));
            gr.drawString(str, 0, ytext);
            gr.dispose();
        }

        g.drawImage(img, x, 0, this);
    }

    public void addX(int dif)
    {
        if (isVisible())
        {
            x = x + dif;

            Graphics g = getGraphics();

            if (g != null) paintComponent(g);
        }
    }
} 


public class Banner extends JFrame 
{ 
    StringBuffer    buf;
    int             sleeptime = 10;

    Banner(String path) throws IOException 
    { 
        setSize(new Dimension(480, 272));
        setTitle("Java Test");
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setLayout(null);

        BufferedReader reader = new BufferedReader(
                new InputStreamReader(new FileInputStream(path), "UTF-8"));

        buf = new StringBuffer();

        while (true) 
        {
           String line = reader.readLine();

           if (line == null) break;
           buf.append(line);
        }

        final Textimg textimg = new Textimg(buf.toString());

        add(textimg);
        textimg.setBounds(0, 0, 480, 272);

        final javax.swing.Timer timer = new javax.swing.Timer(200, new ActionListener()
        {
            public void actionPerformed(ActionEvent e)
            {
                textimg.addX(-1);
            }
        });

        timer.setDelay(sleeptime);
        timer.start();
    }

    //----------------------------------------------------------------------

    public static void main(String[] args) throws Exception
    {
        new Banner(args[0]).setVisible(true);
    }
}
Tummy answered 20/10, 2013 at 16:49 Comment(0)
C
44

Try calling this method when you are done drawing:

 Toolkit.getDefaultToolkit().sync();

This flushs the graphics buffer which some systems like Linux use. See the Javadoc: http://docs.oracle.com/javase/7/docs/api/java/awt/Toolkit.html#sync()

Cheslie answered 15/10, 2014 at 17:9 Comment(7)
This fixed the stutter for me as is (on a Chrubuntu 14.04 Acer C720), but I switched it to frame.getToolkit().sync(); to avoid globals. Thanks!Verily
Oh man, thank you so much, I've been trying to fix this bug for hours! Also used getToolkit().sync() in the frame after the call to repaint(). (Ubuntu 14.04)Noseband
This answer helped me today.Ayres
How would you 'implement' this for the animation of for example an indeterminate JProgressBar animation of the Nimbus LAF? This problem sounds more like a bona fide bug.Kizzee
You saved my day!Crucible
@Verily What's the advantage of frame.getToolkit().sync(); over Toolkit.getDefaultToolkit().sync();?Jarita
@PaulWalker - I have no recollection of what this was about, but sounds like it was just to avoid using a static method when a non-static was available.Verily
P
3
  • Don't, EVER, use getGraphics and you should NEVER call paintComponent yourself, this not how custom painting is done in Swing. Instead, update the state and call repaint.
  • Don't rely on magic numbers, use the information you have at hand (getWidth and getHeight)
  • Swing components are doubled buffered, so it's unlikely you would need to create you own buffered strategy. This simple act could be slowing down your painting
  • You must call super.paintComponent. This is even more important with JComponent, as it is not opaque and failing to do so could result in some nasty paint artefacts.
  • You should override JComponent#getPreferredSize so it can work with layout managers for efficiently.
  • You may find a shorter delay produces a better illusion, say 40 milliseconds (roughly 25fps) for example

Take a look at Swing animation running extremely slow, which through some object management and optimisation, was able to increase from 500 animated objects up to 4500.

Also take a look at Performing Custom Painting and Painting in AWT and Swing in particular

Pottage answered 20/10, 2013 at 19:37 Comment(0)
C
2

Profiling shows that you are saturating the shared thread used by javax.swing.Timer. One mitigation strategy is to use a longer period and/or a larger increment/decrement, as shown here.

Addendum: In addition, you are laboriously re-rendering the entire image in each call to paintComponent(). Instead, render it once using TextLayout, seen here, and draw() only the newly visible portion each time.

image

Casting answered 20/10, 2013 at 17:6 Comment(6)
See also MarqueePanel, cited here.Casting
Thanks. I'll look into TextLayout, never used that before. However, IMHO the fact that the animation runs smoothly whenever I move the mouse clearly shows that a resource bottleneck can't be the reason.Tummy
Results will vary by platform; on my system, the the mouse has no effect. I'd hoist the relatively expensive rendering out of the implicit timer loop. Your basic approach is sound and potentially smoother than character based schemes.Casting
I experminented a little more. The effect is completely independent of the text length, and even present if in the loop I do nothing but x = x -1; setLocation(x, 0);.Tummy
And I tried other Java versions: Java 6 is similar, Java 8 EA is worse, but in Java 5 it works just fine on my notebook, the animation runs smoothly, no need to move the mouse. So I guess it is a bug that crept into Java 6. Now I have a different problem - Oracle doesn't offer a Java 5 JRE for ARM processors.Tummy
If you're still using getGraphics(), I'm not surprised. I've panned large bitmaps on 5, 6 & 7; haven't tried 8. Please edit your question to update your sscce to the current code.Casting
T
0

Problem solved!

To answer my own question: After realizing that any continuous input (mouse or keyboard) makes the animation run smoothly, I remembered that inputs can be generated by the program itself, using an object of the class java.awt.Robot. That lead to a simple workaround: Create a Robot and let it press a key or a mouse move in each animation cycle.

final Robot robot = new Robot();
javax.swing.Timer timer = new javax.swing.Timer(initialDelay, new ActionListener()
{
    public void actionPerformed(ActionEvent e)
    {
        // update your image...

        robot.keyPress(62);
     }
});

This is a kludge, but works perfectly.

Tummy answered 22/10, 2013 at 10:31 Comment(2)
How did you choose the number 62?Bazil
Haha, what an approachWifely

© 2022 - 2024 — McMap. All rights reserved.