这是在Java / Swing中创建流畅动画的好设计吗?

时间:2012-11-16 20:58:35

标签: java image swing buffer paint

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

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

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

更新

我修改了我的实现,如下面的回复所述:

1)创建一个BufferedImage

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

2)创建一个专门用于执行处理的线程,以确定要绘制的内容。

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

4)创建一个只调用g.drawImage(myBufferedImage,0,0,null);

的新paintComponent()

5)我以前会调用repaint(),触发myThread来执行绘图到myBufferedImage。

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

这让我很难过。

3 个答案:

答案 0 :(得分:3)

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

我前段时间生成了一个“动画序列”库来拍摄一系列图像并将它们叠加在每个图像上,给定每层的“速度”,它会将它们从右向左移调。

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

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

我只希望能告诉你它在我的电脑和Mac上的流畅程度。

enter image description here

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

<强>已更新

这些是我在加载或创建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;

}

答案 1 :(得分: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

答案 2 :(得分:0)

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

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

profiler说大部分时间都在paint组件中。有趣的是没有提到画图像。

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);
}