只绘制应该在屏幕上的节点?

Posted

技术标签:

【中文标题】只绘制应该在屏幕上的节点?【英文标题】:Draw only nodes that should be on screen? 【发布时间】:2019-07-28 18:24:01 【问题描述】:

我正在使用场景图(不是画布)在 JavaFX 中进行模拟,并且在仅在屏幕上绘制我需要的内容时遇到问题。

这个模拟中有超过 1000 万个节点,但用户只需要在屏幕上同时看到一小部分节点(最多 160,000 个节点)。我关心的所有节点都是400x400ImageViews

每个节点都是Group(节点块)的成员,该Group(节点块)包含大约 40,000 个节点,因此一次需要显示 4 个或更少的这些“节点块”。为了显示这些“节点块”,它们被添加到静态Pane 中,并且该窗格位于根节点中,即Group

所以我从第一个父母到最后一个孩子的图表如下所示:

根节点Group\显示Pane\(许多)节点块Group\ImageViews

由于显示窗格根据用户输入不断移动(平移和重新缩放),并且节点太多,因此应用程序无法以我想要的速度运行。 JavaFX 无法同时跟踪超过 1000 万个节点是有道理的,所以我的解决方案是从显示窗格中删除所有“节点块”;将它们保存在哈希图中,直到我需要绘制它们。

每个“节点块”都将其LayoutXLayoutYs 设置为均匀分布在网格中的显示窗格中,如下所示:

在本例中,我需要抓取并显示“节点块”7、8、12 和 13,因为这是用户所看到的。

这是手动添加“节点块”0 的屏幕截图。黄绿色是放置“节点块”1、5 和 6 的位置。

我的问题是:由于“节点块”在需要之前不会添加到显示窗格中,因此我无法参考用户看到的显示窗格中不断变化的部分的布局边界,所以我这样做了不知道需要显示哪些“节点块”。

有没有简单的方法来解决这个问题?还是我走错了路? (或两者兼有)谢谢。

【问题讨论】:

我一半的想法说“我理解这个问题”,然后另一半说..“不,我还不清楚!!”。您能否提供到目前为止所做的屏幕截图,以便我了解您的具体要求:) @SaiDandem 没问题,已添加截图。谢谢。 我假设您正在从您拥有的一堆图像文件中构建 ImageView。因此,作为第一步,您是为每个 imageView 构造加载图像还是将它们保存在缓存中? @SaiDandem 我将所有图像保存在缓存中 【参考方案1】:

注意:这不是您确切问题的答案。

我不知道为每个图像创建 ImageView 的原因是什么。相反,我会为每个 nodeChunk(160000 个图像)绘制一个图像,并为这个 nodeChunk 创建一个 ImageView。

从功能上讲,我看不出这两种方法之间有什么区别(您可以告诉我为什么要使用每个图像视图)。但就性能而言,这种方法非常快速和流畅。

请在下面找到我所说的演示。

import javafx.application.Application;
import javafx.embed.swing.SwingFXUtils;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.ScrollPane;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.GridPane;
import javafx.stage.Stage;

    import java.awt.*;
    import java.awt.image.BufferedImage;
    import java.io.IOException;
    import java.security.SecureRandom;
    import java.util.HashMap;
    import java.util.Map;

    /**
     * Combining 4,000,000 images
     */
    public class ImageLoadingDemo extends Application 

        SecureRandom rnd = new SecureRandom();
        // I created 5 images of dimension 1 x 1  each with new color
        String[] images = "1.png", "2.png", "3.png", "4.png", "5.png";
        Map<Integer, BufferedImage> buffImages = new HashMap<>();
        Map<Integer, Image> fxImages = new HashMap<>();

        @Override
        public void start(Stage primaryStage) throws IOException 
            ScrollPane root = new ScrollPane();
            root.setPannable(true);
            Scene scene = new Scene(root, 800, 800);
            primaryStage.setScene(scene);
            primaryStage.show();

            root.setContent(approach1());  // Fast & smooth rendering

            // Your approach of creating ImageViews
            // root.setContent(approach2());  // Very very very very very slow rendering
        

        private Node approach1()
            // Creating 5 x 5 nodeChunk grid
            GridPane grid = new GridPane();
            for (int i = 0; i < 5; i++) 
                for (int j = 0; j < 5; j++) 
                    ImageView nodeChunk = new ImageView(SwingFXUtils.toFXImage(createChunk(), null));
                    grid.add(nodeChunk, i, j);
                
            
            return grid;
        

        private BufferedImage createChunk() 
            // Combining 160000 1px images into a single image of 400 x 400
            BufferedImage chunck = new BufferedImage(400, 400, BufferedImage.TYPE_INT_ARGB);
            Graphics2D paint;
            paint = chunck.createGraphics();
            paint.setPaint(Color.WHITE);
            paint.fillRect(0, 0, chunck.getWidth(), chunck.getHeight());
            paint.setBackground(Color.WHITE);
            for (int i = 0; i < 400; i++) 
                for (int j = 0; j < 400; j++) 
                    int index = rnd.nextInt(5); // Picking a random image
                    BufferedImage buffImage = buffImages.get(index);
                    if (buffImage == null) 
                        Image image = new Image(ImageLoadingDemo.class.getResourceAsStream(images[index]));
                        buffImages.put(index, SwingFXUtils.fromFXImage(image, null));
                    
                    paint.drawImage(buffImage, i, j, null);
                
            
            return chunck;
        

        private Node approach2()
            GridPane grid = new GridPane();
            for (int i = 0; i < 5; i++) 
                for (int j = 0; j < 5; j++) 
                    grid.add(createGroup(), i, j);
                
            
            return grid;
        
        private Group createGroup() 
            GridPane grid = new GridPane();
            for (int i = 0; i < 400; i++) 
                for (int j = 0; j < 400; j++) 
                    int index = rnd.nextInt(5);
                    Image fxImage = fxImages.get(index);
                    if (fxImage == null) 
                        fxImage = new Image(ImageLoadingDemo.class.getResourceAsStream(images[index]));
                        fxImages.put(index, fxImage);
                    
                    grid.add(new ImageView(fxImage), i, j);
                
            
            return new Group(grid);
        



        public static void main(String[] args) 
            Application.launch(args);
        
    

【讨论】:

感谢您的回答!我对每个图像都使用 ImageViews,因为它们正在被实时操作(移动、删除、重新着色、拆分等)。我不完全确定我是否可以摆脱使用 ImageViews 我会尝试为任何操作重建适当的块。只是出于好奇,当您知道某些事情发生了变化时,您想做什么。?您是否获得特定的 imageView 并更新其图像? 这取决于 imageview 的变化。有时,imageview 会更改其图像,但大多数情况下,imageview 会根据用户点击的内容重新定位或重新着色 所以是的,每个图像视图都需要自己选择和区分【参考方案2】:

我最终为每个“节点块”创建了一个矩形,将不透明度设置为零,然后检查与用户正在查看的显示窗格部分是否发生冲突。当与矩形发生碰撞时,相应的“节点块”会被添加到显示窗格中。

【讨论】:

以上是关于只绘制应该在屏幕上的节点?的主要内容,如果未能解决你的问题,请参考以下文章

要在屏幕上绘制一些小图块,我应该使用 QQuickItem 还是 QQuickPaintedItem?

在屏幕外绘制 uiview

在不使用点精灵的情况下在屏幕上的不同点渲染多个纹理

Android 上的 openGl es 在右上角显示一个 Square

壁纸不适合设备屏幕

操作系统如何在屏幕上绘制窗口?