JavaFX: Animation based on time function - animation

I don't have an MWE because I'm not sure how to start. I guess my question is mostly about which are the best tools for the job.
I have an object that amounts to a Function<Double, VectorXYZ>, which outputs the position of an object given a time. It handles its own interpolation. I'm wondering if there's a way to handle the functionality of a Timeline without having to use KeyFrames. I would like to be able to both play it forward, and to use a Slider.
I thought of having a DoubleProperty that is somehow linked to the Timeline, associated with a Listener that updates the translation property of the Group containing the object. But I don't know how to go about doing that.
Thanks for your help!

So yeah, it was a very vague question, but as #James_D said, AnimationTimer is the tool I was looking for. Basically I was looking for low-level access to the animation loop, and this seemed to be it. Here's an MWE of an object following some path across the Scene. It separates system time from scene time (stored as a DoubleProperty) so that it can be paused and restarted, and the time can be set via a Slider as well.
import com.google.common.base.Function;
import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.geometry.Point2D;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.SubScene;
import javafx.scene.control.Slider;
import javafx.scene.input.KeyCode;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
public class AnimationTestApp extends Application {
private static final double durationSeconds = 5;
private static final double screenWidthMeters = 10;
private static final double screenHeightMeters = 10;
private static final double pixelsPerMeter = 50;
private static final double squareSizeMeters = 0.5;
private static final double screenWidthPixels = pixelsPerMeter * screenWidthMeters;
private static final double screenHeightPixels = pixelsPerMeter * screenHeightMeters;
private static final double squareSizePixels = pixelsPerMeter * squareSizeMeters;
private static final double originXPixels = screenWidthPixels/2;
private static final double originYPixels = screenHeightPixels/2;
private final Rectangle square = new Rectangle(squareSizePixels, squareSizePixels, Color.RED);
private long lastTime = -1;
private boolean isStopped = true;
private double t = 0;
private DoubleProperty timeProperty;
private DoubleProperty timeProperty() {
if (timeProperty == null) {
timeProperty = new SimpleDoubleProperty();
timeProperty.addListener((obs, ov, nv) -> {updateScene();});
}
return timeProperty;
}
#Override
public void start(Stage primaryStage) throws Exception {
final SubScene subscene = new SubScene(new Group(square), screenWidthPixels, screenHeightPixels);
Slider timeSlider = new Slider(0, 5, 1);
timeSlider.valueProperty().bindBidirectional(timeProperty());
VBox root = new VBox(timeSlider, subscene);
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
AnimationTimer animationTimer = buildTimer();
handleKeyboard(scene, animationTimer);
}
private AnimationTimer buildTimer() {
AnimationTimer animationTimer = new AnimationTimer() {
#Override
public void handle(long now) {
double elapsedNS = now - lastTime;
double dt = elapsedNS * 1E-9;
if (timeProperty().get() + dt > durationSeconds) {
stop();
}
timeProperty().set(timeProperty().get() + dt);
updateScene();//timeProperty.get());
lastTime = now;
}
#Override
public void start() {
lastTime = System.nanoTime();
isStopped = false;
if (timeProperty().get() > durationSeconds) {
timeProperty().set(0);
}
super.start();
}
#Override
public void stop() {
isStopped = true;
super.stop();
}
};
return animationTimer;
}
private void updateScene() {
double t = timeProperty().get();
Point2D point = positionFunction().apply(t);
double xPixels = originXPixels + point.getX() * pixelsPerMeter;
double yPixels = originYPixels + point.getY() * pixelsPerMeter;
square.setTranslateX(xPixels);
square.setTranslateY(yPixels);
}
private Function<Double, Point2D> positionFunction() {
double radius = 3;
double period = 2;
return (t) -> new Point2D(radius * Math.sin(2*Math.PI*t/period), radius * Math.cos(2*Math.PI*t/period));
}
private void handleKeyboard(Scene scene, AnimationTimer timer) {
scene.setOnKeyPressed((ke) -> {
if (ke.getCode().equals(KeyCode.SPACE)) {
if (isStopped) {timer.start();}
else {timer.stop();}
}
});
}
}

Related

(JFX) Troubles when loading images using a static method and inherited classes

I'm trying to load 2 images using a static method loadImage(String) that I made.
This method is stored in my Entity (abstract) class and my classes Projectile and Player are inheriting from this entity.
However when I'm using this method (2 times since i'm loading "Projectile.loadImage("image1");" and "Player.loadImage("image2");") only the second image is stored.
I'm thinking that it's cause by the inheritance since the loadImage() method is written in the Entity class so it might be changing the static sprite from the Entity and not Player or Projectile, but i don't know how to fix this without recreating a loadImage() method for all subclasses and having the subclasses not inheriting the sprite property but instead having their own ones
Here is an example of the code :
//Main class
package application;
import Entities.*;
import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.input.KeyEvent;
import javafx.stage.Stage;
public class Main extends Application
{
final int WIDTH = 800;
final int HEIGHT = 800;
public int frameCount = 0;
Player p;
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage stage) throws Exception {
Player.loadImage("/images/isaac.png");
Projectile.loadImage("/images/tear.png");
stage.setTitle("My first JFX / Java project");
Group root = new Group();
Scene scene = new Scene(root, WIDTH, HEIGHT);
p = new Player(WIDTH/2,HEIGHT/2,root);
stage.setScene(scene);
stage.setResizable(false);
stage.sizeToScene();
stage.show();
}
}
//Entity Class and load Image
package Entities;
import application.Vector2;
import javafx.scene.Group;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
public abstract class Entity {
protected double x;
protected double y;
private Group root;
protected Vector2 dir;
protected double speed;
private static Image sprite;
private ImageView iv;
private int size;
private double hp;
public Entity(float px, float py,Group r, Image s) {
root = r;
x = px;
y = py;
size = 100;
iv = new ImageView(s);
dir = new Vector2(0,0);
speed = 0.2;
hp = 3;
root.getChildren().add(iv);
iv.setX(x);
iv.setY(y);
iv.setFitWidth(100);
iv.setFitHeight(100);
}
public static void loadImage(String input) {
sprite = new Image(input);
}
//Player class constructor
package Entities;
import application.Vector2;
import javafx.scene.Group;
public class Player extends Entity{
private boolean north, west, south, east;
private boolean shootN, shootW, shootS, shootE;
private Vector2 pInput;
public Player(float px, float py,Group r) {
super(px,py,r,getSprite());
north = false;
west = false;
south = false;
east = false;
pInput = new Vector2(0,0);
setHp(10);
}
}
//Projectile Constructor
package Entities;
import java.util.ArrayList;
import application.Vector2;
import javafx.scene.Group;
public class Projectile extends Entity{
private double speed;
Entity parent;
int lifespan;
public Projectile(float px, float py,Group r,Entity p,Vector2 d){
super(px,py,r,getSprite());
speed = 5;
dir = new Vector2(d.x,d.y);
dir.x += p.dir.x/30;
dir.y += p.dir.y/30;
lifespan = 100;
parent = p;
}
}

Animating a sprite with JavaFX on key input

I'm new to JavaFX but have a good understanding of object orientated Java. The following program is a combination of two examples, one that animates and moves shapes , the other animates an object on a mouse button press. Much of the functionality has been removed or changed for my needs.
I've searched through many examples but haven't found one I fully understand regarding moving a sprite and animating on key press.In my program I'm sure that I'm not using the right classes to create the game object, even though with some tweaking I'm sure it could work.
I added some println functions to test the animation. The problem seems to be that the KeyFrame part in the walkSouth animation isn't working/playing.
My question is:
Should I be using different JavaFX classes to create the sprite-sheet animation?
Can this code be easily adapted to function so I can get a better understanding of how JavaFX works.
Here is the main class:
package testing;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.layout.Pane;
import javafx.stage.Stage;
import javafx.util.Duration;
public class Main extends Application {
private enum UserAction{
NONE,NORTH,SOUTH;
}
private static int APP_W = 200;
private static int APP_H = 200;
private Scene scene;
private UserAction action = UserAction.NONE;
private Timeline timeline = new Timeline();
private boolean running = true;
private int FPS = 60;
private Parent createContent(){
Pane root = new Pane();
root.setPrefSize(APP_W,APP_H);
Image cat_image = new Image("file:res/cata.png");
GameObject obj = new GameObject(cat_image,12,8);
obj.setTranslateX(100);
obj.setTranslateY(100);
KeyFrame frame = new KeyFrame(Duration.millis(1000/FPS), event -> {
if(!running)
return;
switch(action){
case NORTH:
obj.setTranslateY(obj.getTranslateY()-1);
break;
case SOUTH:
obj.walkSouth();
obj.setTranslateY(obj.getTranslateY()+1);
break;
case NONE:
obj.pauseAnimation();
break;
}
});
timeline.getKeyFrames().add(frame);
timeline.setCycleCount(Timeline.INDEFINITE);
root.getChildren().add(obj);
return root;
}
private void restartGame(){
stopGame();
startGame();
}
private void stopGame(){
running = false;
timeline.stop();
}
private void startGame(){
timeline.play();
running = true;
}
public void start(Stage primaryStage) throws Exception{
scene = new Scene(createContent());
scene.setOnKeyPressed(event -> {
switch (event.getCode()) {
case W:
action = UserAction.NORTH;
break;
case S:
action = UserAction.SOUTH;
break;
}
});
scene.setOnKeyReleased(event -> {
switch (event.getCode()) {
case W:
action = UserAction.NONE;
break;
case S:
action = UserAction.NONE;
break;
}
});
primaryStage.setTitle("Simple Animation");
primaryStage.setScene(scene);
primaryStage.show();
startGame();
}
public static void main(String[] args) {
launch(args);
}
}
Here is the GameObject class:
package testing;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.geometry.Rectangle2D;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.Pane;
import javafx.util.Duration;
/**
* Created by matt on 26/02/17.
*/
public class GameObject extends Pane {
ObjectImage objectImage;
public GameObject( Image image, int columns, int rows){
objectImage = new ObjectImage(image,columns,rows);
getChildren().setAll(objectImage);
}
public void pauseAnimation(){
getChildren().setAll(objectImage);
objectImage.pauseAnimation();
}
public void walkSouth(){
getChildren().setAll(objectImage);
objectImage.walkSouth();
}
}
class ObjectImage extends ImageView {
private Rectangle2D[] clips;
private double width,height;
private Timeline timeline = new Timeline();
public ObjectImage(Image image,int columns,int rows){
width = image.getWidth()/columns;
height = image.getHeight()/rows;
clips = new Rectangle2D[rows*columns];
int count=0;
for(int row =0;row < rows;row++ )
for(int column = 0 ; column < columns; column++,count++)
clips[count] = new Rectangle2D(width * column, height * row,width,height);
setImage(image);
setViewport(clips[0]);
}
public void pauseAnimation(){
timeline.pause();
}
public void walkSouth(){
System.out.println("walk south test");
IntegerProperty count = new SimpleIntegerProperty(0);
KeyFrame frame = new KeyFrame( Duration.millis(1000/5), event -> {
if(count.get() < 2) count.set(count.get()+1);
else count.set(0);
setViewport(clips[count.get()]);
System.out.println("frame test");
});
timeline.setCycleCount(timeline.INDEFINITE);
timeline.getKeyFrames();
timeline.play();
}
}
This is the sprite-sheet image I'm working with
This is the outcome
As hinted by the comment, you did forget to add the frame in the walkSouth method. (Also you set each picture frame in walkSouth method to 200ms. Did you meant to change that?) Here's the code after changing:
public void walkSouth(){
System.out.println("walk south test");
IntegerProperty count = new SimpleIntegerProperty(0);
KeyFrame frame = new KeyFrame( Duration.millis(1000/FPS), event -> {
if(count.get() < 2) count.set(count.get()+1);
else count.set(0);
setViewport(clips[count.get()]);
});
timeline.setCycleCount(timeline.INDEFINITE);
timeline.getKeyFrames().add(frame); //This was the offending line.
timeline.play();
}
To answer your first question, yes there are many other options of classes you could use. Two options you could do is use the AnimationTimer or the Transition class. Here's a brief explanation for both (with code samples).
AnimationTimer is called every cycle or frame of rendering, which I believe you might be wanting this one:
public void walkSouth(){
System.out.println("walk south test");
IntegerProperty count = new SimpleIntegerProperty(0);
AnimationTimer tmr = new AnimationTimer() {
#Override
public void handle(long nanoTime)
{
//nanoTime specifies the current time at the beginning of the frame in nano seconds.
if(count.get() < 2) count.set(count.get()+1);
else count.set(0);
setViewport(clips[count.get()]);
}
};
tmr.start();
//call tmr.stop() to stop/ pause timer.
}
If however, you don't want an animation to be called each frame, you could extend Transition. A transition has an frac (fractional) value ranging from 0 to 1 that increases with respect to time. I'm not going to go into a whole lot detail, but I'm sure you could look up some more information on the api.
public void walkSouth(){
System.out.println("walk south test");
IntegerProperty count = new SimpleIntegerProperty(0);
Transition trans = new Transition() {
{
setCycleDuration(Duration.millis(1000 / 60.0));
}
#Override
public void interpolate(double frac)
{
if (frac != 1)
return;
//End of one cycle.
if(count.get() < 2) count.set(count.get()+1);
else count.set(0);
setViewport(clips[count.get()]);
}
};
trans.setCycleCount(Animation.INDEFINITE);
trans.playFromStart();
//Use trans.pause to pause, trans.stop to stop.
}

javafx: How to add an appropriate listener to an ensemble demo?

I'm new to javafx and browsed through the demos provided by oracle, especially I found this:
package ensemble.samples.graphics2d.images.imageoperator;
import javafx.application.Application;
import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.image.PixelWriter;
import javafx.scene.image.WritableImage;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
public class ImageOperationApp extends Application {
private SimpleDoubleProperty gridSize = new SimpleDoubleProperty(3.0);
private SimpleDoubleProperty hueFactor = new SimpleDoubleProperty(12.0);
private SimpleDoubleProperty hueOffset = new SimpleDoubleProperty(240.0);
private static void renderImage(WritableImage img, double gridSize, double hueFactor, double hueOffset) {
PixelWriter pw = img.getPixelWriter();
double w = img.getWidth();
double h = img.getHeight();
double xRatio = 0.0;
double yRatio = 0.0;
double hue = 0.0;
for (int y = 0; y < h; y++) {
for (int x = 0; x < w; x++) {
xRatio = x/w;
yRatio = y/h;
hue = Math.sin(yRatio*(gridSize*Math.PI))*Math.sin(xRatio*(gridSize*Math.PI))*Math.tan(hueFactor/20.0)*360.0 + hueOffset;
Color c = Color.hsb(hue, 1.0, 1.0);
pw.setColor(x, y, c);
}
}
}
public Parent createContent() {
StackPane root = new StackPane();
final WritableImage img = new WritableImage(200, 200);
gridSize.addListener((Observable observable) -> {
renderImage(img, gridSize.doubleValue(), hueFactor.doubleValue(), hueOffset.doubleValue());
});
hueFactor.addListener((Observable observable) -> {
renderImage(img, gridSize.doubleValue(), hueFactor.doubleValue(), hueOffset.doubleValue());
});
hueOffset.addListener((Observable observable) -> {
renderImage(img, gridSize.doubleValue(), hueFactor.doubleValue(), hueOffset.doubleValue());
});
renderImage(img, 3.0, 12.0, 240.0);
ImageView view = new ImageView(img);
root.getChildren().add(view);
return root;
}
#Override public void start(Stage primaryStage) throws Exception {
primaryStage.setScene(new Scene(createContent()));
primaryStage.show();
}
/** Java main for when running without JavaFX launcher
* #param args command line arguments
*/
public static void main(String[] args) { launch(args); }
}
1. This is selfcontained and runable.
2. In opposite to the container application for the demos, "ensemle.jar", which provides a "playground" with some sliders for the three SimpleDoubleProperties, here are no sliders cf. the screenshot of ensemble .
3. In order to get an idea how the event-handling with FX works (and to enjoy this nice application) I would like to add appropriate keylisteners to imitate the sliders.
I have no idea where to add the listeners and where to process the events fired by the keyboard, but guess, that there are missing only some lines of code.
Edit: I would be happy if I had a hint, where (and how) to insert a keylistener, so that typing "Y" would give me a "HelloWorld" in the console. I'm confident to do the rest myself.
Adjust your start method like this and you will get a message whenever you press the Y-key.
#Override public void start(Stage primaryStage) throws Exception {
Scene scene = new Scene(createContent());
scene.setOnKeyPressed(new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent event) {
if(event.getCode()== KeyCode.Y){
System.out.println("got a Y");
}
}
});
primaryStage.setScene(scene);
primaryStage.show();
}
If you want to do sth else, I would suggest to look into all the setOn...-methods applicable for scene in the javadocs.

Trying to use a JScrollPane to display an array of strings but i keep getting an error

import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JList;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
public class SavingAccountFrame extends JFrame {
private static final int FRAME_WIDTH = 300;
private static final int FRAME_LENGTH = 500;
private static final double INITIAL_BALANCE = 0.0;
private static final double ANNUAL_RATE = 0.0;
private static final int YEARS = 0;
String[] result;
private JLabel initialLabel;
private JLabel rate;
private JLabel years;
private JTextField initialBal;
private JTextField annualRate;
private JTextField numOfYears;
private JButton calculate;
private JPanel panel;
private JList box;
private JScrollPane scroll;
SavingAccountFrame(){
createTextField();
createButton();
createScrollPane();
createPanel();
setSize(FRAME_WIDTH, FRAME_LENGTH);
}
private void createTextField(){
final int FIELD_WIDTH = 10;
initialLabel = new JLabel("Initial Balance");
initialBal = new JTextField(FIELD_WIDTH);
rate = new JLabel("Annual Rate");
annualRate = new JTextField(FIELD_WIDTH);
years = new JLabel("Number of Years");
numOfYears = new JTextField(FIELD_WIDTH);
}
private void createButton(){
calculate = new JButton("Calculate");
class CalcListener implements ActionListener
{
public void actionPerformed(ActionEvent event)
{
double iB = Double.parseDouble(initialBal.getText());
double r = Double.parseDouble(rate.getText());
int y = Integer.parseInt(years.getText());
r = r / 100;
for (int i = 0; i < y; i++) {
double newbalance = iB * r;
iB += newbalance;
String test = String.valueOf(iB);
result[i] = test;
}
box = new JList(result);
scroll = new JScrollPane(box);
getContentPane().add(scroll);
}
}
ActionListener d = new CalcListener();
calculate.addActionListener(d);
}
private void createScrollPane(){
scroll = new JScrollPane();
}
private void createPanel()
{
panel = new JPanel();
panel = new JPanel();
panel.add(initialLabel);
panel.add(initialBal);
panel.add(rate);
panel.add(annualRate);
panel.add(years);
panel.add(numOfYears);
panel.add(calculate);
panel.add(scroll);
add(panel);
}
}
import javax.swing.JFrame;
public class SavingAccount {
public static void main(String[] args) {
JFrame frame = new SavingAccountFrame();
frame.setTitle("Savings Account");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
}
i am having a bit of homework trouble and my code keeps spitting out this error when i press the calculate button.
Exception in thread "AWT-EventQueue-0" java.lang.NumberFormatException: For input string: "Annual Rate"
at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1242)
at java.lang.Double.parseDouble(Double.java:527)
at SavingAccountFrame$1CalcListener.actionPerformed(SavingAccountFrame.java:54)
at javax.swing.AbstractButton.fireActionPerformed(AbstractButton.java:2012)
at javax.swing.AbstractButton$Handler.actionPerformed(AbstractButton.java:2335)
at javax.swing.DefaultButtonModel.fireActionPerformed(DefaultButtonModel.java:404)
at javax.swing.DefaultButtonModel.setPressed(DefaultButtonModel.java:259)
at javax.swing.plaf.basic.BasicButtonListener.mouseReleased(BasicButtonListener.java:252)
at java.awt.Component.processMouseEvent(Component.java:6268)
at javax.swing.JComponent.processMouseEvent(JComponent.java:3267)
at java.awt.Component.processEvent(Component.java:6033)
at java.awt.Container.processEvent(Container.java:2045)
at java.awt.Component.dispatchEventImpl(Component.java:4629)
at java.awt.Container.dispatchEventImpl(Container.java:2103)
at java.awt.Component.dispatchEvent(Component.java:4455)
at java.awt.LightweightDispatcher.retargetMouseEvent(Container.java:4633)
at java.awt.LightweightDispatcher.processMouseEvent(Container.java:4297)
at java.awt.LightweightDispatcher.dispatchEvent(Container.java:4227)
at java.awt.Container.dispatchEventImpl(Container.java:2089)
at java.awt.Window.dispatchEventImpl(Window.java:2517)
at java.awt.Component.dispatchEvent(Component.java:4455)
at java.awt.EventQueue.dispatchEventImpl(EventQueue.java:649)
at java.awt.EventQueue.access$000(EventQueue.java:96)
at java.awt.EventQueue$1.run(EventQueue.java:608)
at java.awt.EventQueue$1.run(EventQueue.java:606)
at java.security.AccessController.doPrivileged(Native Method)
at java.security.AccessControlContext$1.doIntersectionPrivilege(AccessControlContext.java:105)
at java.security.AccessControlContext$1.doIntersectionPrivilege(AccessControlContext.java:116)
at java.awt.EventQueue$2.run(EventQueue.java:622)
at java.awt.EventQueue$2.run(EventQueue.java:620)
at java.security.AccessController.doPrivileged(Native Method)
at java.security.AccessControlContext$1.doIntersectionPrivilege(AccessControlContext.java:105)
at java.awt.EventQueue.dispatchEvent(EventQueue.java:619)
at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:275)
at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:200)
at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:190)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:185)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:177)
at java.awt.EventDispatchThread.run(EventDispatchThread.java:138)
Can someone please clarify for me what this exception is and how to fix it. I can't pinpoint where the seems to point null or where the format is incorrect. Please and thank you
The error is here
double r = Double.parseDouble(rate.getText());
in createButton.
instead you should use annualRate.parseDouble.
because rate is a JLabel but not the textfield.
rate = new JLabel("Annual Rate");
When you try to parse the "Annual Rate" into a number, it will give you java.lang.NumberFormatException

JavaFX : Canvas to Image in non GUI Thread

I have to visualize lot of data (real-time) and I am using JavaFX 2.2. So I have decided to "pre-visualize" data before they are inserted into GUI thread.
In my opinion the fastest way to do it (with antialliasing etc.) is let some NON GUI thread to generate image/bitmap and then put in GUI thread (so the UI is still responsive for user).
But I can't find way how to conver Canvas to Image and then use:
Image imageToDraw = convert_tmpCanvasToImage(tmpCanvas);
Platform.runLater(new Runnable() {
#Override
public void run() {
canvas.getGraphicsContext2D().drawImage(imageToDraw, data.offsetX, data.offsetY);
}
});
Thx for some usable answers. :-)
btw: I have made test app to show my problem.
package canvasandthreads02;
import java.util.Random;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.Button;
import javafx.scene.image.Image;
import javafx.scene.layout.AnchorPane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
public class CanvasAndThreads02 extends Application {
#Override
public void start(Stage primaryStage) {
Button btn = new Button();
btn.setText("Paint");
final AnchorPane root = new AnchorPane();
final Canvas canvas = new Canvas(900, 800);
canvas.setLayoutX(50);
canvas.setLayoutY(50);
root.getChildren().add(canvas);
root.getChildren().add(btn);
Scene scene = new Scene(root, 900, 800);
primaryStage.setTitle("Painting in JavaFX");
primaryStage.setScene(scene);
primaryStage.show();
btn.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
System.out.println("Start painting");
/**
* Start Thread where some data will be visualized
*/
new Thread(new PainterThread(canvas, new DataToPaint())).start();
}
});
}
private class PainterThread implements Runnable{
private final DataToPaint data;
private final Canvas canvas;
public PainterThread(Canvas canvas, DataToPaint data){
this.canvas = canvas;
this.data = data;
}
#Override
public void run() {
long currentTimeMillis = System.currentTimeMillis();
Canvas tmpCanvas = new Canvas(data.width, data.height);
GraphicsContext graphicsContext2D = tmpCanvas.getGraphicsContext2D();
graphicsContext2D.setFill(data.color;);
for (int i = 0; i < data.height; i++) {
for (int j = 0; j < data.width; j++) {
graphicsContext2D.fillRect(j, i, 1, 1); //draw 1x1 rectangle
}
}
/**
* And now I need still in this Thread convert tmpCanvas to Image,
* or use some other method to put result to Main GIU Thread using Platform.runLater(...);
*/
final Image imageToDraw = convert_tmpCanvasToImage(tmpCanvas);
System.out.println("Canvas painting: " + (System.currentTimeMillis()-currentTimeMillis));
Platform.runLater(new Runnable() {
#Override
public void run() {
//Start painting\n Canvas painting: 430 \n Time to convert:62
//long currentTimeMillis1 = System.currentTimeMillis();
//Image imageToDraw = tmpCanvas.snapshot(null, null);
//System.out.println("Time to convert:" + (System.currentTimeMillis()-currentTimeMillis1));
canvas.getGraphicsContext2D().drawImage(imageToDraw, data.offsetX, data.offsetY);
}
});
}
}
private class DataToPaint{
double offsetX = 0;
double offsetY = 0;
Color color;
int width = 500;
int height = 250;
public DataToPaint(){
Random rand = new Random();
color = new Color(rand.nextDouble(), rand.nextDouble(), rand.nextDouble(), rand.nextDouble());
offsetX = rand.nextDouble() * 20;
offsetY = rand.nextDouble() * 20;
}
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}
use Canvas' snapshot(...) method to create a WritableImage from the Canvas' content. ^^
Works fine for me.
I know this is a really old question, but just for anyone who cares:
There is now a second version of canvas.snapshot that takes a callback and works asynchronously!
public void snapshot(Callback<SnapshotResult,Void> callback,
SnapshotParameters params,
WritableImage image)

Resources