JavaFX Image Loading in Background and Threads JavaFX Image Loading in Background and Threads multithreading multithreading

JavaFX Image Loading in Background and Threads


I actually think there's probably a better general approach to satisfying whatever your application's requirements are than the approach you are trying to use, but here is my best answer at implementing the approach you describe.

Create a bounded BlockingQueue to hold the images as you load them. The size of the queue may need some tuning: too small and you won't have any "buffer" (so you won't be able to take advantage of any that are faster to load than the average), too large and you might consume too much memory. The BlockingQueue allows you to access it safely from multiple threads.

Create a thread that simply loops and loads each image synchronously, i.e. that thread blocks while each image loads, and deposits them in the BlockingQueue.

Since you want to try to display images up to once per FX frame (i.e. 60fps), use an AnimationTimer. This has a handle method that is invoked on each frame render, on the FX Application Thread, so you can implement it just to poll() the BlockingQueue, and if an image was available, set it in the ImageView.

Here's an SSCCE. I also indicated how to do this where you display each image for a fixed amount of time, as I think that's a more common use case and might help others looking for similar functionality.

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