java—调整帧大小时渲染图像的时间长得离谱

u1ehiz5o  于 2021-06-27  发布在  Java
关注(0)|答案(1)|浏览(365)

我目前正在开发一个纸牌游戏“芒奇金”(我和我的朋友在流感大流行期间玩),并在发射器上显示芒奇金的标志。这个代码实际上可以很好地将logo设置在它应该位于的位置。
不过,我在调整jframe事件的大小时遇到了一个问题:最初,当将帧调整到最小大小时,加载图像大约需要5秒钟,只有在这之后,帧中包含的内容才会更新。在重新加载图像之前,内容仅在屏幕内部分可见,例如按钮仍在框架外。
反复调整大小后,ui的加载时间似乎呈指数增长(可能是因为图像的加载和缩放是针对缩放前后大小之间的多个帧大小执行的)。另外,当新生成帧时,图像需要可感知的加载时间(如0.5秒)。
为了更清楚地说明我的意思,以下是最初和调整大小后不久的样子:
之前:

之后:

为了让您对以下代码有所了解,我们将执行所有ui操作:

package de.munchkin.frontend;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Image;
import java.awt.Toolkit;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;

import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;

public class MunchkinLauncher extends JFrame{

    private static final long serialVersionUID = 1598309638008663371L;

    private Toolkit toolkit = Toolkit.getDefaultToolkit();
    private Dimension screenDim = toolkit.getScreenSize();
    private JPanel contentPane;
    private JButton btnHostGame, btnJoinGame;
    private JLabel imageLabel;

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

    public MunchkinLauncher() {
        setTitle("Munchkin Launcher");
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setLocation(screenDim.width / 4, screenDim.height / 4);
        setSize(screenDim.width / 2, screenDim.height / 2);
        setMinimumSize(new Dimension(700, 387));

        contentPane = new JPanel(null);
        contentPane.setLayout(null);
        setLayout(null);
        setContentPane(contentPane);
        contentPane.setBackground(new Color(253, 205, 136));

        setVisible(true);

        loadComponents();
        loadBounds();
        addActionListeners();

    }

    private void loadComponents() {

        // Load header image
        imageLabel = new JLabel("");

        btnHostGame = new JButton("Host Game");

        btnJoinGame = new JButton("Join Game");

    }

    private void loadBounds() {

        int width = contentPane.getSize().width;
        int height = contentPane.getSize().height;

        btnHostGame.setBounds(width/2 - 300, height * 2 / 3, 250, 100);
        contentPane.add(btnHostGame);

        btnJoinGame.setBounds(width/2 + 50, height * 2 / 3, 250, 100);
        contentPane.add(btnJoinGame);

        imageLabel.setIcon(new ImageIcon(new ImageIcon(this.getClass().getResource("/Munchkin_logo.jpg"))
                .getImage().getScaledInstance(width - 40, height * 2 / 3 - 40, Image.SCALE_DEFAULT)));
        imageLabel.setBounds(20, 0, width - 40, height * 2 / 3);
        contentPane.add(imageLabel);

        revalidate();
        repaint();

    }

    private void addActionListeners() {

        addComponentListener(new ComponentAdapter() {

            @Override
            public void componentResized(ComponentEvent e) {
                super.componentResized(e);
                loadBounds();
            }

        });

        btnHostGame.addActionListener(e -> {
            new MatchCreation();
            dispose();
        });

        btnJoinGame.addActionListener(e -> {
            new MatchJoin();
            dispose();
        });

    }

}

我还尝试在中单独加载内部imageicon资源本身 loadComponents() 为了减少加载时间,但是在我看来,这样做似乎需要更长的时间来更新。我认为这可能与图像的原始大小有关,因为它的分辨率为 4961 x 1658 px . 我还尝试了不同的缩放算法,还使用 Image.SCALE_FAST 并没有明显加快进程。
最让我困惑的是,这个过程是在高端硬件(3900x+2080ti)上执行的,这意味着中端或低端pc可能需要更长的时间来加载和更新它,这让我觉得更荒谬。如果没有其他的解决方案,我想即使是基本的2d游戏在现在的硬件上也会落后,而他们显然没有。
在加载图像以调整大小时,是否有我丢失的最佳做法,或者通常如何解决此问题?

hlswsv35

hlswsv351#

所以,我重新编写了你的部分代码。
如果您运行旧代码并添加了一些输出,那么在输出中您将看到实际问题所在:实际调整大小的次数。第二个问题是:如果你使用 getScaledInstance() 一起 new ImageIcon() ,imageicon初始化将等待图片完全创建/绘制完成。这将阻止所有其他ui操作,直到完成。
另一个小问题是您将控件重新添加到它们的父控件,这也可能会导致大量不必要的重新布局和重新绘制。
更简单的方法是使用/编写imagepanel。graphics.drawimage()方法有一个imageobserver。无论何时调用drawimage(),都可以异步(非阻塞)绘制大型图像,因此不会有如此可怕的加载时间。另外,它将自己进行验证等,所以您不需要对此进行任何调用,这可能会导致不必要的布局和重新绘制。
在下面的示例中,我还包含了一些附加提示,如使用“final”、“early initialization”和“fail fast fail early”。
你看,解决办法是多么简单:
例子:

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Image;
import java.awt.Toolkit;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.net.URL;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;

public class MunchkinLauncher extends JFrame {

    private static final long serialVersionUID = 1598309638008663371L;

    private final Toolkit       toolkit     = Toolkit.getDefaultToolkit();
    private final Dimension     screenDim   = toolkit.getScreenSize();
    private final JPanel        contentPane = new JPanel(null);
    private final JButton       btnHostGame = new JButton("Host Game");     // make as many 'final' as possible. earlier initialization saves you from a lot of problems when it comes to inheritance and events
    private final JButton       btnJoinGame = new JButton("Join Game");
    private final ImagePanel    imageLabel  = new ImagePanel();

    private final Image backgroundImageicon; // pardon my camelCase, imo composite logic is more important

    public static void main(final String[] args) {
        new MunchkinLauncher().setVisible(true);
    }

    public MunchkinLauncher() {
        setDefaultCloseOperation(EXIT_ON_CLOSE); // should always be first, you never know what exception happens next and this might keep a lot of JVMs running if due to exception a visual jframe is not disposed properly
        setTitle("Munchkin Launcher");
        setLocation(screenDim.width / 4, screenDim.height / 4);
        setSize(screenDim.width / 2, screenDim.height / 2);
        setMinimumSize(new Dimension(700, 387));
        setLayout(null);

        contentPane.setLayout(null);
        contentPane.setBackground(new Color(253, 205, 136));
        contentPane.add(imageLabel);
        contentPane.add(btnHostGame);
        contentPane.add(btnJoinGame);
        setContentPane(contentPane);

        loadComponents();

        backgroundImageicon = loadBgImage();
        imageLabel.setImage(backgroundImageicon);

        loadBounds();

        addActionListeners();

        //      setVisible(true); // should be last, unless you need something specific to happen durint init
        // and ACTUALLY it should be used outside the CTOR so caller has more control
    }

    private Image loadBgImage() {
        final long nowMs = System.currentTimeMillis();
        final URL res = this.getClass().getResource("/Munchkin_logo.jpg");

        // load Image
        //      final ImageIcon ret1 = new ImageIcon(res); // is not as good, we eventually only need an Image, we do not need the wrapper that ImageIcon provides
        //      final BufferedImage ret2 = ImageIO.read(res); // is better, but gives us BufferedImage, which is also more than we need. plus throws IOException
        final Image ret3 = Toolkit.getDefaultToolkit().getImage(res); // best way. If behaviour plays a role: Loads the image the same way that ImageIcon CTOR would.

        final long durMs = System.currentTimeMillis() - nowMs;
        System.out.println("Loading BG Image took " + durMs + "ms.");

        return ret3;
    }

    private void loadComponents() {}

    void loadBounds() {
        final int width = contentPane.getSize().width;
        final int height = contentPane.getSize().height;
        btnHostGame.setBounds(width / 2 - 300, height * 2 / 3, 250, 100);
        btnJoinGame.setBounds(width / 2 + 50, height * 2 / 3, 250, 100);
        imageLabel.setBounds(20, 0, width - 40, height * 2 / 3);
    }

    private void addActionListeners() {
        addComponentListener(new ComponentAdapter() {
            @Override public void componentResized(final ComponentEvent e) {
                super.componentResized(e);
                loadBounds(); // this access needs protected loadBounds() or synthetic accessor method
            }
        });
        btnHostGame.addActionListener(e -> {
            System.out.println("MunchkinLauncher.addActionListeners(1)");
            //          new MatchCreation();
            dispose();
        });
        btnJoinGame.addActionListener(e -> {
            System.out.println("MunchkinLauncher.addActionListeners(2)");
            //          new MatchJoin();
            dispose();
        });
    }
}

以及图像面板:

import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.RenderingHints;

import javax.swing.JPanel;

public class ImagePanel extends JPanel {

    private static final long serialVersionUID = 6196514831308649310L;

    private Image mImage;

    public ImagePanel(final Image pImage) {
        mImage = pImage;
    }
    public ImagePanel() {}

    public Image getImage() {
        return mImage;
    }
    public void setImage(final Image pImage) {
        mImage = pImage;
        repaint();
    }

    @Override protected void paintComponent(final Graphics pG) {
        if (mImage == null) return;

        final Graphics2D g = (Graphics2D) pG;
        g.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);// can add more
        g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        g.drawImage(mImage, 0, 0, getWidth(), getHeight(), this);
    }
}

编辑:另一方面,您应该将嵌入的图像大小限制为合理的大小,例如,如果在全屏显示中使用4k。作为一个窗口控件显示,720p通常就足够了。其他的一切只会让你的软件膨胀,让它变慢。在很多情况下,建议使用矢量图形。

相关问题