简体   繁体   English

这是用于在Java / Swing中创建平滑动画的良好设计吗?

[英]Is this a good design for creating smooth animations in Java/Swing?

I have a JPanel subclass with custom paintComponent() implementation. 我有一个带有自定义paintComponent()实现的JPanel子类。 It is being refreshed at 50fps. 它以50fps刷新。 It is typically in the range of 500x300 pixels in size. 它的大小通常在500x300像素范围内。 I'm seeing some flickering (not too bad but noticeable) and I've inserted some debug code that indicates that Swing/EDT is skipping (presumably) redundant painting. 我看到了一些闪烁(不是太糟,但是很明显),并且插入了一些调试代码,这些代码指示Swing / EDT正在跳过(大概是)多余的绘画。 I am guessing that's because the EDT is not giving enough time for paintComponent() to always finish or it is taking too much time on the EDT. 我猜这是因为EDT没有给足够的时间使paintComponent()始终完成,或者在EDT上花费了太多时间。

My thinking is that I need to take the code currently implementing paintComponent() (which is not very complex but not completely trivial either) and refactor it so it is executed on its own Thread (or at least not the EDT) and draws to an ImageBuffer. 我的想法是,我需要采用当前实现paintComponent()的代码(这不是很复杂,但也不是完全不重要的)并对其进行重构,以便在其自己的线程(或至少不是EDT)上执行并绘制一个ImageBuffer的。 I then implement paintComponent on my custom JPanel and draw (render) from the ImageBuffer to the screen (actually to the buffer behind Swing components as my research into the solution led me some information about Swing being (by default) double-buffered, though I'm not completely clear on that). 然后,我在自定义的JPanel上实现paintComponent,并从ImageBuffer绘制(渲染)到屏幕(实际上是绘制到Swing组件后面的缓冲区),因为对解决方案的研究使我了解到有关Swing的信息(默认情况下为双缓冲),尽管我(对此尚不完全清楚)。 If it is true that the rendering from the ImageBuffer to the JPanel is faster than my implementation that constructs the ImageBuffer then I will be going in the right direction. 如果确实从ImageBuffer到JPanel的渲染比构造ImageBuffer的实现更快,那么我将朝着正确的方向前进。

Is this the proper design direction for me to take? 这是我要接受的正确设计方向吗?

UPDATE UPDATE

I modified my implementation as discussed in reponses below: 我修改了我的实现,如以下答复中所述:

1) Create a BufferedImage 1)创建一个BufferedImage

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

2) Create a Thread dedicated to peforming the processing to determine what is to be drawn. 2)创建一个专用于执行处理以确定要绘制的线程。

3) Move the code previously in paintComponent() to another method that is executed by the dedicated Thread. 3)将先前在paintComponent()中的代码移动到由专用线程执行的另一个方法。 At the end of this method, call repaint(); 在此方法的最后,调用repaint();。

4) Create a new paintComponent() that simply calls g.drawImage(myBufferedImage,0,0,null); 4)创建一个新的paintComponent(),它只需调用g.drawImage(myBufferedImage,0,0,null);

5) Where I previously would call repaint(), trigger myThread to perform the drawing to myBufferedImage. 5)在我以前调用repaint()的地方,触发myThread对myBufferedImage执行绘制。

This was a disaster, as predicted. 正如预料的那样,这是一场灾难。 Much worse flickering and sluggishness, partial paints, etc. I believe this was due to contention reading/writing myBufferedImage (as mentioned below). 更糟糕的闪烁和缓慢,局部绘制等。我相信这是由于争用读取/写入myBufferedImage(如下所述)。 So I then created a lock and lock myBufferedImage when I am writing to it (in the dedicated drawing Thread) and wait to get that lock in paintComponent() before calling Graphics2D.drawImage(); 因此,当我向其写入时(在专用绘图线程中),我创建了一个锁并锁定myBufferedImage,并在调用Graphics2D.drawImage()之前等待在paintComponent()中获得该锁。 The flicker and partial paints go away - but performance is no better (maybe even worse) than when I was doing all the calculations for the drawing in paintComponent (and therefore in the EDT). 闪烁的油漆和部分油漆消失了-但是性能并没有比我在paintComponent(因此在EDT)中为图纸进行所有计算时更好(甚至更差)。

This has me stumped at this point. 在这一点上,我很困惑。

If you're not updating the entire component (ie only small areas are changing), you could use JComponent#repaint(Rectangle r) indicating the areas that have changed. 如果您不更新整个组件(即仅更改了很小的区域),则可以使用JComponent#repaint(Rectangle r)指示已更改的区域。 This will result in a repaint cycle that updates (potentially) a much smaller area. 这将导致重新绘制周期,从而(可能)更新更小的区域。

I generated a "animated sequence" library some time ago to take a series of images and layer them ontop of each, given a "speed" of each layer, it would transpose them from right to left. 不久前,我生成了一个“动画序列”库,以拍摄一系列图像并将它们分层放置在每个图像的顶部,如果每一层都具有“速度”,它将从右到左进行转置。

The whole sequence would cycle for 10 seconds, where a speed of 1 would take take 10 seconds to complete. 整个序列将循环10秒钟,而速度1则需要10秒钟才能完成。 Each layer is moving at difference speeds. 每层都以不同的速度移动。

The original images where 1024x256, and the sequence was devised of 5 animated layers and 2 static layers... 原始图像的分辨率为1024x256,序列由5个动画层和2个静态层组成...

I only wish I could show you how smooth this plays on my PC and Mac. 我只希望能告诉您在我的PC和Mac上播放效果如何。

在此处输入图片说明

The only signification issue I had to over come was making sure that the images where compatible with the screen devices color model. 我唯一要解决的标志性问题是确保图像与屏幕设备的颜色模型兼容。

UPDATED 更新

These are some utility classes I use when loading or creating BufferedImage s, especially for animation. 这些是我在加载或创建BufferedImage时使用的一些实用程序类,尤其是对于动画。 The make sure that the colour models are the same as those used by the screens, which will make them faster to update/repaint 确保颜色模型与屏幕使用的颜色模型相同,这将使它们更快地更新/重新绘制

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;

}

I think this is what you're looking for on information about double buffering: 我认为这是您在寻找有关双重缓冲的信息的目的:

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

You could turn off double buffering with setDoubleBuffered(false) if you can't get access to the underlying buffer which I'm not entirely sure you can. 如果无法完全访问底层缓冲区,则可以使用setDoubleBuffered(false)关闭双缓冲。

I don't think you can safely draw on an image from another thread because you'll get into the thread writing to the image while the EDT is reading that same image as it redraws. 我认为您不能从另一个线程安全地绘制图像,因为您会在EDT读取与重新绘制相同的图像时进入写入图像的线程。 If you share an image between them you're going to have multi-threading issues that you'll have to synchronize. 如果您在它们之间共享映像,那么您将不得不同步多线程问题。 If you synchronize then you're performance isn't going to be very good. 如果进行同步,那么性能将不会很好。 If you instantiate a new image every frame you're memory is going to skyrocket and GC will get you. 如果您实例化一个新图像,那么您存储的每一帧都会急剧上升,GC会帮助您。 You may be able to instantiate 10 frames and keep the writing away from the reading or something like that, but either way this is going to very tricky to make it performant and correct. 您可能能够实例化10帧并使文字远离阅读或类似内容,但是无论哪种方式,要使其性能和正确性都非常棘手。

My suggestion is to do all drawing from EDT, and figure out a way to do the calculations (rendering) on another thread that doesn't involve ImageBuffer sharing. 我的建议是从EDT进行所有绘制,并找出一种方法在另一个不涉及ImageBuffer共享的线程上进行计算(渲染)。

Update While it is used for fullscreen. 更新用于全屏显示时。 The suggestions in there apply to windowed mode as well: "Separate your drawing code from your rendering loop, so that you can operate fully under both full-screen exclusive and windowed modes." 那里的建议也适用于窗口模式:“将图形代码与渲染循环分开,以便您可以在全屏独占模式和窗口模式下完全操作。” See this http://docs.oracle.com/javase/tutorial/extra/fullscreen/rendering.html 看到这个http://docs.oracle.com/javase/tutorial/extra/fullscreen/rendering.html

i've has similar problems trying to paint smoothly. 我在尝试平滑绘画时也遇到了类似的问题。

try running this and see how smooth it is (its smooth for me). 尝试运行此命令,看看它有多平滑(对我而言它很平滑)。

profiler says most of the time is in paint component. 分析器说,大部分时间都在涂料组件中。 interestingly draw image is not mentioned. 有趣的是,没有提到绘制图像。

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM