Мне нужно очень быстро просматривать тысячи миниатюр в кроссплатформенном приложении (маркировка / проверка изображений для машинного обучения). Я написал менеджер миниатюр, который заботится о создании миниатюр в 200 пикселей (например) по мере необходимости. Я написал приложение JavaFX, которое создает ScrollPane с TilePane с 2000 дочерними элементами, каждое с ImageView, которое содержит одно из этих изображений 200x200, считанных с диска в ImageBuffer и преобразованных в изображение JavaFX. Я загружаю, конвертирую и добавляю изображения в TilePane в фоновом режиме (используя Platform.runLater), и, кажется, все работает хорошо.
С 2000 миниатюр в 200x200, TilePane прокручивается очень быстро, как я надеялся. Но при разрешении 400x400, или когда я go до 16000 миниатюр (даже при 100x100), дисплей замедляется до ползания, с «вращающимся леденцом» в течение нескольких секунд между каждым обновлением экрана.
Я использую 6 ГБ, выделенных для JVM. Я сказал каждому ImageView для setCache (true) и setCacheHint (CacheHint.SPEED). Все загружено в память и уже отрендерено, и все еще очень медленно.
JavaFX выполняет масштабирование изображений или что-то на лету? Мне просто интересно, что я могу сделать, чтобы сделать это намного быстрее.
Ниже приведен пример того, что я делаю, за исключением того, что этот пример генерирует изображения с нуля вместо чтения миниатюр (и генерирует при необходимости). ). Но он воспроизводит проблему:
- С 200 панелями он работает хорошо и быстро (на моем ноутбуке).
- С 2000 панелями это раздражающе медленно.
- С 16000 панелями он вращается в течение нескольких секунд между обновлениями, что невозможно.
import java.awt.*;
import java.awt.image.BufferedImage;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.embed.swing.SwingFXUtils;
import javafx.scene.CacheHint;
import javafx.scene.Scene;
import javafx.scene.control.ScrollPane;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.TilePane;
import javafx.stage.Stage;
public class ThumbnailBrowser extends Application {
public static void main(String[] args) {
launch(args);
}
@Override
public void start(Stage primaryStage) {
// Create a Scene with a ScrollPane that contains a TilePane.
TilePane tilePane = new TilePane();
tilePane.getStyleClass().add("pane");
tilePane.setCache(true);
tilePane.setCacheHint(CacheHint.SPEED);
ScrollPane scrollPane = new ScrollPane();
scrollPane.setFitToWidth(true);
scrollPane.setContent(tilePane);
Scene scene = new Scene(scrollPane, 1000, 600);
primaryStage.setScene(scene);
// Start showing the UI before taking time to load any images
primaryStage.show();
// Load images in the background so the UI stays responsive.
ExecutorService executor = Executors.newFixedThreadPool(20);
executor.submit(() -> {
addImagesToGrid(tilePane);
});
}
private void addImagesToGrid(TilePane tilePane) {
int size = 200;
int numCells = 2000;
for (int i = 0; i < numCells; i++) {
// (In the real application, get a list of image filenames, read each image's thumbnail, generating it if needed.
// (In this minimal reproducible code, we'll just create a new dummy image for each ImageView)
ImageView imageView = new ImageView(createFakeImage(i, size));
imageView.setPreserveRatio(true);
imageView.setFitHeight(size);
imageView.setFitWidth(size);
imageView.setCache(true);
imageView.setCacheHint(CacheHint.SPEED);
Platform.runLater(() -> tilePane.getChildren().add(imageView));
}
}
// Create an image with a bunch of rectangles in it just to have something to display.
private Image createFakeImage(int imageIndex, int size) {
BufferedImage image = new BufferedImage(size, size, BufferedImage.TYPE_INT_RGB);
Graphics g = image.getGraphics();
for (int i = 1; i < size; i ++) {
g.setColor(new Color(i * imageIndex % 256, i * 2 * (imageIndex + 40) % 256, i * 3 * (imageIndex + 60) % 256));
g.drawRect(i, i, size - i * 2, size - i * 2);
}
return SwingFXUtils.toFXImage(image, null);
}
}
Обновление : Оказывается, что если я заменим «TilePane» на «ListView» в приведенном выше коде, то он быстро и красиво прокручивается, даже с 16 000 плиток. Но тогда проблема в том, что он находится в одном вертикальном списке, а не в виде сетки миниатюр. Возможно, мне следует задать это как новую топи c, но это приводит меня к вопросу о том, как я могу расширить ListView для отображения его элементов в двумерной сетке (фиксированного размера) вместо одномерного списка.