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
因为否则它不会在屏幕上呈现,或者如果我减小单元格的大小,将呈现为完全空白(单元格变得太小)
我正在开发一个模拟元胞自动机的应用程序。碰巧我需要非常快地(每 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
因为否则它不会在屏幕上呈现,或者如果我减小单元格的大小,将呈现为完全空白(单元格变得太小)