Absent Link
Absent Link

Reputation: 7

Can a specific RepaintManager be used for a specific JPanel?

I understand that the Java RepaintManager will coalesce calls to repaint(), which is fine for 99% of rendering. I have one JPanel that I'd like to update on a timer (100 ms) with images to provide smooth'ish rendering like video. In practice the RepaintManager seems to steal/ignore about every other repaint() unless the mouse is being moved. I'm wondering what options I have to workaround this issue. I also looked at paintImmediately(), but it results in the same behavior as repaint(), thus not very useful. Thanks in advance for the helpful ideas!

  1. Is it possible to create and use a custom RepaintManager for a specific JPanel, and use the default for everything else?
  2. Is there a way to make the default RepaintManger determine a certain panel is "dirty" such that it would be repainted vs. ignored?

Below is some code to illustrate the implementation, you will notice (at least on my Linux testing), that the numbers will skip almost every other sequence.

    public class PanelRepaintIssue
    {
        private static final int kWIDTH = 200;
        private static final int kHEIGHT = 100;
        private static final int kNUM_IMAGES = 10;
        private static final int kREPAINT_DELAY = 250;

        private final JPanel _ImagePanel;
        private final BufferedImage[] _Images;
        private final Timer _Timer;

        private TimerTask _TimerTask;
        private int _Index;

        public PanelRepaintIssue()
        {
            _Index = 0;

            _ImagePanel = new JPanel()
            {
                @Override
                protected void paintComponent(Graphics g)
                {
                    super.paintComponent(g);

                    if (_Index < kNUM_IMAGES)
                    {
                        g.drawImage(_Images[_Index], 0, 0, null);
                    }
                }
            };
            _ImagePanel.setSize(new Dimension(kWIDTH, kHEIGHT));
            _ImagePanel.setPreferredSize(new Dimension(kWIDTH, kHEIGHT));

            _Images = new BufferedImage[kNUM_IMAGES];

            for (int i = 0; i < _Images.length; ++i)
            {
                _Images[i] = new BufferedImage(kWIDTH, kHEIGHT, BufferedImage.TYPE_INT_ARGB);
                Graphics2D t2d = _Images[i].createGraphics();
                t2d.setColor(Color.BLACK);
                t2d.fillRect(0, 0, kWIDTH, kHEIGHT);
                t2d.setColor(Color.RED);
                t2d.drawString(Integer.toString(i), kWIDTH/2, kHEIGHT/2);
                t2d.dispose();
            }

            _Timer = new Timer(this.getClass().getName());
        }

        public JPanel getPanel()
        {
            return _ImagePanel;
        }

        public void start()
        {
            if (null != _TimerTask)
            {
                _TimerTask.cancel();
                _TimerTask = null;
            }

            _TimerTask = new TimerTask()
            {
                @Override
                public void run()
                {
                    ++_Index;

                    if (_Index >= kNUM_IMAGES)
                    {
                        _Index = 0;
                    }

                    _ImagePanel.repaint();
                    // Also tried _ImagePanel.paintImmediately(0, 0, kWIDTH, kHEIGHT);
                }
            };

            _Timer.scheduleAtFixedRate(_TimerTask, 1000, kREPAINT_DELAY);
        }

        public static void main(String[] args)
        {
            PanelRepaintIssue tPanel = new PanelRepaintIssue();
            tPanel.start();
            JFrame tFrame = new JFrame("PanelRepaintIssue");
            tFrame.add(tPanel.getPanel());
            tFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            tFrame.setResizable(false);
            tFrame.pack();
            tFrame.setVisible(true);
        }
    }

Upvotes: 0

Views: 256

Answers (2)

Absent Link
Absent Link

Reputation: 7

The issue ended up being platform specific with Linux, possibly other, but only Linux was tested. The issue was corrected or avoided by using the following:

_ImagePanel.repaint();
Toolkit.getDefaultToolkit().sync();

The addition of Toolkit.getDefaultToolkit().sync() ensured that repaint() actually updated the image on the screen.

Upvotes: 0

Gilbert Le Blanc
Gilbert Le Blanc

Reputation: 51565

I'm still not sure what the painting problem is.

I made some changes to your code.

  1. I used the SwingUtilities invokeLater method to put the creation and execution of the Swing components on the Event Dispatch Thread (EDT).

  2. I removed everything from the paintComponent method except the painting code. Nothing else should happen in the paintComponent method but painting.

Here's the code I ran.

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.util.Timer;
import java.util.TimerTask;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

public class PanelRepaintIssue {
    private static final int kWIDTH = 200;
    private static final int kHEIGHT = 100;
    private static final int kNUM_IMAGES = 10;
    private static final int kREPAINT_DELAY = 250;

    private final JPanel _ImagePanel;
    private final BufferedImage[] _Images;
    private final Timer _Timer;

    private TimerTask _TimerTask;
    private int _Index;
    private int _TimerCnt;
    private int _PaintCnt;

    public PanelRepaintIssue() {
        _Index = 0;
        _TimerCnt = 0;
        _PaintCnt = 0;

        _ImagePanel = new JPanel() {

            private static final long serialVersionUID = 1L;

            @Override
            protected void paintComponent(Graphics g) {
                super.paintComponent(g);

//              if (_Index < kNUM_IMAGES) {
                    g.drawImage(_Images[_Index], 0, 0, null);
                    ++_PaintCnt;
//                  System.out.println("Timer: " + _TimerCnt + 
//                          " Paint: " + _PaintCnt);
//              }
            }
        };
        _ImagePanel.setSize(new Dimension(kWIDTH, kHEIGHT));
        _ImagePanel.setPreferredSize(
                new Dimension(kWIDTH, kHEIGHT));

        _Images = new BufferedImage[kNUM_IMAGES];

        for (int i = 0; i < _Images.length; ++i) {
            _Images[i] = new BufferedImage(kWIDTH, kHEIGHT, 
                    BufferedImage.TYPE_INT_ARGB);
            Color color = new Color(128, 128, 20 * i);
            Graphics g = _Images[i].getGraphics();
            g.setColor(color);
            g.fillRect(0, 0, kWIDTH, kHEIGHT);
            g.dispose();
        }

        _Timer = new Timer(this.getClass().getName());
    }

    public JPanel getPanel() {
        return _ImagePanel;
    }

    public void start() {
        if (null != _TimerTask) {
            _TimerTask.cancel();
            _TimerTask = null;
        }

        _TimerTask = new TimerTask() {
            @Override
            public void run() {
                ++_TimerCnt;
                ++_Index;

                if (_Index >= kNUM_IMAGES) {
                    _Index = 0;
                }

                _ImagePanel.repaint();

                System.out.println("Timer: " + _TimerCnt + 
                        " Paint: " + _PaintCnt);
                // Also tried _ImagePanel.paintImmediately
                //    (0, 0, kWIDTH, kHEIGHT);
            }
        };

        _Timer.scheduleAtFixedRate(_TimerTask, 1000, 
                kREPAINT_DELAY);
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                PanelRepaintIssue tPanel = 
                        new PanelRepaintIssue();
                tPanel.start();
                JFrame tFrame = new JFrame("PanelRepaintIssue");
                tFrame.add(tPanel.getPanel());
                tFrame.setDefaultCloseOperation(
                        JFrame.EXIT_ON_CLOSE);
                tFrame.setResizable(false);
                tFrame.pack();
                tFrame.setVisible(true);
            }   
        });
    }
}

Edit: Based on the new information provided in a comment instead of the original question, I reworked the code.

I didn't see a problem. Here are the last 4 lines of the println from the Timer method.

Timer: 200 Paint: 201
Timer: 201 Paint: 202
Timer: 202 Paint: 203
Timer: 203 Paint: 205

As you can see, the numbers are synchronized, except for the last line. The discrepancy was probably caused by my cancelling the program through Eclipse.

Here's the revised code. I still don't see the issue.

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.util.Timer;
import java.util.TimerTask;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

public class PanelRepaintIssue {
    private static final int kWIDTH = 300;
    private static final int kHEIGHT = 200;
    private static final int kNUM_IMAGES = 10;
    private static final int kREPAINT_DELAY = 250;

    private final JPanel _ImagePanel;
    private final BufferedImage[] _Images;
    private final Timer _Timer;

    private TimerTask _TimerTask;
    private int _Index;
    private int _TimerCnt;
    private int _PaintCnt;

    public PanelRepaintIssue() {
        _Index = 0;
        _TimerCnt = 0;
        _PaintCnt = 0;

        _ImagePanel = new JPanel() {

            private static final long serialVersionUID = 1L;

            @Override
            protected void paintComponent(Graphics g) {
                super.paintComponent(g);
                g.drawImage(_Images[_Index], 0, 0, null);
                ++_PaintCnt;
            }
        };
        _ImagePanel.setSize(new Dimension(kWIDTH, kHEIGHT));
        _ImagePanel.setPreferredSize(
                new Dimension(kWIDTH, kHEIGHT));

        _Images = new BufferedImage[kNUM_IMAGES];

        int x = kWIDTH / 2;
        int y = kHEIGHT / 2;

        for (int i = 0; i < _Images.length; ++i) {
            _Images[i] = new BufferedImage(kWIDTH, kHEIGHT, 
                    BufferedImage.TYPE_INT_ARGB);
            String text = Integer.toString(i + 1);
            Graphics g = _Images[i].getGraphics();
            g.setFont(g.getFont().deriveFont(80f));
            g.setColor(Color.BLACK);
            g.drawString(text, x, y);
            g.dispose();
        }

        _Timer = new Timer(this.getClass().getName());
    }

    public JPanel getPanel() {
        return _ImagePanel;
    }

    public void start() {
        if (null != _TimerTask) {
            _TimerTask.cancel();
            _TimerTask = null;
        }

        _TimerTask = new TimerTask() {
            @Override
            public void run() {
                ++_TimerCnt;
                ++_Index;

                if (_Index >= kNUM_IMAGES) {
                    _Index = 0;
                }

                updateImagePanel();

                System.out.println("Timer: " + _TimerCnt + 
                        " Paint: " + _PaintCnt);
            }
        };

        _Timer.scheduleAtFixedRate(_TimerTask, 1000, 
                kREPAINT_DELAY);
    }

    private void updateImagePanel() {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                _ImagePanel.repaint();
            }
        });
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                PanelRepaintIssue tPanel = 
                        new PanelRepaintIssue();
                tPanel.start();
                JFrame tFrame = new JFrame("PanelRepaintIssue");
                tFrame.add(tPanel.getPanel());
                tFrame.setDefaultCloseOperation(
                        JFrame.EXIT_ON_CLOSE);
                tFrame.setResizable(false);
                tFrame.pack();
                tFrame.setVisible(true);
            }   
        });
    }
}

Edited again: I ran your code while I prepared lunch. Here are the last few lines of the println output.

Timer: 4037 Paint: 4038
Timer: 4038 Paint: 4040
Timer: 4039 Paint: 4040
Timer: 4040 Paint: 4041
Timer: 4041 Paint: 4042
Timer: 4042 Paint: 4043

As you can see, not one frame of the animation was dropped. At 250 milliseconds per frame, the Swing repaint manager has no problem painting an image.

Just for fun, I decreased the kREPAINT_DELAY to 10 milliseconds per frame. That's 100 frames / second.

Here are the println lines from that test.

Timer: 1104 Paint: 1105
Timer: 1105 Paint: 1106
Timer: 1106 Paint: 1107
Timer: 1107 Paint: 1108
Timer: 1108 Paint: 1109
Timer: 1109 Paint: 1110

Again, not a single frame dropped.

The problem is probably in your code, not in the Swing repaint manager.

Upvotes: 0

Related Questions