簡體   English   中英

調整框架大小時,圖像渲染時間過長

[英]Image taking ridiculously long to render when resizing frame

我目前正在實現紙牌游戲“Munchkin”(供我和我的朋友在大流行期間玩)並在啟動器上顯示 Munchkin 標志。 該代碼實際上很好地將徽標設置在它應該在的位置。

但是,我在調整 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();
        });
        
    }
    
    
}

我還嘗試在loadComponents()中單獨加載內部 ImageIcon 資源本身以減少加載時間,但在我看來,這樣做似乎需要更長的時間來更新。 我認為這可能與圖像的原始尺寸有關,因為它的分辨率為4961 x 1658 px 我還嘗試了不同的縮放算法,並且使用Image.SCALE_FAST並沒有明顯加快這個過程。

最讓我困惑的是,這個過程是在高端硬件(3900X + 2080Ti)上執行的,這意味着中端或低端 PC 可能需要更長的時間來加載和更新它,這讓我更加荒謬. 如果沒有其他解決方案,我認為即使是基本的 2D 游戲在如今的硬件上仍然落后,而他們顯然沒有。

加載圖像以調整我丟失的大小時是否有任何最佳做法,或者我一般如何解決此問題?

所以,我重寫了你的部分代碼。

如果您運行舊代碼並添加一些輸出,在 output 中,您會看到實際問題出在哪里:實際完成調整大小的次數。 第二個問題如下:如果您將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 通常就足夠了。 其他一切只會使您的軟件膨脹並使其變慢。 在很多情況下,建議使用矢量圖形。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM