How to animate texture coordinates in javafx - animation

Is there some kind of animation for liquid surfaces that mimics waves by animating uvcoordinates like in the picture below?
Can this method be recreated in the JavaFX framework?

animating by replacing textcoordinate values
Animating by replacing texture coordinate values . In this aproach , an integer property changes its value over time with the help of timeline object . there is a listener that will trigger an update in texture u and v coordinate values every time integer property changes . The result will remap normalmap over time bringing a sense of motion.
As you can see the animation only moves in one direction . even if the normal map is seamless tile texture ; this animation is not . it will be reset every 20 seconds . this aproach need improvements , but it's a good starting point I think.
This is a single javafx functional javafx app you can try
normal map file is at this page
App.java
public class App extends Application {
private final float[] uvCoords = {3, 0, 3, 3, 0, 0, 0, 3};
private final IntegerProperty keyCycle = new SimpleIntegerProperty();
#Override
public void start(Stage stage) {
PerspectiveCamera camera = new PerspectiveCamera(true);
camera.setTranslateZ(-8);
camera.setTranslateY(10);
camera.setRotationAxis(Rotate.X_AXIS);
camera.setRotate(45);
PointLight pointLight = new PointLight(Color.LIGHTYELLOW);
pointLight.setTranslateZ(-2.5);
pointLight.setTranslateY(-1.5);
pointLight.setRotationAxis(Rotate.Y_AXIS);
pointLight.setQuadraticAttenuation(0.1);
MeshView meshView = makeMeshView();
PhongMaterial material = new PhongMaterial(new Color(0, 1, 1, 0.5));
material.setSpecularColor(Color.LIGHTYELLOW);
material.setSpecularPower(512);
Image image = new Image("normal.jpg");
material.setBumpMap(image);
meshView.setMaterial(material);
makeCycle();
keyCycle.addListener(e -> {
float add = keyCycle.getValue() / 30000f;
TriangleMesh mesh = (TriangleMesh) meshView.getMesh();
for (int i = 0; i < uvCoords.length; i++) {
uvCoords[i] += add;
}
mesh.getTexCoords().set(0, uvCoords, 0, uvCoords.length);
});
Group group3d = new Group(camera, meshView, pointLight);
Scene scene = new Scene(group3d, 640, 480, true, SceneAntialiasing.BALANCED);
scene.setCamera(camera);
scene.setFill(Color.PERU);
stage.setTitle("Animating uv coordinates in javafx");
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch();
}
private MeshView makeMeshView() {
TriangleMesh mesh = new TriangleMesh();
MeshView mv = new MeshView(mesh);
mesh.getPoints().addAll(-3, -3, 0, 3, -3, 0, -3, 3, 0, 3, 3, 0);
mesh.getTexCoords().addAll(uvCoords);
mesh.getFaces().addAll(0, 0, 3, 3, 1, 1, 0, 0, 2, 2, 3, 3);
return mv;
}
private void makeCycle() {
KeyValue start = new KeyValue(keyCycle, 0, Interpolator.LINEAR);
KeyValue end = new KeyValue(keyCycle, 24 * 20, Interpolator.LINEAR);
KeyFrame kf = new KeyFrame(Duration.seconds(20), start, end);
Timeline tm = new Timeline(kf);
tm.setCycleCount(100);
tm.play();
}
}

Related

Cannon js move physical body with animation

I'm trying to figure this out for a while now and I just can't find a solution.
I'm working with three JS and the Cannon Library.
I created a simple object/ mesh with a tween animation. With the Cannon Library I added a physical body to the mesh.
I put an animation an the mesh so it changes the rotation but the rotation of the physical body just doesn't change at all.
second position
First position
This is where I define the physics:
class Physics {
constructor() {
this.world = new CANNON.World();
this.stepSize = 0;
this.timeToGo = 0;
this.visualObjects = [];
this.physicalBodies = [];
}
addPair(visualObject, body) {
this.visualObjects.push(visualObject);
this.physicalBodies.push(body);
}
initialize(gravityX, gravityY, gravityZ, stepsize, addfloor) {
this.world.gravity.set(gravityX, gravityY, gravityZ);
this.world.broadphase = new CANNON.NaiveBroadphase();
his.stepSize = stepsize;
if (addfloor) {
var floor = new CANNON.Body({
shape: new CANNON.Plane(),
mass: 0
});
floor.quaternion.setFromAxisAngle(new CANNON.Vec3(1, -0, 0), -Math.PI / 2);
floor.position.y = -32;
this.world.addBody(floor);
}
}
update(delta) {
// Step physics world forward
this.timeToGo += delta;
while (this.timeToGo >= this.stepSize) {
this.world.step(this.stepSize);
this.timeToGo -= this.stepSize;
}
// Copy transformations
for (var i = 0; i < this.visualObjects.length; i++) {
this.visualObjects[i].position.copy(this.physicalBodies[i].position);
this.visualObjects[i].quaternion.copy(this.physicalBodies[i].quaternion);
}
}
getWorld() {
return this.world;
}
addStandPhysical(visualObject, mass){
var shapeAblage = new CANNON.Box(new CANNON.Vec3(0.5*2,0.5*8,0.5*40));
var shapeMittelstrebe = new CANNON.Box(new CANNON.Vec3(0.5*10,0.5*1,0.5*100));
var shapeAblage3 = new CANNON.Box(new CANNON.Vec3(0.5*2.5,0.5*1,0.5*15));
var shapeAblage4 = new CANNON.Box(new CANNON.Vec3(0.5*2.5,0.5*1,0.5*15));
var shapeFuss5 = new CANNON.Box(new CANNON.Vec3(0.5*2.5,0.5*1,0.5*15));
var shapeMittelding = new CANNON.Cylinder(2,2,25,32)
var body = new CANNON.Body({ mass: mass });
body.addShape(shapeAblage, new CANNON.Vec3(-1.25, 29, 41), new CANNON.Quaternion().setFromEuler( 0, 90*DEG_TO_RAD,90*DEG_TO_RAD, "XYZ")); //ablage1
body.addShape(shapeAblage, new CANNON.Vec3(-1.25, 29, -41), new CANNON.Quaternion().setFromEuler( 0, 90*DEG_TO_RAD,90*DEG_TO_RAD, "XYZ")); //ablage2
body.addShape(shapeAblage, new CANNON.Vec3(-1.25, -29, -41), new CANNON.Quaternion().setFromEuler( 0, 90*DEG_TO_RAD,90*DEG_TO_RAD, "XYZ")); //ablage3
body.addShape(shapeAblage, new CANNON.Vec3(-1.25, -29, 41), new CANNON.Quaternion().setFromEuler( 0, 90*DEG_TO_RAD,90*DEG_TO_RAD, "XYZ")); //ablage4
body.addShape(shapeMittelstrebe, new CANNON.Vec3( 1.75, 0, 0), new CANNON.Quaternion().setFromEuler(35* DEG_TO_RAD, 0, 0, "XYZ"));
body.addShape(shapeMittelstrebe, new CANNON.Vec3( -1.75, 0, 0), new CANNON.Quaternion().setFromEuler(-35* DEG_TO_RAD, 0, 0, "XYZ"));
body.position.copy(visualObject.position);
body.quaternion.copy(visualObject.quaternion);
this.world.addBody(body);
this.addPair(visualObject, body);
}
}
And this is basically my main:
function main() {
scene = new THREE.Scene();
physics = new Physics();
physics.initialize(0, -200, 0, 1 / 120, floor = false); //Hier mit der Stepsize vielleicht noch ein wenig experimentieren und herausfinden, was sie macht
physicsVisualDebugger = new THREE.CannonDebugRenderer(scene, physics.getWorld());
var stand = new Stand();
physics.addStandPhysical(stand,3);
scene.add(stand);
var clock = new THREE.Clock();
function mainLoop() {
stats.begin();
var delta = clock.getDelta();
physics.update(delta);
physicsVisualDebugger.update();
stand.animations.forEach(function (animation) {
animation.update(delta);
});
TWEEN.update();
renderer.render(scene, camera);
stats.end();
requestAnimationFrame(mainLoop);
}
}
So long question short: how do I get the physical Bodies to move with my animation of the meshes to the ground?
I consider but maybe I am wrong, that your two arrays
this.visualObjects, this.physicalBodies
have not the same length. So maybe the update loop do not copy right the positions and quaternions. Better use the loop this way:
world.bodies.forEach( function(body) { ... }
to get looped exact the bodies.

ChartControl - How to plot a line on X Axis (x - fixed coordinate, y - infinity)

I'm developing a C# winforms application in which I want to plot lines on X axis such that the X coordinate is a fixed value, but there is no Y coordinate specified.
Something like this:
Can this be done?
Stripline did the trick!
Here's the code:
public Series series1 = new Series
{
Name = "Series1",
Color = Color.Black,
IsVisibleInLegend = false,
ChartType = SeriesChartType.Line,
BorderWidth = 0,
XValueType = ChartValueType.Double,
YValueType = ChartValueType.Double
};
public StripLine startPositionLine = new StripLine
{
BorderColor = Color.Red,
BorderWidth = 2,
IntervalOffset = 7
};
public StripLine endPositionLine = new StripLine
{
BorderColor = Color.Blue,
BorderWidth = 2,
IntervalOffset = 11
};
private void Form1_Load(object sender, EventArgs e)
{
chart1.Series.Clear();
chart1.ChartAreas[0].AxisX.Minimum = 0;
chart1.ChartAreas[0].AxisX.Interval = 1;
//Apparently some series with some points have to be present on the chart before the striplines get displayed
series1.Points.AddXY(0, 0);
series1.Points.AddXY(10, 0);
chart1.Series.Add(series1);
chart1.ChartAreas[0].AxisX.StripLines.Add(startPositionLine);
chart1.ChartAreas[0].AxisX.StripLines.Add(endPositionLine);
chart1.Invalidate();
}
And here's how the striplines are plotted:
One thing I need to check is whether or not a series needs to be plotted first before a stripline can be plotted.

PHASER ALTARA Struggling with motion paths, enemy ai and animations

Basically I want to make my sprite follow a motion path and depending on its direction it is going, it will play a particular animation. i.e. moving up will display its back, moving left will display the left side of the sprite and so on.
I've tried for hours but to no avail in trying to make this work. I had some luck using prototype but the final game will be using the structure below. ANY help will be appreciated.
/*
* initalise Phaser framework with width:960px, height:540px
*/
var game = new Phaser.Game(960, 540, Phaser.AUTO, 'gameContainer', { preload: preload, create: create, update: update, });
/*
* Preload runs before the game starts. Assets such as images and sounds such be preloaded here.
* A webserver is required to load assets.
*
* Also in this function we set game scale so it full browser width.
*/
function preload() {
// set to scale to full browser width
this.scale.scaleMode = Phaser.ScaleManager.SHOW_ALL;
this.scale.parentIsWindow = true;
//set the background color so can confirm the game renders in the browser
this.stage.backgroundColor = '#4488cc';
this.game.renderer.renderSession.roundPixels = true;
//preload images & sounds
//game.load.image('key', 'folder/filename.png');
//this.load.image('nazi', 'image/nazi.png');
game.load.spritesheet('nazi', 'images/nazi.png', 128, 128, 6);
this.bmd = null;
this.alien = null;
this.mode = 0;
//Use this website to set enemy movements http://phaser.io/waveforms. Export save data from the console log.
this.points = {
"type":0,"closed":true,"x":[120,120,260,260,200,180,120],"y":[368,108,108,308,308,368,368]
};
this.pi = 0;
this.path = [];
}
/*
* Add game variables here
*/
var nazi;
/*
* Create runs once only when Phaser first loads
* create the game scene by adding objects to the stage
*/
function create() {
bmd = this.add.bitmapData(this.game.width, this.game.height);
bmd.addToWorld();
/*
For testing
this.alien = this.add.sprite(0, 0, 'alien');
this.alien.anchor.set(0.5);
*/
this.nazi = this.add.sprite(0, 0, 'nazi');
this.nazi.anchor.set(0.5);
var py = this.points.y;
/*Original Code
//define the animation
nazi.animations.add('walk');
//start the animation at 30fps
nazi.animations.play('walk', 3, true);
*/
//define the animation
this.nazi.animations.add('walkDown', [2, 3]);
//start the animation at 30fps
this.nazi.animations.play('walkDown', 3, true);
//define the animation
this.nazi.animations.add('walkLR', [4, 5]);
//start the animation at 30fps
this.nazi.animations.play('walkLR', 3, true);
//define the animation
this.nazi.animations.add('walkUp', [0, 1]);
//start the animation at 30fps
this.nazi.animations.play('walkUp', 3, true);
}
function plot() {
this.bmd.clear();
this.path = [];
/*ROTATION CODE*/
var ix = 0;
/**/
//Sets the speed of the sprite
var x = 0.5 / game.width;
//looping through plotting points from x and y array
for (var i = 0; i <= 1; i += x) {
var px = this.math.linearInterpolation(this.points.x, i);
var py = this.math.linearInterpolation(this.points.y, i);
/* ROTATION CODE to follow direction of path*/
var node = { x: px, y: py, angle: 0 };
if (ix > 0)
{
node.angle = this.math.angleBetweenPoints(this.path[ix - 1], node);
}
this.path.push(node);
ix++;
/**/
//this.path.push( { x: px, y: py });
this.bmd.rect(px, py, 1, 1, 'rgba(255, 255, 255, 1)');
}
for (var p = 0; p < this.points.x.length; p++) {
this.bmd.rect(this.points.x[p]-3, this.points.y[p]-3, 6, 6, 'rgba(255, 0, 0, 1)');
}
}
/*
* Update runs continuously. Its the game loop function.
* Add collision detections and control events here
*/
function update() {
plot();
// Reset the players velocity (movement)
//this.nazi = 'nazi';
/* For Testing
this.alien.x = this.path[this.pi].x;
this.alien.y = this.path[this.pi].y;
//ROTATION CODE:
this.alien.rotation = this.path[this.pi].angle;
*/
this.nazi.x = this.path[this.pi].x;
this.nazi.y = this.path[this.pi].y;
//this.nazi.rotation = this.path[this.pi].angle;
this.pi++;
if (this.pi >= this.path.length)
{
this.pi = 0;
}
/*
// Flipping the player image based on the velocity
if(nazi.body.velocity.x > 0){
//player is moving right
nazi.scale.x = -1;
nazi.animations.play('walkLR');
}
else if(nazi.body.velocity.x < 0){
//player is moving left
nazi.scale.x = 1; //flip the image
nazi.animations.play('walkLR');
}
else if (nazi.body.velocity.y < 0){
nazi.animations.play('walkUp');
}
else if(nazi.body.velocity.y > 0){
//player is not moving
nazi.animations.play('walkDown');
}
*/
}

javafx 2.1 cannot update ImageView after editing its Image

I created an ImaveView and affect an Image object to it. The image is displayed correctly.
I also created a button that open up a new stage (with the image displayed inside) and some slidebars to make image processing.
So now after making some edits how can I update my ImageView with the new image ?
Here is what i have done:
//Boutton Traitement d'image
Button btnImageProcess = new Button("Traitement d'Image");
btnImageProcess.setOnAction(new EventHandler<ActionEvent>() {
public void handle(ActionEvent event) {
ImageProcess imageProcess = new ImageProcess();
imageImageProcess = new Image(ImagePathImageProcess);
imageViewImageProcess = ImageViewBuilder.create().image(imageImageProcess).build();
ColorAdjust colorAdjust = ColorAdjustBuilder.create().build();
imageViewImageProcess.setEffect(colorAdjust);
//
Label saturationLabel = LabelBuilder.create().text("Saturation").build();
GridPane.setConstraints(saturationLabel, 0, 0);
Slider saturationSlider = SliderBuilder.create().value(50).build();
colorAdjust.saturationProperty().bind(saturationSlider.valueProperty().divide(50).subtract(1));
GridPane.setConstraints(saturationSlider, 1, 0);
GridPane.setHgrow(saturationSlider, Priority.ALWAYS);
Label saturationValueLabel = LabelBuilder.create().minWidth(75).maxWidth(75).build();
saturationValueLabel.textProperty().bind(colorAdjust.saturationProperty().multiply(100).
asString("%.2f%%"));
GridPane.setConstraints(saturationValueLabel, 2, 0);
//
Label hueLabel = LabelBuilder.create().text("Hue").build();
GridPane.setConstraints(hueLabel, 0, 1);
Slider hueSlider = SliderBuilder.create().value(50).build();
colorAdjust.hueProperty().bind(hueSlider.valueProperty().divide(50).subtract(1));
GridPane.setConstraints(hueSlider, 1, 1);
GridPane.setHgrow(hueSlider, Priority.ALWAYS);
Label hueValueLabel = LabelBuilder.create().minWidth(75).maxWidth(75).build();
hueValueLabel.textProperty().bind(colorAdjust.hueProperty().multiply(100).asString("%.2f%%"));
GridPane.setConstraints(hueValueLabel, 2, 1);
//
Label brightnessLabel = LabelBuilder.create().text("Brightness").build();
GridPane.setConstraints(brightnessLabel, 0, 2);
Slider brightnessSlider = SliderBuilder.create().value(50).build();
colorAdjust.brightnessProperty().bind(brightnessSlider.valueProperty().divide(50).subtract(1));
GridPane.setConstraints(brightnessSlider, 1, 2);
GridPane.setHgrow(brightnessSlider, Priority.ALWAYS);
Label brightnessValueLabel = LabelBuilder.create().minWidth(75).maxWidth(75).build();
brightnessValueLabel.textProperty().bind(colorAdjust.brightnessProperty().multiply(100).
asString("%.2f%%"));
GridPane.setConstraints(brightnessValueLabel, 2, 2);
//
Label contrastLabel = LabelBuilder.create().text("Contrast").build();
GridPane.setConstraints(contrastLabel, 0, 3);
Slider contrastSlider = SliderBuilder.create().value(50).build();
colorAdjust.contrastProperty().bind(contrastSlider.valueProperty().divide(50).subtract(1));
GridPane.setConstraints(contrastSlider, 1, 3);
GridPane.setHgrow(contrastSlider, Priority.ALWAYS);
Label contrastValueLabel = LabelBuilder.create().minWidth(75).maxWidth(75).build();
contrastValueLabel.textProperty().bind(colorAdjust.contrastProperty().multiply(100).asString("%.2f%%"));
GridPane.setConstraints(contrastValueLabel, 2, 3);
//Validate Button
Button btnValider = new Button("Valider");
btnValider.setOnAction(new EventHandler<ActionEvent>() {
public void handle(ActionEvent event) {
imageProcessStage.hide();
ImageView imageView2 = new ImageView(imageViewImageProcess.getImage());
imageView.setImage(imageViewImageProcess.getImage());
}
});
GridPane.setConstraints(btnValider, 1, 4);
GridPane.setHgrow(btnValider, Priority.ALWAYS);
btnValider.setTranslateX(imageImageProcess.getWidth() / 12);
btnValider.setTranslateY(7);
//Validate Button
Button btnReset = new Button("Reset");
btnReset.setOnAction(new EventHandler<ActionEvent>() {
public void handle(ActionEvent event) {
ImageProcess ip = new ImageProcess();
ip.setImagePath(ImagePathImageProcess);
ip.start();
imageProcessStage.close();
}
});
GridPane.setConstraints(btnReset, 1, 4);
GridPane.setHgrow(btnReset, Priority.ALWAYS);
btnReset.setTranslateX(imageImageProcess.getWidth() / 12 + 82);
btnReset.setTranslateY(7);
//
GridPane sliderGrid = GridPaneBuilder.create().children(saturationLabel, saturationSlider, saturationValueLabel,
hueLabel, hueSlider, hueValueLabel,
brightnessLabel, brightnessSlider, brightnessValueLabel,
contrastLabel, contrastSlider, contrastValueLabel, btnValider, btnReset).build();
imageViewImageProcess.setFitWidth(imageImageProcess.getWidth() / 3);
imageViewImageProcess.setTranslateY(20);
//imageView.setFitHeight(image.getHeight()/2);
imageViewImageProcess.setPreserveRatio(true);
imageViewImageProcess.setSmooth(true);
imageViewImageProcess.setCache(true);
rootImageProcess = BorderPaneBuilder.create().center(imageViewImageProcess).top(sliderGrid).
build();
imageProcessStage.setTitle("Traitement d'image");
sceneImageProcess = new Scene(rootImageProcess);
sceneImageProcess.getStylesheets().add(ImageProcess.class.getResource("ImageProcess.css").
toExternalForm());
imageProcessStage.setScene(sceneImageProcess);
imageProcessStage.show();
}
});
You have...
ImageView imageView2 = new ImageView(imageViewImageProcess.getImage());
imageView.setImage(imageViewImageProcess.getImage());
That doesn't look useful

javafx 2.1 how to save an Image after modification using ColorAdjust

I just make a class that let me modify an image like saturation brightness, contrast and hue using ColorAjust Class.
But i dont know how to save that image after making those modifications.
Here is the code:
final Stage imageProcessStage = new Stage();
imageProcessStage.initModality(Modality.APPLICATION_MODAL);
imageImageProcess = new Image(ImagePathImageProcess);
imageViewImageProcess = ImageViewBuilder.create().image(imageImageProcess).build();
ColorAdjust colorAdjust = ColorAdjustBuilder.create().build();
imageViewImageProcess.setEffect(colorAdjust);
//
Label saturationLabel = LabelBuilder.create().text("Saturation").build();
GridPane.setConstraints(saturationLabel, 0, 0);
Slider saturationSlider = SliderBuilder.create().value(50).build();
colorAdjust.saturationProperty().bind(saturationSlider.valueProperty().divide(50).subtract(1));
GridPane.setConstraints(saturationSlider, 1, 0);
GridPane.setHgrow(saturationSlider, Priority.ALWAYS);
Label saturationValueLabel = LabelBuilder.create().minWidth(75).maxWidth(75).build();
saturationValueLabel.textProperty().bind(colorAdjust.saturationProperty().multiply(100).asString("%.2f%%"));
GridPane.setConstraints(saturationValueLabel, 2, 0);
//
Label hueLabel = LabelBuilder.create().text("Hue").build();
GridPane.setConstraints(hueLabel, 0, 1);
Slider hueSlider = SliderBuilder.create().value(50).build();
colorAdjust.hueProperty().bind(hueSlider.valueProperty().divide(50).subtract(1));
GridPane.setConstraints(hueSlider, 1, 1);
GridPane.setHgrow(hueSlider, Priority.ALWAYS);
Label hueValueLabel = LabelBuilder.create().minWidth(75).maxWidth(75).build();
hueValueLabel.textProperty().bind(colorAdjust.hueProperty().multiply(100).asString("%.2f%%"));
GridPane.setConstraints(hueValueLabel, 2, 1);
//
Label brightnessLabel = LabelBuilder.create().text("Brightness").build();
GridPane.setConstraints(brightnessLabel, 0, 2);
Slider brightnessSlider = SliderBuilder.create().value(50).build();
colorAdjust.brightnessProperty().bind(brightnessSlider.valueProperty().divide(50).subtract(1));
GridPane.setConstraints(brightnessSlider, 1, 2);
GridPane.setHgrow(brightnessSlider, Priority.ALWAYS);
Label brightnessValueLabel = LabelBuilder.create().minWidth(75).maxWidth(75).build();
brightnessValueLabel.textProperty().bind(colorAdjust.brightnessProperty().multiply(100).asString("%.2f%%"));
GridPane.setConstraints(brightnessValueLabel, 2, 2);
//
Label contrastLabel = LabelBuilder.create().text("Contrast").build();
GridPane.setConstraints(contrastLabel, 0, 3);
Slider contrastSlider = SliderBuilder.create().value(50).build();
colorAdjust.contrastProperty().bind(contrastSlider.valueProperty().divide(50).subtract(1));
GridPane.setConstraints(contrastSlider, 1, 3);
GridPane.setHgrow(contrastSlider, Priority.ALWAYS);
Label contrastValueLabel = LabelBuilder.create().minWidth(75).maxWidth(75).build();
contrastValueLabel.textProperty().bind(colorAdjust.contrastProperty().multiply(100).asString("%.2f%%"));
GridPane.setConstraints(contrastValueLabel, 2, 3);
//Validate Button
Button btnValider = new Button("Valider");
btnValider.setOnAction(new EventHandler<ActionEvent>() {
public void handle(ActionEvent event) {
// SAVE IMAGE HERE
}
});
Upgrade to JavaFX 2.2 and use the following code in your button event handler.
ImageIO.write(
SwingFXUtils.fromFXImage(
imageViewImageProcess.snapshot(null, null), null
),
"png",
new File("valider.png")
);
Note that the 2.2 is currently in developer preview rather than a GA product, so you may encounter some issues and bugs until the new 2.2 methods have been thoroughly QAed.
Here is a complete, executable example: https://gist.github.com/2870355
A general purpose and simpler version of the code:
private void saveImage(
Image i,
String extension,
String pathname)
throws IOException {
ImageIO.write(
SwingFXUtils.fromFXImage(
image,
null),
extension,
new File(
pathname));
}

Resources