0

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?

  • Call `super.paintComponent` before doing any custom painting – MadProgrammer Jun 15 '15 at 01:55
  • Your while loop is kind of crazy – MadProgrammer Jun 15 '15 at 01:56
  • I made it while (true) only as an example here. In my actual program, it's not like that. :p I'll try that though. – asdfaweglkelkr Jun 15 '15 at 01:59
  • You may also have a thread race condition, as the panel is "trying" to paint the image, you're updating it... – MadProgrammer Jun 15 '15 at 02:01
  • Just tried calling super.paintComponent, but it didn't change anything. I'll just leave it there though I guess. – asdfaweglkelkr Jun 15 '15 at 02:02
  • I know nothing about threads, how should I go about fixing that? o-o – asdfaweglkelkr Jun 15 '15 at 02:02
  • Well, the first question is, why are you trying to invent your own double buffering? Swing is already double buffered. The solution right now, is complicated, as you would need to devices some kind of page flipping algorithm which would allow you to separate the update from the painting – MadProgrammer Jun 15 '15 at 02:04
  • I'm just not sure how to use Swing's double buffering. I read about BufferStrategy though, so I guess I'll just use that. – asdfaweglkelkr Jun 15 '15 at 02:08
  • Back when I was programming in C++ using the SDL library, I had to do double buffering myself, so I wanted my methods to look similar to what I had in my C++ programs. – asdfaweglkelkr Jun 15 '15 at 02:09
  • The general answer is, you don't, it just is. When `paintComponent` is called, the `Graphics` context is already been double buffered automatically for you. Swing uses a passive rendering algorithm, this means painting can occur at any time, most of the time without your intervention. Maybe have a look at [Painting in AWT and Swing](http://www.oracle.com/technetwork/java/painting-140037.html) and [Performing Custom Painting](http://docs.oracle.com/javase/tutorial/uiswing/painting/) – MadProgrammer Jun 15 '15 at 02:10
  • Ah I see. Thanks! What I was trying to do was actually an example in here: http://stackoverflow.com/questions/10508042/how-do-you-double-buffer-in-java-for-a-game except that I used a JPanel instead of Canvas. – asdfaweglkelkr Jun 15 '15 at 02:21
  • `Canvas` has a direct connection to the hardware (or close enough to it) which allows it to control it's own painting process (active painting), not that easy to achieve in Swing – MadProgrammer Jun 15 '15 at 02:22

1 Answers1

2

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.

MadProgrammer
  • 343,457
  • 22
  • 230
  • 366