0

I am rendering a background image that should remain unchanging. On top of it, there should be a moving item image.

Right now, I am rendering both the background image and item image on every call to paint. However, is there a more intelligent and efficient way to render the moving the item without having to re-render the background image?

This is what my class looks like:

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

public class Test extends Canvas {

    private int x, y;
    private Toolkit toolkit;

    Test() {
        toolkit = Toolkit.getDefaultToolkit();
    }

    void render() {

        JFrame f = new JFrame();
        f.add(this);
        f.setSize(500, 500);
        f.setVisible(true);
    }

    public void paint(Graphics g) {

        // Render the background image
        Image background = toolkit.getImage("background.jpg");
        g.drawImage(background, 0, 0, this);

        // Render the current position of the item
        Image item = toolkit.getImage("item.png");
        g.drawImage(item, x, y, this);
    }

    public void moveItem(int x, int y) {
        this.x = x;
        this.y = y;

        validate();
        repaint();
    }
}
Nir
  • 221
  • 3
  • 12
  • 2
    As shown [here](https://stackoverflow.com/a/3256941/230513), "Swing programs should override `paintComponent()` instead of overriding `paint()`."—[*Painting in AWT and Swing: The Paint Methods*](http://www.oracle.com/technetwork/java/painting-140037.html#callbacks). Also, do image I/O _outside_ your implementation of `paintComponent()`. – trashgod Jan 02 '20 at 00:06
  • Ok, a few things. Look at [Painting in AWT and Swing](https://www.oracle.com/technetwork/java/painting-140037.html) - it talks a lot about how to optimise your painting process. If you want complete control, then you should be look at [BufferStrategy and BufferCapabilities](https://docs.oracle.com/javase/tutorial/extra/fullscreen/bufferstrategy.html). But one of the starting points would be to look at [`JComponent#paintImmediately(int, int, int, int)`](https://docs.oracle.com/javase/10/docs/api/javax/swing/JComponent.html#paintImmediately(int,int,int,int)) – MadProgrammer Jan 02 '20 at 03:16
  • And [`JComponent#paintImmediately(Rectangle)`](https://docs.oracle.com/javase/10/docs/api/javax/swing/JComponent.html#paintImmediately(java.awt.Rectangle)) – MadProgrammer Jan 02 '20 at 03:17
  • I would also avoid using `validate` in this case as it's not doing anything other then wasting time. Has already been stated, you should not be loading your resources inside the paint method, this is potentially wasting time – MadProgrammer Jan 02 '20 at 03:22

1 Answers1

1

So, there are lots of little things you can do to make rendering faster

  • Reduce the amount of work either the update loop or paint pass is actually doing (like not loading images)
  • Reduce the number of short lived objects you create in the update look and paint pass (for example
  • Optimising the clipping area which is updated.

The following is a simple example which uses JComponent#paintImmediately to restrict the actual area which is painted. In this case, it updates the old space the new space the player object occupies. This is rather the simple and a more complicated update pass might need to perform a number of such calls to deal with disappearing objects or objects which have a much large area of movement.

Simple example

import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.Area;
import java.awt.image.BufferedImage;
import java.awt.image.ImageObserver;
import java.io.File;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;

public class Test {

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

    public Test() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    JFrame frame = new JFrame();
                    frame.add(new TestPane());
                    frame.pack();
                    frame.setLocationRelativeTo(null);
                    frame.setVisible(true);
                } catch (IOException ex) {
                    ex.printStackTrace();
                }
            }
        });
    }

    public class TestPane extends JPanel {

        private BufferedImage background;
        private Player player;

        public TestPane() throws IOException {
            background = ImageIO.read(new File("/Users/shanew/Downloads/124178.jpg"));
            player = new Player();

            player.setX(-player.getWidth());
            player.setY((getPreferredSize().height - player.getHeight()) / 2);

            Timer timer = new Timer(5, new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    Area area = new Area(player.getBounds());
                    int x = player.getX();
                    x += 1;
                    if (x > getPreferredSize().width) {
                        x = -player.getWidth();
                    }
                    player.setX(x);
                    area.add(new Area(player.getBounds()));
                    paintImmediately(area.getBounds());
                }
            });
            addMouseListener(new MouseAdapter() {
                @Override
                public void mouseClicked(MouseEvent e) {
                    System.out.println("Clicked");
                    try {
                        Thread.sleep(5000);
                    } catch (InterruptedException ex) {
                        Logger.getLogger(Test.class.getName()).log(Level.SEVERE, null, ex);
                    }
                    System.out.println("Started");
                    timer.start();

                }
            });
        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(711, 400);
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2d = (Graphics2D) g.create();
            int x = (getWidth() - background.getWidth()) / 2;
            int y = (getHeight() - background.getHeight()) / 2;
            g2d.translate(x, y);
            g2d.drawImage(background, 0, 0, this);
            player.paint(g2d, this);
            g2d.dispose();
        }

    }

    public class Player {

        private BufferedImage image;

        private int x, y;
        private Rectangle bounds;

        public Player() throws IOException {
            image = ImageIO.read(new File("/Users/shanew/Downloads/e13333ab21b52d8.png"));
            bounds = new Rectangle();
        }

        public int getWidth() {
            return image.getWidth();
        }

        public int getHeight() {
            return image.getHeight();
        }

        public int getX() {
            return x;
        }

        public void setX(int x) {
            this.x = x;
        }

        public int getY() {
            return y;
        }

        public void setY(int y) {
            this.y = y;
        }

        public void paint(Graphics2D g2d, ImageObserver observer) {
            g2d.drawImage(image, x, y, observer);
        }

        public Rectangle getBounds() {
            bounds.x = getX();
            bounds.y = getY();
            bounds.width = getWidth();
            bounds.height = getHeight();
            return bounds;
        }

    }

}

Another work flow might be to use a seperate BufferedImage for the dynamic content. This would then be faster of the subsystem to render rather then trying to render numerous small elements

MadProgrammer
  • 343,457
  • 22
  • 230
  • 366