【发布时间】:2022-06-14 15:49:16
【问题描述】:
我正在使用 JavaFX 17 制作图片查看器应用程序。总而言之,该应用程序类似于 Windows Photo / Windows Picture Viewer。用户可以打开图片或文件夹。应用程序将显示给定图片或给定文件夹中的第一张图片。我的应用程序将一次显示一张图片,用户可以使用可用控件(下一张、上一张、最后一张和开始)浏览图片。
我检查了以下线程以确保它已经足够优化:
- JavaFx Images in Gridpane slowing down performance drastically
- What is the best way to display millions of images in Java?
但是,我发现我的代码在处理 200 张图片时存在问题,每个图片大小约为 1~2 MB。
没有background loading,应用程序不会显示任何内容。即使导航控制状态因为知道有可用的图片而改变。所以,点击next & prev只会显示一个空白屏幕。使用背景加载时,仅加载第一张图片中的一小部分。几次next控制后,突然又变成空白了。
这是我最小的、可重现的示例:
package com.swardana.mcve.image;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executors;
import javafx.application.Application;
import javafx.concurrent.Task;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
/**
* JavaFX App
*/
public class App extends Application {
@Override
public void start(Stage stage) {
var view = new View();
var path = Paths.get("Path/to/many/images");
var storage = new Storage(new PictureSource(path));
storage.setOnSucceeded(eh -> view.exhibit(storage.getValue()));
Executors.newSingleThreadExecutor().submit(storage);
var scene = new Scene(view, 640, 480);
scene.addEventFilter(KeyEvent.KEY_PRESSED, eh -> {
switch (eh.getCode()) {
case RIGHT:
view.next();
break;
case DOWN:
view.last();
break;
case LEFT:
view.prev();
break;
case UP:
view.beginning();
break;
default:
throw new AssertionError();
}
});
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch();
}
public class Picture {
private final String name;
private final Image image;
public Picture(final String name, final Path src) throws IOException {
this(name, new Image(src.toUri().toURL().toExternalForm(), true));
}
public Picture(final String name, final Image img) {
this.name = name;
this.image = img;
}
public final String name() {
return this.name;
}
public final Image image() {
return this.image;
}
}
public class PictureSource {
private final Path source;
public PictureSource(final Path src) {
this.source = src;
}
public final List<Picture> pictures() {
var dir = this.source.toString();
final List<Picture> pictures = new ArrayList<>();
try (var stream = Files.newDirectoryStream(this.source, "*.{png,PNG,JPG,jpg,JPEG,jpeg,GIF,gif,BMP,bmp}")) {
for (final var path : stream) {
var picName = path.getFileName().toString();
pictures.add(
new Picture(picName, path)
);
}
return pictures;
} catch (final IOException ex) {
throw new RuntimeException(ex);
}
}
}
public class Storage extends Task<List<Picture>> {
private final PictureSource source;
public Storage(final PictureSource src) {
this.source = src;
}
@Override
protected final List<Picture> call() throws Exception {
return this.source.pictures();
}
}
public class View extends VBox {
private final ImageView image;
private List<Picture> pictures;
private int lastIdx;
private int index;
public View() {
this.image = new ImageView();
this.initGraphics();
}
// This method to accept value from the `Storage`.
public void exhibit(final List<Picture> pics) {
this.pictures = pics;
this.index = 0;
this.lastIdx = pics.size();
this.onChange();
}
public void next() {
if (this.index != this.lastIdx - 1) {
this.index++;
this.onChange();
}
}
public void prev() {
if (this.index != 0) {
this.index--;
this.onChange();
}
}
public void last() {
this.index = this.lastIdx - 1;
this.onChange();
}
public void beginning() {
this.index = 0;
this.onChange();
}
// Whenever the action change, update the image from pictures.
public void onChange() {
this.image.setImage(this.pictures.get(this.index).image());
}
private void initGraphics() {
this.getChildren().add(this.image);
}
}
}
非常感谢任何帮助和建议。
【问题讨论】:
-
minimal reproducible example 请注意 M (忽略所有的绒毛 - 只是一个简单的类来加载图像以及如何在后台线程上使用它)
-
@kleopatra 我已更新我的答案以删除所有绒毛并提供最少的可重复示例。
-
hmm .. 无法重现问题(不过可能是图像)。注意:我认为,不需要您的存储,无论如何它都会立即返回 - 加载是由图像本身完成的,也就是说您有许多您不(想要)控制的后台线程。因此,您可能正在尝试导航到尚未完全加载的图像 - 不确定在这种情况下系统应该做什么(文档状态“显示占位符”,而我看到一个空白视图,直到图像已完全加载 - 检查其进度属性)
-
是的,我的代码在处理 200 项以下的图像时没有任何问题。但是,当达到 200 张图像时,它会显示这种行为。可能需要查看有关如何解决此问题的其他选项
-
尝试使用
GridView。
标签: java image performance javafx