Memory leak with FXCanvas - javafx

I'm working on Eclipse e4 RCP application where I have one SWT part. Using JavaFX interoperability with SWT I have added javafx.embed.swt.FXCanvas. After I create a javafx.scene.Scene with 50000 javafx.scene.control.Button and hook it to the canvas, application memory jumps to 1.5gb.
Problem starts when I close that part, application does not release a memory. When I open that part again, memory jumps to 2.something gb.
Part class:
import javax.annotation.PostConstruct;
import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import javafx.embed.swt.FXCanvas;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ScrollPane;
import javafx.scene.layout.Pane;
public class StressTestJavaFXPart
{
private Scene scene;
private FXCanvas canvas;
private final int columns = 100;
private final int rows = 500;
#PostConstruct
public void createComposite(final Composite parent)
{
parent.setLayout(new GridLayout(1, false));
canvas = new FXCanvas(parent, SWT.NONE);
GridDataFactory.fillDefaults().grab(true, true).span(0, 0).applyTo(canvas);
final Pane pane = new Pane();
for (int i = 0; i < columns ; i++)
{
for (int j = 0; j < rows ; j++)
{
final Button button = new Button(String.format("%s : %s", i, j));
pane.getChildren().add(button);
button.setLayoutX(100 * i);
button.setLayoutY(100 * j);
}
}
final ScrollPane sp = new ScrollPane(pane);
scene = new Scene(sp);
canvas.setScene(scene);
parent.addDisposeListener(new DisposeListener()
{
#Override
public void widgetDisposed(final DisposeEvent e)
{
canvas.dispose();
System.gc();
}
});
}
}
Any suggestions are very welcome! Thanks

I solved it using reflection:
parent.addDisposeListener(disposeEvent -> {
//release Pixel Buffer via Reflection -> prevents memory leak
try {
Field pixelBuffer = FXCanvas.class.getDeclaredField("pixelsBuf");
pixelBuffer.setAccessible(true);
pixelBuffer.set(this, null);
} catch (NoSuchFieldException | IllegalAccessException e) {
}
});
Worked well for me.

Related

Erasing Antialiased Shapes from a JavaFX Canvas

I have inherited a simulation program to extend with new features. The original was written as an Applet using the AWT library for graphics. Before adding the new features I want to adapt the program to the desktop and use JavaFX instead of AWT.
The simulation paints hundreds or thousands of objects dozens of times per second, then erases them and repaints them at new locations, effectively animating them. I am using a Canvas object for that part of the UI. Erasing is done by repainting the object with the background color. What I am seeing though is that erasing objects is incomplete. A kind of "halo" gets left behind though.
The following program illustrates the problem. Clicking the "Draw" button causes it to draw a few hundred circles on the Canvas using the foreground color. After drawing, clicking the button again will erase the circles by re-drawing them in the background color. Multiple cycles of draw/erase will build up a visible background of "ghost" images.
package com.clartaq.antialiasingghosts;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import java.util.Random;
public class Main extends Application {
static final int NUM_CIRCLES = 500;
static final int CIRCLE_DIAMETER = 10;
static final double PANEL_WIDTH = 75.0;
static final double PANEL_HEIGHT = 40.0;
static final Color FG_COLOR = Color.rgb(10, 0, 200);
static final Color BG_COLOR = Color.rgb(255, 255, 255);
static final double BUTTON_WIDTH = 50.0;
GraphicsContext gc;
Random rand = new Random();
double[] px = new double[NUM_CIRCLES];
double[] py = new double[NUM_CIRCLES];
void randomizeParticlePositions() {
for (int i = 0; i < NUM_CIRCLES; i++) {
px[i] = rand.nextDouble() * PANEL_WIDTH;
py[i] = rand.nextDouble() * PANEL_HEIGHT;
}
}
void drawCircles(Color color) {
gc.setFill(color);
for (int i = 0; i < NUM_CIRCLES; i++) {
var screenX = px[i] * CIRCLE_DIAMETER;
var screenY = py[i] * CIRCLE_DIAMETER;
gc.fillOval(screenX, screenY, CIRCLE_DIAMETER, CIRCLE_DIAMETER);
}
}
#Override
public void start(Stage stage) {
String javaVersion = System.getProperty("java.version");
String javafxVersion = System.getProperty("javafx.version");
stage.setTitle("AntiAliasingGhosts -- erasing objects leaves ghosts in JavaFX");
Label versionLabel = new Label("JavaFX " + javafxVersion
+ ", running on Java " + javaVersion + ".");
double canvasWidth = (PANEL_WIDTH * CIRCLE_DIAMETER);
double canvasHeight = (PANEL_HEIGHT * CIRCLE_DIAMETER);
Canvas canvasRef = new Canvas(canvasWidth, canvasHeight);
gc = canvasRef.getGraphicsContext2D();
Button deBtn = new Button("Draw");
deBtn.setPrefWidth(BUTTON_WIDTH);
deBtn.setOnAction(e -> {
String txt = deBtn.getText();
switch (txt) {
case "Draw" -> {
randomizeParticlePositions();
drawCircles(FG_COLOR);
deBtn.setText("Erase");
}
case "Erase" -> {
drawCircles(BG_COLOR);
deBtn.setText("Draw");
}
default -> Platform.exit();
}
});
Button exBtn = new Button("Exit");
exBtn.setPrefWidth(BUTTON_WIDTH);
exBtn.setOnAction(e -> Platform.exit());
TilePane tp = new TilePane();
tp.setAlignment(Pos.CENTER);
tp.setHgap(10);
tp.getChildren().addAll(deBtn, exBtn);
VBox root = new VBox();
root.setPadding(new Insets(7));
root.setSpacing(10);
root.setAlignment(Pos.CENTER);
root.getChildren().addAll(versionLabel, canvasRef, tp);
StackPane sp = new StackPane(root);
BackgroundFill bf = new BackgroundFill(BG_COLOR, CornerRadii.EMPTY, Insets.EMPTY);
Background bg = new Background(bf);
sp.setBackground(bg);
Scene scene = new Scene(sp, 640.0, 480.0);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch();
}
}
I can get good erasure by expanding the diameter of the circles by 2 pixels when erasing. Of course, that can affect nearby shapes too.
Also, using the fillRect method to erase the entire Canvas seems reasonable, but that means everything has to be re-drawn if anything has to be re-drawn. I suppose it is possible to optimize the re-draw by erasing and re-drawing a smaller section of the Canvas but I don't want to do that if it isn't necessary.
Magnifying sections of the program display shows that it is really an antialiasing effect. Constructing the Scene with the SceneAntialiasing.DISABLED parameter does not seem to have any effect.
Attempting to turn off image smoothing as suggested in this question does not help.
Is possible to erase a single shape drawn on a Canvas by re-drawing it in the background color?
I am using Java 17.0.1, JavaFX 17.0.1, and a 5K Mac display if that is relevant.
For expedience, note the difference between fillOval and strokeOval() in the GraphicsContext. You can conditionally erase the outline in drawCircles() as a function of a suitable boolean value:
if (stroke) {
gc.setStroke(BG_COLOR);
gc.strokeOval(screenX, screenY, CIRCLE_DIAMETER, CIRCLE_DIAMETER);
}
Try a few representative shapes, e.g. fillRect, to verify the desired result.
A better alternative, IMO, is to pursue the erase -> render strategy. Complete examples seen here and here may help you establish whether the approach is scalable to your use-case. See also this related examination of resampling artifact.
Expedient approach, as tested:
import javafx.application.Application;
import javafx.application.Platform;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import java.util.Random;
public class Main extends Application {
static final int NUM_CIRCLES = 500;
static final int CIRCLE_DIAMETER = 10;
static final double PANEL_WIDTH = 75.0;
static final double PANEL_HEIGHT = 40.0;
static final Color FG_COLOR = Color.rgb(10, 0, 200);
static final Color BG_COLOR = Color.rgb(255, 255, 255);
static final double BUTTON_WIDTH = 50.0;
GraphicsContext gc;
Random rand = new Random();
private boolean stroke;
double[] px = new double[NUM_CIRCLES];
double[] py = new double[NUM_CIRCLES];
void randomizeParticlePositions() {
for (int i = 0; i < NUM_CIRCLES; i++) {
px[i] = rand.nextDouble() * PANEL_WIDTH;
py[i] = rand.nextDouble() * PANEL_HEIGHT;
}
}
void drawCircles(Color color) {
gc.setFill(color);
for (int i = 0; i < NUM_CIRCLES; i++) {
var screenX = px[i] * CIRCLE_DIAMETER;
var screenY = py[i] * CIRCLE_DIAMETER;
gc.fillOval(screenX, screenY, CIRCLE_DIAMETER, CIRCLE_DIAMETER);
if (stroke) {
gc.setStroke(BG_COLOR);
gc.strokeOval(screenX, screenY, CIRCLE_DIAMETER, CIRCLE_DIAMETER);
}
}
}
#Override
public void start(Stage stage) {
String javaVersion = System.getProperty("java.version");
String javafxVersion = System.getProperty("javafx.version");
stage.setTitle("AntiAliasingGhosts -- erasing objects leaves ghosts in JavaFX");
Label versionLabel = new Label("JavaFX " + javafxVersion
+ ", running on Java " + javaVersion + ".");
double canvasWidth = (PANEL_WIDTH * CIRCLE_DIAMETER);
double canvasHeight = (PANEL_HEIGHT * CIRCLE_DIAMETER);
Canvas canvasRef = new Canvas(canvasWidth, canvasHeight);
gc = canvasRef.getGraphicsContext2D();
Button deBtn = new Button("Draw");
deBtn.setPrefWidth(BUTTON_WIDTH);
deBtn.setOnAction(e -> {
String txt = deBtn.getText();
switch (txt) {
case "Draw" -> {
randomizeParticlePositions();
drawCircles(FG_COLOR);
deBtn.setText("Erase");
stroke = true;
}
case "Erase" -> {
drawCircles(BG_COLOR);
deBtn.setText("Draw");
stroke = false;
}
default ->
Platform.exit();
}
});
Button exBtn = new Button("Exit");
exBtn.setPrefWidth(BUTTON_WIDTH);
exBtn.setOnAction(e -> Platform.exit());
TilePane tp = new TilePane();
tp.setAlignment(Pos.CENTER);
tp.setHgap(10);
tp.getChildren().addAll(deBtn, exBtn);
VBox root = new VBox();
root.setPadding(new Insets(7));
root.setSpacing(10);
root.setAlignment(Pos.CENTER);
root.getChildren().addAll(versionLabel, canvasRef, tp);
StackPane sp = new StackPane(root);
BackgroundFill bf = new BackgroundFill(BG_COLOR, CornerRadii.EMPTY, Insets.EMPTY);
Background bg = new Background(bf);
sp.setBackground(bg);
Scene scene = new Scene(sp, 640.0, 480.0);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch();
}
}

JavaFX Slider: How to only change value when dragging is done while also maintaining keyboard / touch support

How can I make the application so that the graph will only update when the slider has been let go, while still maintaining keyboard / touch screen support?
Replacing valueProperty().addListener() with setOnMouseReleased() will not allow the value to be changed with keyboard or touch screen anymore.
package application;
import javafx.application.Application;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.layout.VBox;
import javafx.scene.control.Slider;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.geometry.Insets;
public class SavingsCalculatorApplication extends Application {
#Override
public void start(Stage stage) {
NumberAxis xAxis = new NumberAxis();
NumberAxis yAxis = new NumberAxis();
LineChart chart = new LineChart(xAxis, yAxis);
chart.setLegendVisible(false);
chart.setCreateSymbols(false);
Slider slider = new Slider(0, 100, 10);
slider.setShowTickLabels(true);
slider.setShowTickMarks(true);
slider.setPadding(new Insets(20, 40, 0, 40));
XYChart.Series data = new XYChart.Series();
chart.getData().add(data);
slider.valueProperty().addListener(event -> {
data.getData().clear();
for(int counter = 0; counter < 100; counter++) {
data.getData().add(new XYChart.Data(counter, counter * slider.getValue()));
}
});
VBox layout = new VBox();
layout.getChildren().add(slider);
layout.getChildren().add(chart);
Scene scene = new Scene(layout);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(SavingsCalculatorApplication.class);
}
}
Use the valueChanging property. You can respond when the value changes and valueChanging is false, which will happen if the slider changes by the keyboard (or programmatically), or when the valueChanging changes from true to false (which will happen when a change due to the mouse is completed):
slider.valueProperty().addListener((obs, oldValue, newValue) -> {
if ( !slider.isValueChanging()) {
updateChart(newValue.doubleValue(), data);
}
});
slider.valueChangingProperty().addListener((obs, wasChanging, isNowChanging) -> {
if (! isNowChanging) {
updateChart(slider.getValue(), data);
}
});
with
private void updateChart(double value, XYChart.Series data) {
data.getData().clear();
for(int counter = 0; counter < 100; counter++) {
data.getData().add(new XYChart.Data(counter, counter * value));
}
}
Never use low-level input events (mouse events, key events, etc) for semantic changes in a control. Always register listeners on the control's properties instead.

Why the program gets the error in JavaFX? [duplicate]

This question already has an answer here:
How do I determine the correct path for FXML files, CSS files, Images, and other resources needed by my JavaFX Application?
(1 answer)
Closed 2 years ago.
I have spent the last 3 days trying to find an answer, watching videos and forum posts but no luck. Please take a look in those 4 pics. It seems that it only does it when the problem has image files.
import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.layout.GridPane;
import javafx.stage.Stage;
import javafx.scene.image.ImageView;
import javafx.scene.image.Image;
public class Exercise14_02 extends Application {
#Override
public void start(Stage primaryStage) {
Image imageX = new Image("image/x.gif");
Image imageO = new Image("image/o.gif");
GridPane pane = new GridPane();
pane.setAlignment(Pos.CENTER);
pane.setHgap(5);
pane.setVgap(5);
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
int status = (int)(Math.random() * 3);
if (status == 0) {
pane.add(new ImageView(imageX), j, i);
} else if (status == 1) {
pane.add(new ImageView(imageO), j, i);
}
}
}
// Create a scene and place it in the stage
Scene scene = new Scene(pane);
primaryStage.setTitle("Exercise14_02"); // Set the stage title
primaryStage.setScene(scene); // Place the scene in the stage
primaryStage.show(); // Display the stage
}
public static void main(String[] args) {
launch(args);
}
}
try moving your images to 'resources' folder, create one if needed.
Image imagex = new Image(getClass().getResourceAsStream("/resources/your_image.jpg"));
or
check if you have included your resources folder in project.

JavaFX AnimationTimer renders

My goal is to completely sync each animation frame to the monitor device vsync at perfectly 60fps. I googled online, and people suggested that i could use JavaFX AnimationTimer. I wrote a simple ball JavaFX AnimationTimer, but i could sometimes see a very noticeable jittery in the animation. Could you guys please help me?
I have attached the code here:
import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.paint.Color;
import javafx.scene.shape.Box;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;
public class Main extends Application
implements EventHandler <KeyEvent>
{
final int WIDTH = 600;
final int HEIGHT = 400;
double ballRadius = 40;
double ballX = 100;
double ballY = 200;
double xSpeed = 4;
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage stage) throws Exception {
stage.setTitle("Basic JavaFX demo");
Group root = new Group();
Scene scene = new Scene(root, WIDTH, HEIGHT);
// Bouncing Ball
Circle circle = new Circle();
circle.setCenterX(ballX);
circle.setCenterY(ballY);
circle.setRadius(ballRadius);
circle.setFill(Color.BLUE);
root.getChildren().add(circle);
// need to attach KeyEvent caller to a Node of some sort.
// How about an invisible Box?
final Box keyboardNode = new Box();
keyboardNode.setFocusTraversable(true);
keyboardNode.requestFocus();
keyboardNode.setOnKeyPressed(this);
root.getChildren().add(keyboardNode);
stage.setScene(scene);
stage.show();
AnimationTimer animator = new AnimationTimer(){
#Override
public void handle(long arg0) {
long startTime = System.currentTimeMillis();
// UPDATE
ballX += xSpeed;
if (ballX + ballRadius >= WIDTH)
{
ballX = WIDTH - ballRadius;
xSpeed *= -1;
} else if (ballX - ballRadius < 0) {
ballX = 0 + ballRadius;
xSpeed *= -1;
}
// RENDER
circle.setCenterX(ballX);
long stopTime = System.currentTimeMillis();
long elapsedTime = stopTime - startTime;
System.out.println(elapsedTime);
}
};
animator.start();
}
#Override
public void handle(KeyEvent arg0) {
if (arg0.getCode() == KeyCode.SPACE )
{
xSpeed *= -1;
}
}
}

How to change ImageView on KeyPressed

I am having difficulty trying to solve this, I am currently trying to figure out how to change the ImageView to another ImageView when I press a certain Key on my keyboard but I have no idea how to, I have already looked around almost everywhere I can think of but haven't got a clue.
Heres the code, this is a character class:
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import javafx.animation.Animation;
import javafx.application.Application;
import javafx.geometry.Rectangle2D;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.input.KeyCode;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import javafx.util.Duration;
public class Character extends Application{
private static final int COLUMNS = 3;
private static final int COUNT = 3;
private static final int WIDTH = 48;
private static final int HEIGHT = 50;
ImageView imgView;
public void start(Stage primaryStage) throws Exception {
Group root = new Group();
imgView = characterStill();
root.setOnKeyPressed(e -> {
if(e.getCode().equals(KeyCode.RIGHT)){
try {
imgView = characterWalking();
} catch (IOException ex) {}
}
});
root.setOnKeyReleased(e -> {
if(e.getCode().equals(KeyCode.RIGHT)){
try {
imgView = characterStill();
} catch (IOException ex) {}
}
});
root.getChildren().add(imgView);
Scene scene = new Scene(root, Menu.WIDTH, Menu.HEIGHT, Color.GREENYELLOW);
primaryStage.setScene(scene);
primaryStage.show();
}
public ImageView characterStill() throws IOException{
InputStream is = Files.newInputStream(Paths.get("C:\\Users\\Javier\\Desktop\\StickHero\\StillNinja.png"));
Image characterStill = new Image(is);
ImageView stillView = new ImageView(characterStill);
return stillView;
}
public ImageView characterWalking() throws IOException{
final InputStream is = Files.newInputStream(Paths.get("C:\\Users\\Javier\\Desktop\\StickHero\\WalkingNinja.png"));
final Image characterWalking = new Image(is);
ImageView charView = new ImageView(characterWalking);
charView.setViewport(new Rectangle2D(0, 0, WIDTH, HEIGHT));
final Animation animation = new AnimationGen(charView, Duration.millis(300), COUNT, COLUMNS, 0, 0, WIDTH, HEIGHT);
animation.setCycleCount(Animation.INDEFINITE);
animation.play();
return charView;
}
}
In Java, all objects are accessed via a reference. It is important to keep the distinction between the object in memory, and the reference to the object, clear.
When you execute
new ImageView(characterStill);
a new ImageView object is created in memory. When you then (effectively) do
imgView = new ImageView(characterStill);
you assign the variable imgView a reference to that object. You can think of the reference as the memory location of the object (though in reality it doesn't necessarily have to be implemented that way).
What happens in your code at initialization is effectively the following:
imgView = new ImageView(characterStill);
root.getChildren().add(imgView);
So imgView contains a reference to an ImageView object displaying your characterStill image. You then pass that reference to the child list of root, so now the child list of root contains a reference to that same ImageView object.
Now, when the user presses the RIGHT key, you
(effectively) execute
imgView = new ImageView(characterWalking);
This creates a new ImageView object in memory, and assigns a reference to it (think: memory location of this new object) to the variable imgView.
However, the child list of root hasn't changed: it still contains the original reference to the first ImageView object. So nothing changes in your UI.
You could fix this by replacing the child list of root with the newly created ImageView, i.e.:
root.setOnKeyPressed(e -> {
if(e.getCode().equals(KeyCode.RIGHT)){
try {
imgView = characterWalking();
root.getChildren().setAll(imgView);
} catch (IOException ex) {}
}
});
However, this is not very efficient. On every key press, you create a brand new object in memory, from an image freshly loaded from the hard drive. Then you discard the previous object and put the new object in the UI.
A better way is just to use a single ImageView object, and replace the image which it displays. You can do this by calling
imgView.setImage(...);
To make things even more efficient, instead of loading the image from disk every time, you could just load the images at startup and then reuse them. This code looks like
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import javafx.animation.Animation;
import javafx.application.Application;
import javafx.geometry.Rectangle2D;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.input.KeyCode;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import javafx.util.Duration;
public class Character extends Application{
private static final int COLUMNS = 3;
private static final int COUNT = 3;
private static final int WIDTH = 48;
private static final int HEIGHT = 50;
private ImageView imgView;
private Image characterStill ;
private Image characterWalking ;
private Animation animation ;
public void start(Stage primaryStage) throws Exception {
imgView = new ImageView();
characterStill = loadCharacterStill();
characterWalking = loadCharacterWalking();
imgView.setViewport(new Rectangle2D(0, 0, WIDTH, HEIGHT));
animation = new AnimationGen(charView, Duration.millis(300), COUNT, COLUMNS, 0, 0, WIDTH, HEIGHT);
animation.setCycleCount(Animation.INDEFINITE);
imgView.setImage(characterStill);
Group root = new Group();
root.setOnKeyPressed(e -> {
if(e.getCode().equals(KeyCode.RIGHT)){
imgView.setImage(characterWalking);
animation.play();
}
});
root.setOnKeyReleased(e -> {
if(e.getCode().equals(KeyCode.RIGHT)){
imgView.setImage(characterStill);
animation.stop();
}
});
root.getChildren().add(imgView);
Scene scene = new Scene(root, Menu.WIDTH, Menu.HEIGHT, Color.GREENYELLOW);
primaryStage.setScene(scene);
primaryStage.show();
}
public Image loadCharacterStill() throws IOException{
InputStream is = Files.newInputStream(Paths.get("C:\\Users\\Javier\\Desktop\\StickHero\\StillNinja.png"));
Image characterStill = new Image(is);
return characterStill ;
}
public Image loadCharacterWalking() throws IOException{
final InputStream is = Files.newInputStream(Paths.get("C:\\Users\\Javier\\Desktop\\StickHero\\WalkingNinja.png"));
final Image characterWalking = new Image(is);
return characterWalking ;
}
}
Obviously, since I don't have access to your images or your AnimationGen class, I haven't tested this; it should give you the idea though.

Resources