java what is heavier: Canvas or paintComponent()?

2020-02-02 03:18发布

Can someone tell me what should I use for painting graphics on a JPanel: Canvas, or simply drawing everything in paintComponent()? I am painting hundreds of small images around 30 times per second, and I wonder which one would be the most lightweight, and in what conditions should I use both? Thanks.

1条回答
叛逆
2楼-- · 2020-02-02 03:57

The issue is rather broad and the context rather slim.

Consider taking a look at Rotating multiple images causing flickering. Java Graphics2D and Swing animation running extremely slow for example.

The first uses paintComponent to render up to 10, 000 rotating images at 25fps (or as close as it can).

The second actually animates up to 4, 500 component based images

The ability to paint effectively at this rate is only one consideration to the overall picture

Updated

The primary difference between using a JPanel and Canvas is the difference in rendering algorithms.

With a JPanel you are still at the mercy of the repaint manager. This approach is commonly known as "passive rendering". That is, the manager is responsible for determine what and when something should be painted. Paints are done ad-hoc, when the repaint manager decides something needs to be painted, such as because some OS event has requested that part or whole of the screen should be updated.

Technically, you are not in control of this process and can only simply make requests that a repaint should occur. You may also incur some delays occasionally because the system has butted in and forced an update

Canvas provides you with a BufferStrategy, which is more closely tied to the underlying rendering pipeline, potentially, making it faster.

With this, you become responsible for scheduling the repaints. This is commonly known as "active rendering".

You can still but heads with the OS with this approach...

Either way, unless your update/render pipelines are well optimised, you can still run into lots of problems and could actually get better performance from using JPanel instead of Canvas

Personally, if you're not sure or if you've not done anything like this before, I'd start with JPanel. Generally, it's slightly simpler to deal with.

I changed the linked example to maintain a FPS counter as well...

MiniFig

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.TimeUnit;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

public class ZombieLand {

    protected static final Random RND = new Random();

    private static BufferedImage zombie;
    private static int fps = 25;

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

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

                try {
                    zombie = ImageIO.read(getClass().getResource("/MiniFig.png"));
                } catch (IOException ex) {
                    ex.printStackTrace();
                }

                final ZombiePane zombiePane = new ZombiePane();

                final JSlider slider = new JSlider(1, 10000);
                slider.setMajorTickSpacing(1000);
                slider.setMinorTickSpacing(100);
                slider.setPaintTicks(true);
                slider.addChangeListener(new ChangeListener() {
                    @Override
                    public void stateChanged(ChangeEvent e) {
                        JSlider slider = (JSlider) e.getSource();
                        zombiePane.setZombies(slider.getValue());
                    }
                });

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setLayout(new BorderLayout());
                frame.add(zombiePane);
                frame.add(slider, BorderLayout.SOUTH);
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);

                SwingUtilities.invokeLater(new Runnable() {
                    @Override
                    public void run() {
                        slider.setValue(10000);
                    }
                });
            }
        });
    }

    public static class ZombiePane extends JPanel {

        private List<ZombieSprite> sprites;
        protected static final Object SPRITE_LOCK = new Object();

        private int desiredCount = 1;

        public ZombiePane() {
            sprites = new ArrayList<>(25);
            sprites.add(new ZombieSprite());
            Thread t = new Thread(new GameLoop());
            t.setDaemon(false);
            t.start();
            Font font = getFont();
            setFont(font.deriveFont(Font.BOLD, 48f));
        }

        public void setZombies(int count) {
            desiredCount = count;
        }

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

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2d = (Graphics2D) g.create();
            synchronized (SPRITE_LOCK) {
                for (ZombieSprite sprite : sprites) {
                    sprite.paint(g2d);
                }
            }
            String text = Integer.toString(sprites.size());
            FontMetrics fm = g2d.getFontMetrics();
            g2d.drawString(text, getWidth() - fm.stringWidth(text), getHeight() - fm.getHeight() + fm.getAscent());

            text = Integer.toString(fps);
            g2d.drawString(text, 0, getHeight() - fm.getHeight() + fm.getAscent());
            g2d.dispose();
        }

        protected void cycle() {
            synchronized (SPRITE_LOCK) {
                if (desiredCount != sprites.size()) {
                    int count = 0;
                    int fill = 100;
                    while (sprites.size() > desiredCount && count < fill) {
                        sprites.remove(0);
                        count++;
                    }
                    count = 0;
                    while (sprites.size() < desiredCount && count < fill) {
                        sprites.add(new ZombieSprite());
                        count++;
                    }
                }

                for (ZombieSprite sprite : sprites) {
                    sprite.update(getWidth(), getHeight());
                }
            }
        }

        public static class ZombieSprite {

            private Point motionDelta;
            private double rotationDelta;

            private Point location;
            private double angle;

            public ZombieSprite() {
                motionDelta = new Point();
                motionDelta.x = (int) ((Math.random() * 3) + 1);
                motionDelta.y = (int) ((Math.random() * 3) + 1);
                if (Math.random() > 0.5) {
                    motionDelta.x *= -1;
                }
                if (Math.random() > 0.5) {
                    motionDelta.y *= -1;
                }
                rotationDelta = (int) ((Math.random() * 9) + 1);
                if (Math.random() > 0.5) {
                    rotationDelta *= -1;
                }
            }

            public void paint(Graphics2D g2d) {
                if (location != null) {
                    Graphics2D g = (Graphics2D) g2d.create();
                    AffineTransform at = new AffineTransform();
                    at.translate(location.x, location.y);
                    at.rotate(Math.toRadians(angle), zombie.getWidth() / 2, zombie.getHeight() / 2);
                    g.setTransform(at);
                    g.drawImage(zombie, 0, 0, null);
                    g.dispose();
                }
            }

            public void update(int width, int height) {
                if (location == null) {
                    angle = (Math.random() * 360d);
                    location = new Point();
                    location.x = (int) (Math.random() * (width - zombie.getWidth()));
                    location.y = (int) (Math.random() * (height - zombie.getHeight()));
                } else {
                    angle += rotationDelta;
                    location.x += motionDelta.x;
                    location.y += motionDelta.y;

                    if (location.x < 0) {
                        location.x = 0;
                        motionDelta.x *= -1;
                    } else if (location.x + zombie.getWidth() > width) {
                        location.x = width - zombie.getWidth();
                        motionDelta.x *= -1;
                    }
                    if (location.y < 0) {
                        location.y = 0;
                        motionDelta.y *= -1;
                    } else if (location.y + zombie.getHeight() > height) {
                        location.y = height - zombie.getHeight();
                        motionDelta.y *= -1;
                    }
                }
            }

        }

        public class GameLoop implements Runnable {

            private long last;
            private long start;
            private int wait;

            private boolean keepRunning = true;

            public void run() {

                // Calculate the optimal/maximum delay time
                // This is converted to nanos so it can be 
                // used to calculate the actual delay...
                long millisPerSecond = TimeUnit.MILLISECONDS.convert(1, TimeUnit.SECONDS);
                long optimalDelay = Math.round(millisPerSecond / 25);

                optimalDelay = TimeUnit.MILLISECONDS.toNanos(optimalDelay);

                // Last start of a "second" loop                    
                long loop = System.nanoTime();
                int frameCount = 0;
                // While gaming...
                while (keepRunning) {
                    // Start of this cycle...
                    long now = System.nanoTime();

                    // Update the state and render the 
                    // current frame...
                    cycle();
                    repaint();

                    // How long did that update take??
                    long timeTaken = System.nanoTime();
                    long delta = timeTaken - now;

                    // Subtract the delay from the maximum delay
                    long delay = optimalDelay - delta;
                    if (delay > 0) {
                        try {
                            // Sleep expects milliseconds...
                            delay = TimeUnit.NANOSECONDS.toMillis(delay);
                            Thread.sleep(delay);
                        } catch (InterruptedException ex) {
                            ex.printStackTrace();
                        }
                    }

                    // Calculate if we've being running for a second yet...
                    long loopDelay = TimeUnit.NANOSECONDS.toSeconds(System.nanoTime() - loop);
                    // If the loop has been cycling for a second...
                    if (loopDelay >= 1) {
                        // Reset the loop time
                        loop = System.nanoTime();
                        System.out.println("FPS = " + frameCount);
                        fps = frameCount;
                        frameCount = 0;
                    } else {
                        // Add another frame to the pile...
                        frameCount++;
                    }
                }
            }
        }
    }
}
查看更多
登录 后发表回答