Screen flickers when setting background

512 views Asked by At

I'm trying to make a simple GUI program without using JComponents. Currently, I have a BufferedImage that I draw to off screen so that it doesn't flicker (or so I thought).

I made a new program here to replicate the issue:

package Main;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;

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

public class Main {

private final static JFrame frame = new JFrame();
private final static Panel panel = new Panel();

public static void main(String[] args) {
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    panel.setPreferredSize(new Dimension(1000, 750));
    frame.setContentPane(panel);
    frame.pack();
    frame.setVisible(true);

    while (true) {
        panel.setBackgroundColour(Color.WHITE);
        panel.setBackgroundColour(Color.BLACK);
        panel.repaint();
    }
}

private static class Panel extends JPanel {

    private final BufferedImage offScreen = new BufferedImage(1000, 750, BufferedImage.TYPE_INT_ARGB);
    private final Graphics graphics = offScreen.getGraphics();

    @Override
    protected void paintComponent(Graphics graphics) {
        graphics.drawImage(offScreen, 0, 0, null);
    }

    public void setBackgroundColour(Color colour) {
        graphics.setColor(colour);
        graphics.fillRect(0, 0, 1000, 750);
    }
}

}

In the example above, I made the screen turn black, and then white (offscreen). What I'd expect is that paintComponent() only displays the white screen. Instead, a black screen is showed as well, but everything is flickered.

Am I just using Graphics2D incorrectly, or should I just use BufferStrategy to incorporate my double buffering needs?

1

There are 1 answers

0
MadProgrammer On BEST ANSWER

My best guess is you have a race condition, where your while-loop is trying to update the BufferedImage, but Swing is also trying to paint it, meaning they are getting dirty updates between them. Also, you might be thrashing the Event Dispatching Thread, which could have it's own, long term issues.

After some playing around, I was able to get something like this to work...

import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsEnvironment;
import java.awt.image.BufferedImage;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class Main {

    private final static JFrame frame = new JFrame();
    private final static Panel panel = new Panel();

    public static void main(String[] args) {
        new Main();
    }

    public Main() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                    ex.printStackTrace();
                }

                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                panel.setPreferredSize(new Dimension(1000, 750));
                frame.setContentPane(panel);
                frame.pack();
                frame.setVisible(true);
            }
        });

        while (true) {
            panel.setBackgroundColour(Color.WHITE);
            panel.setBackgroundColour(Color.BLACK);
            panel.repaint();
            try {
                Thread.sleep(40);
            } catch (InterruptedException ex) {
                Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
            }
        }

    }

    private static class Panel extends JPanel {

        private BufferedImage offScreen = new BufferedImage(1000, 700, BufferedImage.TYPE_INT_ARGB);

        @Override
        protected void paintComponent(Graphics graphics) {
            super.paintComponent(graphics);
            graphics.drawImage(offScreen, 0, 0, this);
        }

        public void setBackgroundColour(Color colour) {
            Graphics graphics = offScreen.getGraphics();
            graphics.setColor(colour);
            graphics.fillRect(0, 0, 1000, 700);
            graphics.dispose();
        }

    }

    public static BufferedImage createCompatibleImage(int width, int height, int transparency) {

        BufferedImage image = getGraphicsConfiguration().createCompatibleImage(width, height, transparency);
        image.coerceData(true);
        return image;

    }

    public static GraphicsConfiguration getGraphicsConfiguration() {

        return GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration();

    }
}

All it does is injects a small delay (25fps) between the updates, allowing Swing time to render the result.

You have to remember at two things with Swing, repaint doesn't happen immediately and may not happen at all, depending on what the RepaintManager decides to do. Second, you don't control the painting process.

Swing uses a passive rendering algorithm, meaning that painting will occur when it's needed, many times without your knowledge or intervention. The best you can do is make suggestions to the framework when you want something updated

See Painting in AWT and Swing and Performing Custom Painting for more details.