Graphics2D 绘画变得太慢

Graphics2D painting getting too slow

我正在开发一个模拟元胞自动机的应用程序。碰巧我需要非常快地(每 100 毫秒)绘制一个 80x80 正方形(6400 正方形)的网格。

我的第一个方法是使用 JLabels,但它真的很慢。现在我正在使用 Graphics2D 并且效果很好,但是在绘制大约 50 次之后,它开始变慢,并且随着转弯的推进而变得越来越慢。

我需要在每回合后调用 repaint() 才能 'repaint' 正方形,但我猜测之前绘制的内容仍在内存中,是吗?我怎样才能丢弃绘制的内容,这样它就不会占用缓冲区或内存?

最后一件事,我看到了这个 post 但我看不出我的代码和那里提供的代码之间的区别:post about repaint

这里有一张图片可以帮助您理解它的全部内容: Application running

这是我的代码:

private void drawMatriz(int[][] array, DrawSquare square, int size, AppController contr) {
    Color[] configColors = contr.getArrayOfCollors();
    int posX = 0;
    int posY = 0;
    for (int i = 0; i < array.length; i++) {
        for (int j = 0; j < array[0].length; j++) {
            square.addSquare(posX, posY, size, size, configColors[array[i][j]]);
            posX += size;
        }
        posX = 0;
        posY += size;
    }
    repaint();
}

public AppRun(AppController controller) {
    [...]
    squares = new DrawSquare();
    squares.setBorder(new LineBorder(new Color(0, 0, 0)));
    squares.setBounds(209, 11, 640, 640);
    getContentPane().add(squares);
    squares.setPreferredSize(new Dimension(500, 500));
    squares.setLayout(null);

    drawMatriz(controller.getVector(), squares, (squares.getBounds().width / controller.getVector().length),
            controller);

}

class DrawSquare extends JPanel {
    private static final long serialVersionUID = 1L;
    private static final int PREF_W = 400;
    private static final int PREF_H = PREF_W;
    private List<Rectangle> squares = new ArrayList<Rectangle>();
    private List<Color> colors = new ArrayList<Color>();

    public void addSquare(int x, int y, int width, int height, Color color) {
        Rectangle rect = new Rectangle(x, y, width, height);
        squares.add(rect);
        colors.add(color);
    }

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

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        Graphics2D g2 = (Graphics2D) g;

        for (int i = 0; i < squares.size(); i++) {
            g2.setColor(colors.get(i));
            g2.fill(squares.get(i));
            g2.setColor(Color.BLACK);
            g2.draw(squares.get(i));
        }
    }
}

我没有发现你的 paintComponent 方法有任何问题。

但是您似乎从来没有重置 DrawSquare class 中 ArrayList 的内容, 例如通过对它们调用 clear() 。 因此,在第一次调用 drawMatriz(...) 之后,ArrayList 有 6400 个条目, 第二次调用后他们有 12800 个条目,50 次调用后有 320000 个条目,...

与其在每次调用 paintComponent 时尝试 运行 遍历元素列表,这很耗时,只需更新几个单元格,请考虑使用后备缓冲区,您可以可以直接绘制到 Graphics 上下文。

这意味着当你想更新一个单元格时,你更新后备缓冲区上的单个单元格并重新绘制它,这通常更有效。

此示例还检查了 Graphics 上下文的裁剪范围,并且只绘制在这些范围内实际呈现的图像,这进一步提高了效率。

该示例以 10 像素大小渲染了总共 1、000、000 个单元格 (1000 x 1000),生成了 10, 000x10, 000 的后备缓冲区,所以我们不是在谈论关于少量值。

该示例允许您随机更新单元格(因为它们可以在屏幕外生成,所以我将其限制在前 50x50 个单元格,以便您可以看到它们更新,但实际上它会在整个范围内工作)

import java.awt.BorderLayout;
import java.awt.Color;
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.image.BufferedImage;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.Scrollable;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class Test {

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

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

                int cols = 1000;
                int rows = 1000;
                DrawSquare squares = new DrawSquare(cols, rows);

                JButton btn = new JButton("Random");
                btn.addActionListener(new ActionListener() {
                    @Override
                    public void actionPerformed(ActionEvent e) {
                        int col = (int) (Math.random() * 50);
                        int row = (int) (Math.random() * 50);
                        System.out.println(col + "x" + row);
                        squares.addSquare(col, row, Color.RED);
                        squares.repaint();
                    }
                });

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.add(new JScrollPane(squares));
                frame.add(btn, BorderLayout.SOUTH);
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });

    }

    class DrawSquare extends JPanel implements Scrollable {

        private static final long serialVersionUID = 1L;
        private List<Rectangle> squares = new ArrayList<Rectangle>();
        private List<Color> colors = new ArrayList<Color>();

        private BufferedImage img;
        private int cellSize;
        private int cols, rows;

        public DrawSquare(int cols, int rows) {
            this.cols = cols;
            this.rows = rows;
            cellSize = 10;
            img = new BufferedImage(cols * cellSize, rows * cellSize, BufferedImage.TYPE_INT_RGB);
            System.out.println(cellSize);
            for (int i = 0; i < cols; i++) {
                for (int j = 0; j < rows; j++) {
                    addSquare(i, j, Color.WHITE);
                }
            }
        }

        public void addSquare(int col, int row, Color color) {
            Graphics2D g2d = img.createGraphics();

            int x = col * cellSize;
            int y = row * cellSize;

            Rectangle rect = new Rectangle(x, y, cellSize, cellSize);
            g2d.setColor(color);
            g2d.fill(rect);
            g2d.setColor(Color.BLACK);
            g2d.draw(rect);
            g2d.dispose();
        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(cols * cellSize, rows * cellSize);
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2 = (Graphics2D) g.create();
            Rectangle clip = g2.getClipBounds();

            int width = clip.x + clip.width > img.getWidth() ? img.getWidth() - clip.x : clip.width;
            int height = clip.y + clip.height > img.getHeight()? img.getHeight() - clip.y : clip.height;

            img.getSubimage(clip.x, clip.y, width, height);
            g2.drawImage(img.getSubimage(clip.x, clip.y, width, height), clip.x, clip.y, this);
            g2.dispose();
        }

        @Override
        public Dimension getPreferredScrollableViewportSize() {
            return new Dimension(500, 500);
        }

        @Override
        public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) {
            return 128;
        }

        @Override
        public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) {
            return 128;
        }

        @Override
        public boolean getScrollableTracksViewportWidth() {
            return false;
        }

        @Override
        public boolean getScrollableTracksViewportHeight() {
            return false;
        }
    }

}

我放入 JScrollPane 因为否则它不会在屏幕上呈现,或者如果我减小单元格的大小,将呈现为完全空白(单元格变得太小)