当前位置: 首页 > 知识库问答 >
问题:

JavaFX图像加载在后台和线程

苍宝
2023-03-14

我原以为这是一个简单的问题,但我很难找到答案。我有一个与JavaFX场景对象关联的ImageView对象,我想从磁盘加载大图像,并使用ImageView一个接一个地显示它们。我一直在努力寻找一种好方法来反复检查图像对象,当它在后台加载完成时,将其设置为ImageView,然后开始加载新的图像对象。我提出的代码(如下)有时有效,有时无效。我很确定我在JavaFX和线程方面遇到了问题。它有时加载第一个图像,然后停止。变量“processing”是类中的布尔实例变量。

在后台加载JavaFX中的图像并在加载完成后将其设置为ImageView的正确方法是什么?

public void start(Stage primaryStage) {

       ... 

       ImageView view = new ImageView();
       ((Group)scene.getRoot()).getChildren().add(view);

       ... 

        Thread th = new Thread(new Thread() {
            public void run() {

                while(true) {
                    if (!processing) {

                        processing = true;         
                        String filename = files[count].toURI().toString();
                        Image image = new Image(filename,true);

                        image.progressProperty().addListener(new ChangeListener<Number>() {
                            @Override public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number progress) {
                                if ((Double) progress == 1.0) {
                                    if (! image.isError()) {
                                        view.setImage(image);
                                    }
                                    count++;
                                    if (count == files.length) {
                                        count = 0;
                                    }
                                    processing = false;
                                }
                            }
                        });
                    }
                }
            }
        });
    }

共有1个答案

杨超
2023-03-14

实际上,我认为可能有一种更好的通用方法来满足您的应用程序的任何需求,而不是您试图使用的方法,但下面是我实现您描述的方法的最佳答案。

创建一个有边界的BlockingQueue,以便在加载图像时保存图像。队列的大小可能需要一些调整:太小,您将没有任何“缓冲区”(因此您将无法利用比平均速度更快的任何缓冲区),太大,您可能会消耗太多内存。BlockingQueue允许您从多个线程安全地访问它。

创建一个线程,该线程只需同步循环和加载每个图像,即在加载每个图像时线程会阻塞,并将其存放在阻塞队列中。

由于您想尝试每FX帧最多显示一次图像(即60fps),请使用AnimationTimer。这有一个句柄方法,在FX应用程序线程上的每个帧渲染上都调用它,所以您可以将它实现为轮询()BlockingQueue,如果图像可用,请在ImageView.

import java.io.File;
import java.net.MalformedURLException;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import javafx.animation.AnimationTimer;
import javafx.animation.PauseTransition;
import javafx.application.Application;
import javafx.concurrent.Task;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.stage.DirectoryChooser;
import javafx.stage.Stage;


public class ScreenSaver extends Application {

    @Override
    public void start(Stage primaryStage) {

        BorderPane root = new BorderPane();

        Button startButton = new Button("Choose image directory...");
        startButton.setOnAction(e -> {
            DirectoryChooser chooser= new DirectoryChooser();
            File dir = chooser.showDialog(primaryStage);
            if (dir != null) {
                File[] files = Stream.of(dir.listFiles()).filter(file -> {
                    String fName = file.getAbsolutePath().toLowerCase();
                    return fName.endsWith(".jpeg") | fName.endsWith(".jpg") | fName.endsWith(".png");
                }).collect(Collectors.toList()).toArray(new File[0]);
                root.setCenter(createScreenSaver(files));
            }
        });

        root.setCenter(new StackPane(startButton));

        primaryStage.setScene(new Scene(root, 800, 800));
        primaryStage.show();
    }

    private Parent createScreenSaver(File[] files) {
        ImageView imageView = new ImageView();
        Pane pane = new Pane(imageView);
        imageView.fitWidthProperty().bind(pane.widthProperty());
        imageView.fitHeightProperty().bind(pane.heightProperty());
        imageView.setPreserveRatio(true);

        Executor exec = Executors.newCachedThreadPool(runnable -> {
            Thread t = new Thread(runnable);
            t.setDaemon(true);
            return t ;
        });

        final int imageBufferSize = 5 ;
        BlockingQueue<Image> imageQueue = new ArrayBlockingQueue<Image>(imageBufferSize);

        exec.execute(() -> {
            int index = 0 ;
            try {
                while (true) {
                    Image image = new Image(files[index].toURI().toURL().toExternalForm(), false);
                    imageQueue.put(image);
                    index = (index + 1) % files.length ;
                }
            } catch (MalformedURLException e) {
                throw new RuntimeException(e);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        });

        // This will show a new image every single rendering frame, if one is available: 
        AnimationTimer timer = new AnimationTimer() {

            @Override
            public void handle(long now) {
                Image image = imageQueue.poll();
                if (image != null) {
                    imageView.setImage(image);
                }
            }
        };
        timer.start();

        // This wait for an image to become available, then show it for a fixed amount of time,
        // before attempting to load the next one:

//        Duration displayTime = Duration.seconds(1);
//        PauseTransition pause = new PauseTransition(displayTime);
//        pause.setOnFinished(e -> exec.execute(createImageDisplayTask(pause, imageQueue, imageView)));
//        exec.execute(createImageDisplayTask(pause, imageQueue, imageView));

        return pane ;
    }

    private Task<Image> createImageDisplayTask(PauseTransition pause, BlockingQueue<Image> imageQueue, ImageView imageView) {
        Task<Image> imageDisplayTask = new Task<Image>() {
            @Override
            public Image call() throws InterruptedException {
                return imageQueue.take();
            }
        };
        imageDisplayTask.setOnSucceeded(e -> {
            imageView.setImage(imageDisplayTask.getValue());
            pause.playFromStart();
        });
        return imageDisplayTask ;
    }

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

 类似资料:
  • 我目前正在尝试为我的程序创建一个启动屏幕,因为启动需要一些时间。问题是创建GUI需要一段时间(创建对话、更新表格等)。而且我不能将GUI创建移动到后台线程(如“Task”类),因为我会得到“Not on FXApplication thread”异常。我尝试使用: 以及任务的“调用”方法: 当我在Swing中编写程序时,我可以在EventDispatchThread上显示和更新Splash屏幕,而

  • 下面是我的代码: 只是一个加载图像的文件。 我得到以下错误: 以下是完整的代码:

  • 问题内容: 我测试了此代码以创建带有图像的对话框。 我将图像文件放入目录中。但是由于某些原因,图像无法显示。你能帮我纠正我的错误吗? 问题答案: 只需替换以下代码: 有了这个 Docu参考。 https://docs.oracle.com/javase/8/javafx/api/javafx/scene/image/Image.html 当您将a传递给该类时,可以用 四种不同的方式 处理( 从do

  • 为了创建带有图像的对话框,我测试了这段代码。 我将图像文件放入目录。但是由于某种原因,图像不显示。你能帮我改正错误吗

  • 我试图在JavaFX中实现一个非常简单的Raspberry Pi接口。我用的是一台电脑。基于fxml的布局和样式我的项目与css。我的问题是,尽管该应用程序在我的主计算机(从eclipse运行)上运行得很好,但它在Raspberry上也不工作,当我尝试在主计算机上运行导出的jar时也不工作。 我就是这样把纽扣剥皮的。当然,resources/images文件夹位于我的构建路径中。按钮的颜色与我在c

  • 我正在使用Scene Builder2.0和eclipse Luna。在fxml文件中,我有我的场景的代码和图像。如果我在eclipse上测试这一点,一切都是正常的,但是如果我将它导出到可运行的jar中,然后运行它,我就会得到没有图像的窗口...以下是部分代码: 文件结构: 我觉得问题出在路径上,但我不知道这条路是怎么走的。加载程序代码: 初始化函数: