JavaFX : Canvas to Image in non GUI Thread - image

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)

Related

JavaFX: Animation based on time function

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();}
}
});
}
}

How can i stop an element when it get the bounds of the screen in JavaFX?

I need to create an animation where there is a ball in the screen. I need to stop the ball one it gets the bound of the screen.
I've tried to use this solution:
boolean collision =
bullet.getBoundsInParent().getMaxX()>=View.getInstance().getXLimit() ||
bullet.getBoundsInLocal().getMaxY()>=View.getInstance().getYLimit();
In order to detect the collision, but it works only for a time! I've tried to relocate the ball by using the "relocate()" method, but when i class "getBoundsInParent().getMaxX()" it return the same value of the screen limit, and i can't restart the animation.
How can I solve the problem?
Since you did not provide any relevant code, I will use the example from here.
Replace
deltaX *= -1; and deltaY *= -1;
with
deltaX = 0;deltaY = 0;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Bounds;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;
import javafx.util.Duration;
public class GamePractice extends Application
{
public static Circle circle;
public static Pane canvas;
#Override
public void start(final Stage primaryStage)
{
canvas = new Pane();
final Scene scene = new Scene(canvas, 800, 600);
primaryStage.setTitle("Game");
primaryStage.setScene(scene);
primaryStage.show();
circle = new Circle(15, Color.BLUE);
circle.relocate(100, 100);
canvas.getChildren().addAll(circle);
final Timeline loop = new Timeline(new KeyFrame(Duration.millis(10), new EventHandler<ActionEvent>()
{
double deltaX = 3;
double deltaY = 3;
#Override
public void handle(final ActionEvent t)
{
circle.setLayoutX(circle.getLayoutX() + deltaX);
circle.setLayoutY(circle.getLayoutY() + deltaY);
final Bounds bounds = canvas.getBoundsInLocal();
final boolean atRightBorder = circle.getLayoutX() >= (bounds.getMaxX() - circle.getRadius());
final boolean atLeftBorder = circle.getLayoutX() <= (bounds.getMinX() + circle.getRadius());
final boolean atBottomBorder = circle.getLayoutY() >= (bounds.getMaxY() - circle.getRadius());
final boolean atTopBorder = circle.getLayoutY() <= (bounds.getMinY() + circle.getRadius());
if (atRightBorder || atLeftBorder) {
deltaX = 0;
deltaY = 0;
}
if (atBottomBorder || atTopBorder) {
deltaY = 0;
deltaX = 0;
}
}
}));
loop.setCycleCount(Timeline.INDEFINITE);
loop.play();
}
public static void main(final String[] args)
{
launch(args);
}
}

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.

fading in and fading out of an circle in javafx canvas

I would like to fade in and fadeout a circle in a javafx canvas.
I can move a circle from one part of the screen to another but i just can't seem to get this object to fade in and out.
Below is the code i used for moving a circle from one part of the screen to the other
public class AnimatedCircleOnCanvas extends Application {
public static final double W = 200; // canvas dimensions.
public static final double H = 200;
public static final double D = 20; // diameter.
#Override public void start(Stage stage) {
DoubleProperty x = new SimpleDoubleProperty();
DoubleProperty y = new SimpleDoubleProperty();
Timeline timeline = new Timeline(
new KeyFrame(Duration.seconds(0),
new KeyValue(x, 0),
new KeyValue(y, 0)
),
new KeyFrame(Duration.seconds(3),
new KeyValue(x, W - D),
new KeyValue(y, H - D)
)
);
timeline.setAutoReverse(true);
timeline.setCycleCount(Timeline.INDEFINITE);
final Canvas canvas = new Canvas(W, H);
AnimationTimer timer = new AnimationTimer() {
#Override
public void handle(long now) {
GraphicsContext gc = canvas.getGraphicsContext2D();
gc.setFill(Color.CORNSILK);
gc.fillRect(0, 0, W, H);
gc.setFill(Color.FORESTGREEN);
gc.fillOval(
x.doubleValue(),
y.doubleValue(),
D,
D
);
}
};
stage.setScene(
new Scene(
new Group(
canvas
)
)
);
stage.show();
timer.start();
timeline.play();
}
public static void main(String[] args) { launch(args); }
}
Your help is greatly appreciated
Animate a DoubleProperty for the opacity in values ranging from 1 to 0 in exactly the same way as you animated the properties for x and y, and then use Color.deriveColor(...) to set the color based on the changing property:
import javafx.animation.AnimationTimer;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import javafx.util.Duration;
public class FadeCircleOnCanvas extends Application {
public static final double W = 200; // canvas dimensions.
public static final double H = 200;
public static final double D = 20; // diameter.
#Override public void start(Stage stage) {
DoubleProperty opacity = new SimpleDoubleProperty();
Timeline timeline = new Timeline(
new KeyFrame(Duration.seconds(0),
new KeyValue(opacity, 1)
),
new KeyFrame(Duration.seconds(3),
new KeyValue(opacity, 0)
)
);
timeline.setAutoReverse(true);
timeline.setCycleCount(Timeline.INDEFINITE);
final Canvas canvas = new Canvas(W, H);
AnimationTimer timer = new AnimationTimer() {
#Override
public void handle(long now) {
GraphicsContext gc = canvas.getGraphicsContext2D();
gc.setFill(Color.CORNSILK);
gc.fillRect(0, 0, W, H);
gc.setFill(Color.FORESTGREEN.deriveColor(0, 1, 1, opacity.get()));
gc.fillOval(
W/2,
H/2,
D,
D
);
}
};
stage.setScene(
new Scene(
new Group(
canvas
)
)
);
stage.show();
timer.start();
timeline.play();
}
public static void main(String[] args) { launch(args); }
}
In general, I prefer not to use a Canvas for this, and just to use Shape instances placed in a Pane. Then you can directly change the pre-defined properties of the Shape, or in many cases use specific pre-defined animations (TranslateTransition or FadeTransition for example).
Here's the same example using this technique:
import javafx.animation.FadeTransition;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;
import javafx.util.Duration;
public class FadeCircleOnCanvas extends Application {
public static final double W = 200; // canvas dimensions.
public static final double H = 200;
public static final double D = 20; // diameter.
#Override public void start(Stage stage) {
Circle circle = new Circle(W/2, H/2, D, Color.FORESTGREEN);
FadeTransition fade = new FadeTransition(Duration.seconds(3), circle);
fade.setFromValue(1);
fade.setToValue(0);
fade.setAutoReverse(true);
fade.setCycleCount(Timeline.INDEFINITE);
Pane pane = new Pane(circle);
stage.setScene(
new Scene(
pane, W, H, Color.CORNSILK
)
);
stage.show();
fade.play();
}
public static void main(String[] args) { launch(args); }
}

Resources