2

我有一个带有自定义 paintComponent() 实现的 JPanel 子类。它正在以 50fps 的速度刷新。它的大小通常在 500x300 像素范围内。我看到一些闪烁(不是太糟糕但很明显),并且我插入了一些调试代码,表明 Swing/EDT 正在跳过(大概)冗余绘制。我猜这是因为 EDT 没有给 paintComponent() 提供足够的时间来始终完成,或者 EDT 花费了太多时间。

我的想法是我需要获取当前实现 paintComponent() 的代码(这不是很复杂但也不是完全微不足道)并重构它,以便它在自己的线程(或至少不是 EDT)上执行并绘制到图像缓冲区。然后我在我的自定义 JPanel 上实现 paintComponent 并从 ImageBuffer 绘制(渲染)到屏幕(实际上是到 Swing 组件后面的缓冲区,因为我对解决方案的研究让我了解了一些关于 Swing 被(默认情况下)双缓冲的信息,尽管我'对此并不完全清楚)。如果从 ImageBuffer 到 JPanel 的渲染确实比我构建 ImageBuffer 的实现更快,那么我将朝着正确的方向前进。

这对我来说是正确的设计方向吗?

更新

我修改了我的实现,如下面的响应中所述:

1)创建一个缓冲图像

BufferedImage myBufferedImage = new BufferedImage(mySize.width,mySize.height,BufferedImage.TYPE_INT_ARGB)

2) 创建一个专用于执行处理以确定要绘制什么的线程。

3) 将之前在paintComponent() 中的代码移动到由专用线程执行的另一个方法中。在这个方法的最后,调用 repaint();

4)创建一个新的paintComponent(),它只是调用g.drawImage(myBufferedImage,0,0,null);

5) 在我之前调用 repaint() 的地方,触发 myThread 对 myBufferedImage 执行绘图。

正如预测的那样,这是一场灾难。更糟糕的闪烁和迟缓,部分绘画等。我相信这是由于争用读/写 myBufferedImage(如下所述)。因此,我在写入时(在专用绘图线程中)创建了一个锁并锁定 myBufferedImage,并在调用 Graphics2D.drawImage() 之前等待在 paintComponent() 中获得该锁;闪烁和部分绘制消失了——但性能并不比我在paintComponent(因此在EDT)中为绘图进行所有计算时更好(甚至可能更差)。

这让我在这一点上难住了。

4

3 回答 3

3

如果您没有更新整个组件(即只有小区域正在更改),您可以使用JComponent#repaint(Rectangle r)指示已更改的区域。这将导致更新(可能)更小的区域的重绘周期。

前段时间我生成了一个“动画序列”库来拍摄一系列图像并将它们分层放置在每个图像之上,给定每个图层的“速度”,它会将它们从右到左转置。

整个序列将循环 10 秒,其中 1 的速度需要 10 秒才能完成。每一层都以不同的速度移动。

原始图像为 1024x256,序列由 5 个动画层和 2 个静态层设计...

我只希望我能向您展示这在我的 PC 和 Mac 上播放的流畅度。

在此处输入图像描述

我必须克服的唯一重要问题是确保图像与屏幕设备颜色模型兼容。

更新

这些是我在加载或创建 s 时使用的一些实用程序类BufferedImage,尤其是对于动画。确保颜色模型与屏幕使用的颜色模型相同,这将使它们更快地更新/重绘

public static BufferedImage loadCompatibleImage(URL resource) {

    BufferedImage image = null;

    try {
        image = ImageIO.read(resource);
    } catch (IOException ex) {
    }

    return image == null ? null : toCompatibleImage(image);

}

public static BufferedImage toCompatibleImage(BufferedImage image) {

    if (image.getColorModel().equals(getGraphicsConfiguration().getColorModel())) {

        return image;

    }

    BufferedImage compatibleImage =
            getGraphicsConfiguration().createCompatibleImage(
            image.getWidth(), image.getHeight(),
            image.getTransparency());

    Graphics g = compatibleImage.getGraphics();
    g.drawImage(image, 0, 0, null);
    g.dispose();

    return compatibleImage;

}


public static GraphicsConfiguration getGraphicsConfiguration() {

    return GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration();

}

// Check out java.awt.Transparency for valid values
public static BufferedImage createCompatibleImage(int width, int height, int transparency) {

    BufferedImage image = getGraphicsConfiguration().createCompatibleImage(width, height, transparency);
    image.coerceData(true);
    return image;

}
于 2012-11-16T21:33:54.230 回答
1

我认为这是您正在寻找有关双缓冲信息的内容:

http://docs.oracle.com/javase/tutorial/extra/fullscreen/doublebuf.html

如果您无法访问我不完全确定可以访问的底层缓冲区,您可以使用 setDoubleBuffered(false) 关闭双缓冲。

我认为您不能安全地从另一个线程绘制图像,因为当 EDT 在重绘时读取相同的图像时,您将进入写入图像的线程。如果您在它们之间共享图像,您将遇到必须同步的多线程问题。如果你同步,那么你的性能不会很好。如果您在每一帧都实例化一个新图像,那么您的内存将会猛增,而 GC 会得到您。您可能能够实例化 10 帧并使写入远离读取或类似的东西,但无论哪种方式,要使其高效和正确都非常棘手。

我的建议是从 EDT 进行所有绘图,并找出在另一个不涉及 ImageBuffer 共享的线程上进行计算(渲染)的方法。

更新虽然它用于全屏。那里的建议也适用于窗口模式:“将绘图代码与渲染循环分开,以便您可以在全屏独占模式和窗口模式下完全操作。” 请参阅此http://docs.oracle.com/javase/tutorial/extra/fullscreen/rendering.html

于 2012-11-17T17:11:18.727 回答
0

我在尝试流畅地绘画时遇到了类似的问题。

尝试运行它,看看它有多流畅(对我来说它很流畅)。

profiler 说大部分时间都在油漆组件中。有趣的是没有提到绘制图像。

import java.awt.*;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import javax.swing.*;
class P extends JPanel {
    void init(Dimension d) {
        GraphicsConfiguration gc=getGraphicsConfiguration();
        bi=gc.createCompatibleImage(d.width,d.height);
    }
    @Override public void paintComponent(Graphics g) {
        //super.paintComponent(g);
        if(bi!=null)
            g.drawImage(bi,0,0,null);
    }
    BufferedImage bi;
}
public class So13424311 {
    So13424311() {
        p=new P();
    }
    void createAndShowGUI() {
        Frame f=new JFrame("so13424311");
        // f.setUndecorated(true);
        f.addWindowListener(new WindowAdapter() {
            public void windowClosing(WindowEvent e) {
                System.exit(0);
            }
        });
        f.add(p);
        p.init(d);
        p.setSize(d);
        p.setPreferredSize(d);
        f.pack();
        // if(moveToSecondaryDisplay)
        // moveToSecondaryDisplay(f);
        f.setVisible(true);
    }
    void run() {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                createAndShowGUI();
            }
        });
        Timer t=new Timer(20,new ActionListener() {
            @Override public void actionPerformed(ActionEvent e) {
                Graphics g=p.bi.getGraphics();
                Color old=g.getColor();
                g.fillRect(0,0,d.width,d.height);
                g.setColor(Color.red);
                g.fillRect(n%(d.width/2),n%(d.height/2),20,20);
                g.setColor(Color.green);
                g.fillRect(n%(d.width/2)+20,n%(d.height/2),20,20);
                g.setColor(Color.blue);
                g.fillRect(n%(d.width/2),n%(d.height/2)+20,20,20);
                g.setColor(Color.yellow);
                g.fillRect(n%(d.width/2)+20,n%(d.height/2)+20,20,20);
                g.setColor(old);
                g.dispose();
                p.repaint();
                n++;
            }
            int n;
        });
        t.start();
    }
    public static void main(String[] args) {
        new So13424311().run();
    }
    final P p;
    Dimension d=new Dimension(500,300);
}
于 2012-11-20T02:32:33.587 回答