4

Hei there

So I have the following problem. I have around 1500 images of playing cards. I want to display them in a "Gallery" where you could scroll through them. I was able to create a GridView with the ImageCell object and I was also able to add images to it. Now my problem is that if I add all Image's at once logically Java crashes because of a heap error. I have the image url's (local files) in a list. How could I implement that it only load lets say 15 images. If I then scroll it loads the next 15 and unloads the old ones. So it would only load the images of the actual visible images and not all 1500. How would I do this? I am completely out of ideas. The Platform.runLater() is needed because some sort of issue with ControlsFX

Here my code:

    public void initialize() {

    GridView<Image> gridView = new GridView<>();
    gridView.setCellFactory(gridView1 -> new ImageGridCell(true));
    Image image = new Image("C:\\Users\\nijog\\Downloads\\cardpictures\\01DE001.png");

    gridView.setCellWidth(340);
    gridView.setCellHeight(512);

    //Platform.runLater(()-> {
    //    for (int i = 0; i < 5000; i++){
    //        gridView.getItems().add(image);
    //    }
    //});

    Platform.runLater(() -> gridView.getItems().addAll(createImageListFromCardFiles()));

    borderPane.setCenter(gridView);

}

protected List<Image> createImageListFromCardFiles(){

    List<Image> imageViewList = new ArrayList<>();
    App.getCardService().getCardArray().stream()
            //.filter(Card::isCollectible)
            .sorted(Comparator.comparingInt(Card::getCost))
            .sorted(Comparator.comparing(Card::isChampion).reversed())
            .skip(0)
            //.limit(100)
            .forEach(card -> {
                try {
                    String url = String.format(App.pictureFolderPath +"%s.png", card.getCardCode());
                    imageViewList.add(new Image(url));
                } catch (Exception e) {
                    System.out.println("Picture file not found [CardCode = " + card.getCardCode() + "]");
                    App.logger.writeLog(Logger.Operation.EXCEPTION, "Picture file not found [CardCode = " + card.getCardCode() + "]");
                }
            });
    return imageViewList;
}
lubrum
  • 352
  • 4
  • 21
tctfox
  • 95
  • 1
  • 1
  • 4

1 Answers1

5

You might not need to use the strategy you describe. You're displaying the images in cells of size 340x512, which is 174,080 pixels. Image storage is 4 bytes per pixel, so this is 696,320 bytes per image; 1500 of them will consume about 1GB. You just need to make sure you load the image at the size you are displaying it (instead of its native size):

// imageViewList.add(new Image(url));
imageViewList.add(new Image(url, 340, 512, true, true, true));

If you need an image at full size later (e.g. if you want the user to select an image from your grid view and display it in a bigger pane), you'd just need to reload it from the url.

If you do need to implement the strategy you describe, GridView supports that out of the box. Just keep a list of the URLs, instead of the Images, and use a custom GridCell to load the image as needed. This will consume significantly less memory, at the cost of a lot more I/O (loading the images) and CPU (parsing the image format).

Make the items for the GridView the image urls, stored as Strings.

Then you can do something like:

GridView<String> gridView = new GridView<>();
gridView.getItems().addAll(getAllImageURLs());

gridView.setCellFactory(gv -> new GridCell<>() {
    private final ImageView imageView = new ImageView();

    {
        imageView.fitWidthProperty().bind(widthProperty());
        imageView.fitHeightProperty().bind(heightProperty());
        imageView.setPreserveRatio(true);
    }

    @Override
    protected void updateItem(String url, boolean empty) {
        super.updateItem(url, empty);
        if (empty || url == null) {
            setGraphic(null);
        } else {
            double w = getGridView().getCellWidth();
            double h = getGridView().getCellHeight();
            imageView.setImage(new Image(url, w, h, true, true, true));
            setGraphic(imageView);
        }
    }
});


protected List<String> getAllImageURLs(){

    return App.getCardService().getCardArray().stream()
            // isn't the first sort redundant here?
            .sorted(Comparator.comparingInt(Card::getCost))
            .sorted(Comparator.comparing(Card::isChampion).reversed())
            .map(card -> String.format(App.pictureFolderPath +"%s.png", card.getCardCode()))
            .collect(Collectors.toList());
}
James_D
  • 201,275
  • 16
  • 291
  • 322
  • I think your sorting is more efficient (and equivalent) if you do `.sorted(Comparator.comparing(Card::isChampion, champ -> champ.reverseOrder()).thenComparingInt(Card::getCost))` – James_D May 24 '22 at 15:50
  • Hei there I feel so dumb, using your tip by changing the Image size fixed it. I thank you 1000 times – tctfox May 26 '22 at 12:18
  • @tctfox I would recommend trying the other approach as well, and comparing the performance via different measures. – James_D May 26 '22 at 13:02
  • I will thank you! This is my final project for my java course I have at university. Javafx is still new to me and you guys helped me quite a bit. Thanks :) – tctfox May 26 '22 at 13:34