I'm very new to java.swing and I have looked at other answers but I'm struggling to understand how to properly double buffer in order to remove flickering.
Currently I have a JFrame class that has an action Listener
JButton northWest = new JButton("↖");
northWest.setPreferredSize(new Dimension(60, 60));
moveBorder.add(northWest);
northWest.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
MainBoardJPanel mainBoardPanel = new MainBoardJPanel();
mainBoardPanel.movePlayer(painel.getGraphics(), -40, -40, playerInfo, currentPlayer.getUsername());
performMove();
}
});
When the button is pressed the movePlayer method (inside the MainBoardJPanel class) draws the background, the player icon at the new place and finally another layer which acts as fog of war.
public void movePlayer(Graphics g, int x, int y, PlayerInformation[] playerInfo, String username) {
super.paint(g);
Graphics2D g2d = (Graphics2D) g;
try {
background = ImageIO.read(new File("Images\\BackgroundBoard.png"));
playerIcon1 = ImageIO.read(new File("Images\\PlayerIcon1.png"));
fogOfWar = ImageIO.read(new File("Images\\FogOfWar.png"));
} catch (Exception e) {
e.printStackTrace();
}
g.drawImage(background, 0, 0, this);
g.drawImage(playerIcon1, playerInfo[0].getPosX(), playerInfo[0].getPosY(), null);
int xFOW = x - 775;
int yFOW = y - 775;
g.drawImage(fogOfWar, xFOW, yFOW, this);
updateUserInfo(x, y, username);
setDoubleBuffered(true);
Toolkit.getDefaultToolkit().sync();
g.dispose();
}
(This is a very simplified version of it to cut down on irrelevant code)
Since each image is drawn consecutively the image keeps on flickering whenever the method is called, how would I go about double buffering this?
but I'm struggling to understand how to properly double buffer in order to remove flickering
I just want to make one thing very clear. When custom painting is done correctly, Swing components are automatically double buffered.
Calling super.paint in movePlayer is an inappropriate abuse of the painting system.
Don't use getGraphics, this is not how custom painting works. Also, don't dispose of a Graphics context you didn't create, this can actually prevent components which are schedule to painted after yours from been painted.
Swing uses a passive rendering algorithm, this means that it will schedule painting to occur when ever it decides it needs to be done. You don't have control over it, the best you can do is provide "hints" to the system that you would like a component or some part of the component repainted (ie repaint). The painting subsystem will then decide when and what should be painted. Your components will be requested to perform a paint operation via their paint methods.
When done this way, Swing will double buffer the paint operation, so all components been painted are buffered and when the paint pass is complete, the image updated to the screen
I would, highly recommend, reading through:
Performing Custom Painting
Painting in AWT and Swing
for more details about how the painting system works in Swing and how you are expected to work with it
As a side note, I would also avoid loading resources (or performing any time consuming tasks) in any paint functionality. Painting should run as fast as possible
Related
I am trying to create a 3D game engine. I am using the triangle rasterizing technique so I have a list of lines (that make up triangles) in my CanvasPanel class and each time that paintComponent() is called it goes through all the lines and displays them on the screen. The problem is in my main loop, since I want to remove all lines in order to update the scene but when I call it it's being called while the paintComponent() method is still running so I get a ConcurrentModificationException. I tried having a loop going that checks if it has finished drawing and only then clear the lines list but for some reason it works only when adding a print statement, otherwise it stops working after a few seconds. What should I do to make sure the timing is right?
Relevant code in Main:
import javax.swing.*;
import java.awt.*;
public class Main
{
public static int width = 1280, height = 720;
public static void main(String[] args)
{
JFrame window = new JFrame("3D Game");
CanvasPanel canvas = new CanvasPanel(width, height);
window.setLayout(new FlowLayout());
window.getContentPane().add(canvas);
window.pack();
window.setVisible(true);
window.setResizable(false);
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// Some irrelevant calculations
while (true) // Main loop
{
// Some irrelevant calculations
for (float[][] triangle: cubeMesh)
{
// Here I'm adding lines to the lines list.
}
canvas.repaint(); // update canvas
// My attempt at making sure that clearing the lines list happens only after drawing
while (true)
{
if (!canvas.isDrawing()}
{
// Only works if I print something
break;
}
}
canvas.clearLines(); // here is the problematic line
}
}
}
Relevant code in CanvasPanel:
public class CanvasPanel extends JPanel
{
BufferedImage canvas;
JLabel label;
private ArrayList<Line> lines;
private boolean drawing;
public CanvasPanel(int width, int height)
{
canvas = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
label = new JLabel(new ImageIcon(canvas));
lines = new ArrayList<>();
add(label);
drawing = false;
}
public boolean isDrawing()
{
return drawing;
}
public void clearLines()
{
lines.clear();
}
#Override
protected void paintComponent(Graphics g)
{
drawing = true;
super.paintComponent(g);
g.setColor(Color.black);
g.fillRect(0, 0, getWidth(), getHeight());
for (Line line: lines)
{
g.setColor(line.color);
g.drawLine(line.x1, line.y1, line.x2, line.y2);
}
drawing = false;
}
public void addLine(int x1, int y1, int x2, int y2, Color color)
{
lines.add(new Line(x1, y1, x2, y2, color));
}
First, go read Performing Custom Painting and Painting in AWT and Swing to get a better understanding of how painting works in Swing.
Swing is lazy and makes use of a passive rendering workflow. This means that painting is done when the system decides it needs to be done. You can "request" a paint pass via the RepaintManager (normal via calling repaint on the component)
What this does is, if the RepaintManager agrees, places a "paint" event on the Event Dispatching Queue, which is, at some point in the future picked up by the Event Dispatching Thread and a paint pass is executed (this may be consolidated so that multiple components are painted in one pass).
There are two problems you're facing:
Painting can occur for any number of reasons, many of which you don't control and you're notified by
There's no reliable way to determine if or when a paint pass will/has occurred.
Now, add in the issue that Swing is not thread safe and you run into a mountain of other issues.
See Concurrency in Swing for more details.
So, what's the answer?
Well, you could use Worker Threads and SwingWorker or a Swing Timer to synchronise the updates depending on your needs, but honestly, I don't think making use of Swing's paint process is what you really need.
Another consideration would be use a backing buffer of your own. That is, use two or more BufferedImages onto which you draw the "state" and then pass these to the canvas to be drawn at some point in the future.
However, I would suggest taking control of the painting process directly yourself. Instead, have a look at BufferStrategy and BufferCapabilities (and the JavaDocs because it has a nice example).
This will provide you with direct control over the painting process so you can decide when and how it should be painted.
I am trying to create a window frame to display a game window. I extended JFrame in my GameWindow class and created two methods: drawBackground, which fills the screen with a solid rectangle, and drawGrid, which draws successive lines using a for-loop to make a grid. Here is my code.
public class GameWindow extends JFrame {
// instance variables, etc.
public GameWindow(int width, Color bgColor) {
super();
// ...
this.setVisible(true);
}
public void drawBackground() {
Graphics g = this.getGraphics();
g.setColor(bgColor);
g.fillRect(0, 0, this.getWidth(), this.getWidth());
// I suspect that the problem is here...
this.update(g);
this.revalidate();
this.repaint();
g.dispose();
}
public void drawGrid() {
Graphics g = this.getGraphics();
g.setColor(Color.BLACK);
for (int i = tileWidth; i < TILE_COUNT * tileWidth; i += tileWidth) {
g.drawLine(0, i * tileWidth, this.getWidth(), i * tileWidth);
g.drawLine(i * tileWidth, 0, i * tileWidth, this.getHeight());
}
// ... and here.
this.update(g);
this.revalidate();
this.repaint();
g.dispose();
}
}
However, when I try to test this class in a program like this:
public class Main {
public static void main(String[] args) {
GameWindow game = new GameWindow(700);
game.drawBackground();
game.drawGrid();
}
}
the frame appears on screen but remains blank; neither the background nor the grid is being drawn. I tried Graphics g = this.getGraphics() to this.getContentPane().getGraphics(). I also tried using many different combinations and orders in drawBackground and drawGrid of revalidate, update, etc. None of these attempts seemed to work. How can I fix this issue?
Well, Graphics g = this.getGraphics(); would be a good place to start. Since repaint just schedules a paint pass to occur with the RepaintManager, all the code using getGraphics will simply be ignored.
This is not how custom painting works. getGraphics can return null and is, at best, a snapshot of the last paint cycle, anything you paint to it will be wiped clean on the next paint cycle.
Also, don't dispose of a Graphics context you did not create, on some systems this will stop other components from been able to use it
Start by taking a look at Performing Custom Painting and Painting in AWT and Swing for a better understanding into how painting works and how you should work with it.
You're also, likely, going to want to have a read through Concurrency in Swing and How to Use Swing Timers for ideas on creating a "main loop" to update the UI at constant rate, as Swing is single threaded AND not thread safe
I'm using two .gif files and switching them in a frame.
private Image currentGIF;
if(x == 0) gif = "image1.gif"
if(x == 1) gif = "image2.gif"
ImageIcon reference = new ImageIcon(gif);
currentGIF = reference.getImage();
--
public void paint(Graphics g)
{
Graphics2D graphic = (Graphics2D) g;
graphic.drawImage(gif, 0, 0, this);
g.dispose();
}
--
#Override public void actionPerformed(ActionEvent arg0)
{
repaint();
}
And this is my problem:
image1.gif starts at the first frame.
image2.gif starts at the first frame.
image1.gif starts at a random frame.
It seems the .gif keeps running and when I show image1 again it doesn't start at the first frame.
Issues:
Never override a JFrame's paint method, but instead do your drawing within a JPanel's paintComponent method.
Never dispose of a Graphics object given to your painting method (paint or paintComponent) by the JVM as this breaks the painting chain.
Don't change the state of your GUI (for example swap images) within a painting method. Instead do it within a listener of some type that reacts to the event that you want to use to make the image change -- such as a button's ActionListener if it's to change on button press, or a Swing Timer's ActionListener if the image is to change on timer change.
If this is not an animated GIF, strongly consider making the image into an ImageIcon and simply swapping the icon of a JLabel using setIcon(...) again with the listener that you desire to use.
I've been trying to implement double buffering in my game library, but it is dramatically slower than without it.
This is my class that implements it. Basically, it draws onto an image, and then draws the image onto the JPanel's graphics (pg).
public abstract class DrawingPanel extends GameLoop {
private BufferedImage image;
private Graphics2D ig;
private Graphics pg;
private int backgroundRGB;
public DrawingPanel(final int fps, final int ups, final JPanel panel, final Color background) {
super(fps, ups);
initialize(panel, background);
}
/**
* Creates a panel with 60 fps and 120 ups
*
* #param panel
* #param background
*/
public DrawingPanel(final JPanel panel, final Color background) {
this(60, 120, panel, background);
}
public DrawingPanel(final JPanel panel) {
this(panel, Color.WHITE);
}
public DrawingPanel(final int ups, final JPanel panel, final Color background) {
super(ups);
initialize(panel, background);
}
private void initialize(final JPanel panel, final Color background) {
image = GraphicsUtils.createImage(panel.getWidth(), panel.getHeight(), Transparency.OPAQUE);
ig = (Graphics2D) image.getGraphics();
pg = panel.getGraphics();
GraphicsUtils.prettyGraphics(ig);
backgroundRGB = background.getRGB();
panel.addMouseListener(this);
panel.addMouseMotionListener(this);
panel.addKeyListener(this);
panel.addMouseWheelListener(this);
panel.setFocusable(true);
panel.requestFocusInWindow();
}
#Override
public void draw() {
// set background
Arrays.fill(((DataBufferInt) image.getRaster().getDataBuffer()).getData(), backgroundRGB);
// draw on the buffer
draw(ig);
// draw our buffer to the screen
pg.drawImage(image, 0, 0, null);
}
public abstract void draw(Graphics2D g);
}
The results of this:
This means that 25% - 6% = 19% of the time is being spent in the draw method alone!
I thought it might be the fact that I was using Array.fill, but it turned out not to be a useless "premature optimization;" if I replaced that line with
ig.setColor(backgroundColor);
ig.fillRect(0, 0, image.getWidth(), image.getHeight());
The time it takes is even longer: 26 - 5% = 21%. Is there any way I can speed this method up?
By the way, the GraphicsUtils.createImage creates a compatible image from my GraphicsConfiguration.
This entire library is on github, if you want to look at the entire code.
This is NOT how painting works in Swing -> pg = panel.getGraphics(); this is a horribly bad idea. You are fighting between the passive rendering engine of Swing and your attempt to paint to a "snap shot" of the component.
Swing components are, when you use them properly, double buffered internally, but, you need to be working with the API.
Start by having a look at Performing Custom Painting and Painting in AWT and Swing for more details about how painting works in Swing
Basically, what's happening is, you are using the results from getGraphics to paint to the panel, the RepaintManager is coming along and decides it needs to update your component and repaints it, which produces a blank panel, repeat really fast. This is what's causing your flickering. In Swing, you do not control the paint process, you can only ask to be notified when a paint cycle occurs (overriding paintComponent for example) and make requests to the painting system that a repaint should occur, but it's up to the paint system to actually decide WHEN that paint cycle occurs.
Instead, start by overriding the panel's paintComponent method and perform ALL your custom painting within it. Use repaint to request that the component be updated.
If you "really" need a active painting process, then have a look at BufferStrategy and BufferStrategy and BufferCapabilities, which provides you the means to directly control when the output is pushed to the screen device.
Solution:
Yes it is wrong. Never, never, never, NEVER read in files/images within a painting method. This method must be blindingly fast and must involve painting only, and doing this, reading in a file, will needlessly slow down your GUI to a crawl. And why do this? You can read the image in once in a constructor or method and then reuse the image object in paintComponent as many times as needed.
As an aside, you're probably better off obtaining the image as a resource and not a file.
Thanks, Hovercraft Full Of Eels
I am creating a very simple version of 'Fruit Ninja'. A game where some fruit flies onto the screen and the user has to cut it in two.
I am using a JLabel with ImageIcon for the fruit. I use a swing Timer for movement.
It works great, with animations too, but here is the tricky part. When I add a background image, the animations are verry laggy.
The question is: How can I add a background image, while the swing animations won't lose performance?
I added some code below.
Tim
My JPanel where I draw my background image:
public class PlayingField extends javax.swing.JPanel {
public PlayingField()
{
this.setBounds(0, 50, 500, 500);
this.setLayout(null);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
BufferedImage img = null;
try {
img = ImageIO.read(new File(System.getProperty("user.dir")+"/assets/background.png"));
} catch (IOException e)
{
}
g.drawImage(img, 0, 0, null);
}
}
The objects I'm drawing on them are just simple JLabels with ImageIcons.
I have a timer that handles the animation:
public class GameTimer extends javax.swing.Timer implements ActionListener {
GameController gameController;
public GameTimer(GameController gameController) {
super(delay, null);
this.addActionListener(this);
this.gameController = gameController;
}
#Override
public void actionPerformed(ActionEvent e)
{
gameController.moveObject();
}
}
and last but not least the code that moves the object:
public void moveObject()
{
activeObject.setPositionY((activeObjectView.getPositionY()+1));
playingField.repaint();
}
Yes it is wrong. Never, never, never, NEVER read in files/images within a painting method. This method must be blindingly fast and must involve painting only, and doing this, reading in a file, will needlessly slow down your GUI to a crawl. And why do this? You can read the image in once in a constructor or method and then reuse the image object in paintComponent as many times as needed.
As an aside, you're probably better off obtaining the image as a resource and not a file.