Javafx Scene.snapshot method with slow performance - java

I am making a sliding animation to switch a scene to another scene, but when I call this method, it has a delay for switching scene. I found that the cause is a method snapshot() of class Scene.
Does anyone have a solution?
code:
public void switchScene(Scene target) {
Scene current = getPrimaryStage().getScene();
WritableImage beforeImage;
WritableImage afterImage;
int width = ((int) ((Region) current.getRoot()).getWidth());
int height = ((int) ((Region) current.getRoot()).getHeight());
beforeImage = new WritableImage(width, height);
ImageView leftImage = new ImageView(current.snapshot(beforeImage));
afterImage = new WritableImage(width, height);
ImageView rightImage = new ImageView(target.snapshot(afterImage));
leftImage.setTranslateX(0);
rightImage.setTranslateX(width);
StackPane animation = new StackPane(leftImage, rightImage);
animation.setPrefSize(target.getWidth(), target.getHeight());
primaryStage.setScene(new Scene(animation));
Timeline timeline = new Timeline();
KeyValue kv = new KeyValue(rightImage.translateXProperty(), 0, Interpolator.EASE_BOTH);
KeyFrame kf = new KeyFrame(Duration.seconds(0.75), kv);
timeline.getKeyFrames().add(kf);
timeline.setOnFinished(t -> {
// remove pane and restore scene 1
primaryStage.setScene(target);
});
timeline.play();
}

Taking a snapshot in this way is an inherently slow operation, there's not a great deal that can be done to speed it up while staying in Java land. As suggested in the comment, if you really want to take a snapshot then a better approach would be to use the asynchronous method, which won't block the UI thread while it runs (so while they'll still be a delay, your app will still remain responsive.)
However, if I've understood your example correctly, there's absolutely no need to use screenshots at all - why are you using images rather than just animating the nodes themselves? Remember that all JavaFX elements are nodes of the scenegraph, so can be animated in the same way. So instead of:
StackPane animation = new StackPane(leftImage, rightImage);
You should just be able to do:
StackPane animation = new StackPane(source, target);
...then use this to animate the panes directly without going through the slow process of taking screenshots.

Related

Clear canvas in JavaFX

Im trying to clear simple canvas in JavaFX.
Start function
Canvas canvas = new Canvas();
Group root = new Group(canvas);
Scene scene = new Scene(root, 1400, 1000);
If user want to load game, then loadSave boolean variable sets to 'true'
if(loadSave){
//Clear scene and load new with circles from file
}
Else, it loads new game
else if(!loadSave){
drawSquares(scene, root);
}
I'll really appreciate your help.
I have special group for my Scene - root.
I can delete only this group using
root.getChildren().clear();
This feature has many uses, I can now eg add items that I want to remove into this group.
To remove only e.g Circles from group(where my Canvas is) I used
void clearScene(Group root) {
for (Circle circle : circles) {
root.getChildren().remove(circle);
}
circles.clear();
}

ImageView always scaling beyond scene size, can't seem to force it to fit

I am new to JavaFX (been working with swing for a long time) and am trying to work with BorderPane. One would assume BorderPane is similar to BorderLayout but the big difference is the center of BorderPane will expand to fit its contents while BorderLayout will shrink to fit the window.
I am using JFXPanel in a JFrame and have a 3 part interface: A panel on the left (some text), some buttons on the bottom (flow control), and in the center want to have a dynamic panel/pane, that for the most part will be just an imageview. I set it all up and it works fine, but I'm working with camera images here which are way bigger than my monitor. I've tried scaling the images down by binding the imageview width to different things (such as anchor pane, scene size (works, but not properly), etc. The issue I am having is that since borderpane's center panel expands to fit its content, it will expand and never have a proper value I can bind to. I need the image to be fully visible in the window at any size.
Here's the code I've been working with.
protected void setupFXWindow(JFXPanel mainPanel) {
butNext = new Button("Next Step");
butBack = new Button("PreviousStep Step");
butQuit = new Button("Cancel Signature Generation");
butNext.setTooltip(new Tooltip("Next step..."));
butBack.setTooltip(new Tooltip("Previous Step..."));
butQuit.setTooltip(new Tooltip("Quit generating a signature"));
Group root = new Group();
Scene scene = new Scene(root, javafx.scene.paint.Color.ALICEBLUE);
javafx.scene.image.Image fximage = new javafx.scene.image.Image(new File(image.getSourceFilePath()).toURI().toString());
ImageView iv1 = new ImageView();
iv1.setImage(fximage);
iv1.setPreserveRatio(true);
VBox directionsPanel = new VBox();
HBox authorflowPanel = new HBox(); //bottom buttons for next, back, etc.
mainPanel.setScene(scene);
//INSTRUCTIONS
directionsStepLabel = new Text();
directionsLabel = new Text();
setDirectionsText("Directions will be placed here.");
exampleLabel = new Text("Example");
exampleIconLabel = new Text("An example image will be shown here.");
directionsPanel.setPadding(new javafx.geometry.Insets(5, 5, 5, 5));
directionsPanel.getChildren().addAll(directionsStepLabel, directionsLabel, exampleLabel);
authorflowPanel.getChildren().addAll(butBack, butQuit, butNext);
BorderPane bp = new BorderPane();
bp.prefHeightProperty().bind(scene.heightProperty());
bp.prefWidthProperty().bind(scene.widthProperty());
bp.setLeft(directionsPanel);
bp.setBottom(authorflowPanel);
bp.setCenter(iv1);
root.getChildren().add(bp);
}
This code sample doesn't have iv1 (imageview) binded to anything, cause at this point I have no idea what I can bind to that will give me the remaining space in the scene. Since I cannot use the full width or height of the scene, I'm at a loss of what I am supposed to do here.
The code above makes it look like this:
Wrap the ImageView in some kind of Pane (e.g. a StackPane). Then the pane will fill the center region of the border pane and you can bind to its width and height:
ImageView iv1 = new ImageView();
iv1.setImage(fximage);
iv1.setPreserveRatio(true);
StackPane imageContainer = new StackPane(iv1);
iv1.fitWidthProperty().bind(imageContainer.widthProperty());
iv1.fitHeightProperty().bind(imageContainer.heightProperty());
// ...
bp.setCenter(imageContainer);

JavaFx Images in Gridpane slowing down performance drastically

I want to create a GridPane (which is nested in a ScrollPane) where I add dynamically cells to the GridPane. Each cell contains a VBox with a BackgroundImage, a few Labels and a Checkbox. The Problem is, that the GridPane can contain several hundreds VBoxes, in my case there are about 300 VBoxes and with this many VBoxes the response time of the Gridpane gets really poor. When I click for instance on a CheckBox it takes a few seconds until the CheckBox is selected/unselected, which makes my program pretty much unusable. Without the BackgroundImage the response time of the GridPane is perfect, so I know that the problem here are the Images
This is my Code to create a VBox:
private VBox createAlbumVBox(Album album) {
VBox container = new VBox();
container.setAlignment(Pos.BOTTOM_LEFT);
CheckBox checkBox = new CheckBox();
Label labelAlbum = new Label(album.getName());
Label labelArtist = new Label(album.getArtistName());
labelAlbum.setStyle("-fx-text-fill: #272727");
labelArtist.setStyle("-fx-text-fill: #272727");
Background background;
if(album.getCover() != null)
{
byte[] coverData = album.getCover();
Image image = new Image(new ByteArrayInputStream(coverData));
BackgroundSize bg = new BackgroundSize(100,100,true,true,true,false);
BackgroundImage backgroundImage = new BackgroundImage(image,BackgroundRepeat.NO_REPEAT,BackgroundRepeat.NO_REPEAT,BackgroundPosition.CENTER,bg);
background = new Background(backgroundImage);
}
else
{
Image image = new Image("/ressources/covers/default-cover.png");
BackgroundSize bg = new BackgroundSize(100,100,true,true,true,false);
BackgroundImage backgroundImage = new BackgroundImage(image,BackgroundRepeat.NO_REPEAT,BackgroundRepeat.NO_REPEAT,BackgroundPosition.CENTER,bg);
background = new Background(backgroundImage);
}
checkBox.setOnMouseClicked(e -> {
if (checkBox.isSelected()) {
album.getTitles().forEach(t -> t.setReadyToSync(true));
} else {
album.getTitles().forEach(t -> t.setReadyToSync(false));
}
});
container.setBackground(background);
HBox hBox = new HBox();
hBox.getChildren().addAll(labelAlbum, labelArtist, checkBox);
hBox.setPrefHeight(30);
hBox.setStyle("-fx-background-color: rgba(255, 255, 255, 0.4)");
container.getChildren().addAll(hBox);
return container;
}
I already tried to use an ImageView instead of a BackgroundImage. Unfortunately the performance with an ImageView is as poor as with a BackgroundImage.
This is not really an answer but more a set of suggestions you can try. It's hard to comment on performance issues without a complete mcve, which would allow the issues to be easily reproduced locally in a minimal application.
Some things you could try are:
Use background loading for your images.
Cache loaded images in a LRU cache.
Use a virtualized control, such as a ControlsFX GridView.
See also some of the performance optimization suggestions in a related answer (some of which may not be applicable to your situation):
What is the best way to display millions of images in Java?
Also, your issue could be in code that you don't show. Your routine is being passed an Album instance which includes album data, including image data in binary form. If you load up your album data and images from a database dynamically, then that process could slow or freeze your application, depending upon how you do it.

JavaFX enormous lag with larger Canvas dimensions

This is kind of a vague question, but I'm trying to create a code editor using JavaFX Canvas technologies and its being incredibly slow for what I'd like.
Take the following code for example
public class JavaFXApplication13 extends Application {
#Override
public void start(Stage primaryStage) {
BorderPane root = new BorderPane();
int extent = 6300;
System.out.println(System.getProperty("java.version"));
Canvas cvs = new Canvas(extent,extent);
ScrollPane scpn = new ScrollPane();
root.setTop(cvs);
scpn.setContent(root);
root.autosize();
scpn.autosize();
GraphicsContext ctx = cvs.getGraphicsContext2D();
for(int i = 0; extent / 300 > i; i++){
ctx.setFill(Color.RED);
ctx.fillRect(i*300, 0, 100, extent);
ctx.setFill(Color.BLUE);
ctx.fillRect(i*300+100, 0, 100, extent);
ctx.setFill(Color.GREEN);
ctx.fillRect(i*300+200, 0, 100, extent);
}
//// root.getChildren().add(btn);
Scene scene = new Scene(scpn, 300, 250);
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}
If you try to slide pane around, theres a good few seconds between when you move the cursor and when the scrollbar and the scroll pane update. This has to do with the size of the canvas, which is set to 6300, which is nothing. I can open NotePad and get line heights in the million and its able to draw them with ease.
Performance is even worse when trying to draw on a large sized canvas, simple
onKeyPress((a) -> drawText(a.getText(), ...));
takes seconds to process.
I guess what I'm trying to say is, is this performance normal? or should I just suck it up and move on to something more powerful such as OpenGL?
A code editor should be virtual and only draw the lines you see on screen! So IMHO your use of canvas is completely incorrect!
Canvas at its heart can be seen like a buffered image you can draw on and on the OpenGL / Directx side only sees a image.
Why reinvent the wheel there are at least 2 opensource javafx code editors. See https://tomasmikula.github.io/blog/ and http://tomsondev.bestsolution.at/2014/08/11/efxclipse-1-0-new-features-styledtext-control-to-build-a-code-editor-framework/
I tested JavaFX for a game project about 6 months ago, I was drawing 10000 rectangles and using an animation timer to change the color of each rectangle 60 times a second.
I found that using the canvas for this was really slow and was getting about a frame a second. I changed to just using the scene graph by just adding JavaFX rectangle nodes to a Group node and it worked with no lag.
I was surprised that using JavaFX objects was way more efficient that using the canvas, I am now working on my second JavaFX 2 game using nodes in the scene graph.

Disable automatic resizing, without disabling manual resizing

Coding a GUI in a Java project I've encountered a problem with JavaFX.
I've not found any soulutions for my specific problem, so here I am:)
Is it possible to let a JavaFX scene be resizeable by the user and at the same time have it not resized by child nodes, that are bigger, than the window?
Here is some sample code:
#Override
public void startGUI(int width, int height) {
this.main = new MainWindow(this, this.logic);
this.scene = new Scene(this.main.getRoot());
this.main.setScene(this.scene);
this.primaryStage.setScene(this.scene);
this.primaryStage.setMinHeight(height);
this.primaryStage.setMinWidth(width);
}
The 'MainWindow' has got a childnode, that can be very big (>1024x768).
I want the window not to be resized by this node, but at the same time, the user should be able to resize the window by dragging its borders.
Use a Scene constructor which specifies initial size constraints.
For example:
Scene scene = new Scene(root, 600, 400);
That way the initial size of the Scene will be taken from these constraints rather than calculated from the preferred size of the root node.

Categories