i have this picture(all of these effects are in one .png file ) i want to display for example second picture how can i use Image and ImageView in javafx to display specific part of this image ? thanks
This answer is overkill. But with a nice set of images like you have in your question, maybe overkill is what is called for :-)
The fundamental design is the same as Uluk's, it just adjusts the Viewport of the ImageView rather than setting a clip, but the concept is the same.
Beware => Java 8
import javafx.animation.*;
import javafx.application.Application;
import javafx.beans.property.*;
import javafx.event.*;
import javafx.geometry.Rectangle2D;
import javafx.scene.Scene;
import javafx.scene.control.Control;
import javafx.scene.effect.*;
import javafx.scene.image.*;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import javafx.util.Duration;
class ExploadableImageView extends ImageView {
private final Rectangle2D[] cellClips;
private int numCells;
private final Duration FRAME_TIME = Duration.seconds(.5);
public ExploadableImageView(Image explosionImage, int numCells) {
this.numCells = numCells;
double cellWidth = explosionImage.getWidth() / numCells;
double cellHeight = explosionImage.getHeight();
cellClips = new Rectangle2D[numCells];
for (int i = 0; i < numCells; i++) {
cellClips[i] = new Rectangle2D(
i * cellWidth, 0,
cellWidth, cellHeight
);
}
setImage(explosionImage);
setViewport(cellClips[0]);
}
public void explode(EventHandler<ActionEvent> onFinished) {
final IntegerProperty frameCounter = new SimpleIntegerProperty(0);
Timeline kaboom = new Timeline(
new KeyFrame(FRAME_TIME, event -> {
frameCounter.set((frameCounter.get() + 1) % numCells);
setViewport(cellClips[frameCounter.get()]);
})
);
kaboom.setCycleCount(numCells);
kaboom.setOnFinished(onFinished);
kaboom.play();
}
}
class ExplodableItem extends StackPane {
public ExplodableItem(Image objectImage, Image explosionImage, int numCells) {
ImageView objectView = new ImageView(objectImage);
ExploadableImageView explosionView = new ExploadableImageView(
explosionImage, numCells
);
setMinSize(
Math.max(
objectImage.getWidth(),
explosionView.getViewport().getWidth()
),
Math.max(
objectImage.getHeight(),
explosionView.getViewport().getHeight()
)
);
objectView.setPickOnBounds(false);
objectView.setOnMouseClicked(event -> {
getChildren().setAll(explosionView);
explosionView.explode(complete -> getChildren().setAll(objectView));
});
DropShadow drop = new DropShadow(10, Color.GOLD);
drop.setInput(new Glow());
objectView.setOnMouseEntered(event -> objectView.setEffect(drop));
objectView.setOnMouseExited(event -> objectView.setEffect(null));
getChildren().setAll(objectView);
}
}
public class CatWhack extends Application {
public static void main(String[] args) {
launch(args);
}
private static final int NUM_CELLS_PER_EXPLOSION = 6;
#Override
public void start(Stage stage) {
Image objectImage = new Image("http://icons.iconarchive.com/icons/iconka/meow/96/cat-box-icon.png"); // cat icon linkware: backlink to http://www.iconka.com required
// looks likes imgur may have blocked direct access to following png from a Java app (somehow).
// but you can still download the QMqbQ.png from that location
// and save it locally in the same directory as the CatWhack program
// then pick it up by replacing the new Image call with:
// new Image(CatWhack.class.getResourceAsStream("QMqbQ.png"));
Image explosionImage = new Image("http://i.stack.imgur.com/QMqbQ.png");
TilePane tiles = new TilePane();
tiles.setPrefColumns(4);
for (int i = 0; i <16; i++) {
tiles.getChildren().add(
new ExplodableItem(objectImage, explosionImage, NUM_CELLS_PER_EXPLOSION)
);
}
tiles.setMinSize(Control.USE_PREF_SIZE, Control.USE_PREF_SIZE);
stage.setTitle("Cat Whack - Click a cat to whack it!");
stage.setScene(new Scene(tiles));
stage.show();
}
}
Simpler example
Here is the same concept as demonstrated in the above game, but just with a simpler system of an animated image which can be controlled via method calls rather than user mouse clicks on the image.
The animated image is similar to a Sprite. The code below is not meant to be a production quality Sprite system (probably a true Sprite system for a game would have more functions and features), it just demonstrates very simple display of an animated image based upon a Viewport.
import javafx.animation.*;
import javafx.application.Application;
import javafx.beans.property.*;
import javafx.geometry.*;
import javafx.scene.Scene;
import javafx.scene.control.ToggleButton;
import javafx.scene.image.*;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.Duration;
class Sprite extends ImageView {
private final Rectangle2D[] cellClips;
private int numCells;
private final Timeline timeline;
private final IntegerProperty frameCounter = new SimpleIntegerProperty(0);
public Sprite(Image animationImage, int numCells, Duration frameTime) {
this.numCells = numCells;
double cellWidth = animationImage.getWidth() / numCells;
double cellHeight = animationImage.getHeight();
cellClips = new Rectangle2D[numCells];
for (int i = 0; i < numCells; i++) {
cellClips[i] = new Rectangle2D(
i * cellWidth, 0,
cellWidth, cellHeight
);
}
setImage(animationImage);
setViewport(cellClips[0]);
timeline = new Timeline(
new KeyFrame(frameTime, event -> {
frameCounter.set((frameCounter.get() + 1) % numCells);
setViewport(cellClips[frameCounter.get()]);
})
);
}
public void playOnce() {
frameCounter.set(0);
timeline.setCycleCount(numCells);
timeline.stop();
timeline.playFromStart();
}
public void playContinuously() {
frameCounter.set(0);
timeline.setCycleCount(Timeline.INDEFINITE);
timeline.stop();
timeline.playFromStart();
}
public void stop() {
frameCounter.set(0);
setViewport(cellClips[frameCounter.get()]);
timeline.stop();
}
}
public class SpriteSample extends Application {
private static final int NUM_CELLS_PER_ANIMATION = 6;
private static final Duration FRAME_TIME = Duration.seconds(.5);
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage stage) {
// looks likes imgur may have blocked direct access to following png from a Java app (somehow).
// but you can still download the QMqbQ.png from that location
// and save it locally in the same directory as the CatWhack program
// then pick it up by replacing the new Image call with:
// new Image(Sprite.class.getResourceAsStream("QMqbQ.png"));
Image tilesheetImage = new Image(SpriteSample.class.getResourceAsStream("QMqbQ.png"));
Sprite sprite = new Sprite(tilesheetImage, NUM_CELLS_PER_ANIMATION, FRAME_TIME);
ToggleButton animationControl = new ToggleButton("Animate");
animationControl.setOnAction(event -> {
if (animationControl.isSelected()) {
animationControl.setText("Stop");
sprite.playContinuously();
} else {
animationControl.setText("Animate");
sprite.stop();
}
});
VBox layout = new VBox(10, sprite, animationControl);
layout.setPadding(new Insets(10));
layout.setAlignment(Pos.CENTER);
stage.setScene(new Scene(layout));
stage.show();
}
}
You can utilize the clip property of the Node, along with x property of the ImageView. Below is a demo showing the part of image in timeline, like a gif animated picture:
#Override
public void start(Stage stage) {
Group root = new Group();
Image image = new Image(this.getClass().getResource("your.png").toExternalForm());
final int numberOfFrames = 6; // in image
double frameWidth = image.getWidth() / numberOfFrames;
Scene scene = new Scene(root, frameWidth, image.getHeight());
final ImageView view = new ImageView(image);
Rectangle mask = new Rectangle(frameWidth, image.getHeight());
view.setClip(mask);
Timeline timeline = new Timeline();
for (int i = 0; i <= numberOfFrames; i++) {
KeyFrame kf = new KeyFrame(Duration.seconds(i), new KeyValue(view.xProperty(), -frameWidth * i, Interpolator.DISCRETE));
timeline.getKeyFrames().add(kf);
}
timeline.setCycleCount(Timeline.INDEFINITE);
timeline.play();
root.getChildren().add(view);
stage.setScene(scene);
stage.show();
}
As someone who is new to JavaFX, I had the same question. The question asks for much less than the above answers provide. We just want to manually select and display a portion of the image in an ImageView without animation. Here is the simplest solution wherein the user enters the image number and presses a button to display the desired image part.
package com.pakzaban;
import javafx.fxml.FXML;
import javafx.geometry.Rectangle2D;
import javafx.scene.control.TextField;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
public class Controller {
#FXML
public ImageView imageView;
public TextField numberField;
private final int IMAGE_WIDTH = 50;
private final int IMAGE_HEIGHT = 80;
private int imageNumber = 1;
public void onSetImagePressed(){
try {
imageNumber = Integer.parseInt(numberField.getText());
//SET THE WHOLE IMAGE INTO IMAGEVIEW
Image wholeImage = new Image("com/pakzaban/wholePicture.png");
imageView.setImage(wholeImage);
//SET THE VIEWPORT TO DESIRED PART OF THE IMAGE
Rectangle2D imagePart = new Rectangle2D((imageNumber - 1) * IMAGE_WIDTH, 0, IMAGE_WIDTH, IMAGE_HEIGHT);
imageView.setViewport(imagePart);
}
catch (Exception e){
e.printStackTrace();
}
}
}
Related
I am using Java FX (required for a class project), and I am struggling to format my MVC so that I can load multiple views onto the stage (various scenes). I have a Controller as seen below that has 2 instances of View1.
import javafx.application.Application;
import javafx.stage.Stage;
import javafx.animation.AnimationTimer;
public class Controller extends Application {
public int page = 0;
private View1 view2;
private View1 view1;
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage theStage) {
view1 = new View1(theStage);
view2 = new View1(theStage);
new AnimationTimer() {
public void handle(long currentNanoTime)
{
switch (page){
case 0:
view1.update();
break;
case 1:
view2.update();
break;
default:
page = 0;
}
try {
Thread.sleep(33);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}.start();
theStage.show();
}
}
The problem occurs at the line view2 = new View1(theStage);. Without this line, the output is a large canvas with a background image. With that line, however, there is no background image it is just a blank canvas. Below is my View I simplified it as much as possible, just one background image to determine if it's loaded correctly. (I left the imports out in order to keep it concise, if needed I can add them back in)
public class View1 {
Stage theStage;
Group root;
Scene theScene;
Canvas canvas;
// value of the height and width of screen
int canvasWidth = 1000;
int canvasHeight = 800;
GraphicsContext gc;
Image background;
//View1 constructor initialize the starting position for the image
//Called in controller
public View1(Stage theStage) {
this.theStage = theStage;
this.theStage.setTitle("Estuary Game");
root = new Group();
theScene = new Scene(root);
this.theStage.setScene(theScene);
canvas = new Canvas(canvasWidth, canvasHeight);
root.getChildren().add(canvas);
gc = canvas.getGraphicsContext2D();
background = createImage("assets/mini-game-1.png");
}
//Read image from file and return
private Image createImage(String image_file) {
Image img = new Image(image_file);
return img;
}
//method used to repaint on the image and called in controller
public void update() {
// draw background and sharkMove such like current
gc.drawImage(background, 0, 0);
}
}
I am not sure I am tackling the multiple views correctly, my intentions are to just have multiple scenes but I wasn't sure how to structure it.
Try the following structure (the following code is a one-file mre. Copy-paste it into SwitchScene.java and run) :
import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.image.ImageView;
import javafx.stage.Stage;
public class SwitchScene extends Application {
public int page = 0;
#Override
public void start(Stage theStage) {
new Controller(theStage);
theStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
class Controller {
private final View1 view1, view2;
private final Stage stage;
private static final String[] images = {
"https://findicons.com/files/icons/345/summer/128/cake.png",
"http://icons.iconarchive.com/icons/atyourservice/service-categories/128/Sweets-icon.png"
};
Controller(Stage stage) {
this.stage = stage;
view1 = new View1(images[0]);
view2 = new View1(images[1]);
swapScenes();
}
void swapScenes(){
new AnimationTimer() {
int page = 2;
#Override
public void handle(long currentNanoTime){
switch (page){
case 2:
page = 1;
stage.setScene(new Scene(new Group(view1)));
break;
case 1:
page = 2;
stage.setScene(new Scene(new Group(view2)));
break;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}.start();
}
}
class View1 extends ImageView {
public View1(String imagePath) {
super(imagePath);
}
}
Edit: a version of View1 using canvas:
class View1 extends Group {
public View1(String imagePath) {
Image img = new Image(imagePath);
Canvas canvas = new Canvas(img.getWidth(), img.getHeight());
GraphicsContext gc = canvas.getGraphicsContext2D();
gc.drawImage(img, 0, 0, canvas.getWidth(), canvas.getHeight());
getChildren().add(canvas);
}
}
I am trying to add a 'start' button to a small animation of the earth orbiting the sun. My problem is the AnimationTimer never seems to recieve the start() call when I click the button.
When the Event for clicking the button is fired the animation timer should start, thus making graphics context (which is on the canvas) start to be drawled on.
I'm sure I've made a stupid mistake somewhere, but for the life of me I can't figure it out.
Here's the code:
import javafx.application.Application;
import javafx.stage.Stage;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.image.Image;
import javafx.scene.control.Button;
import javafx.animation.AnimationTimer;
public class Orbit extends Application {
private static int WINDOW_WIDTH = 500;
private static int WINDOW_HEIGHT = 500;
private static int EARTH_RADIUS = 100;
private static int EARTH_ORBIT_RADIUS = 128;
private static int SUN_RADIUS = 100;
public static void main(String[] args) { launch(args); }
public void start(Stage stage) {
stage.setTitle("Orbit");
Group root = new Group();
Scene theScene = new Scene(root);
stage.setScene(theScene);
Canvas canvas = new Canvas(WINDOW_WIDTH, WINDOW_HEIGHT);
GraphicsContext gc = canvas.getGraphicsContext2D();
Image earth = new Image("earth.png", EARTH_RADIUS, EARTH_RADIUS, false, false)
, sun = new Image("sun.png", SUN_RADIUS, SUN_RADIUS, false, false)
, space = new Image("space.png");
final long startNanoTime = System.nanoTime();
final AnimationTimer timer = new AnimationTimer() {
#Override
public void handle(long currentNanoTime) {
double t = (currentNanoTime - startNanoTime) / 1000000000.0;
int centerX = (WINDOW_WIDTH - SUN_RADIUS) / 2
, centerY = (WINDOW_HEIGHT - SUN_RADIUS) / 2;
double x = centerX + EARTH_ORBIT_RADIUS * Math.cos(t)
, y = centerY + EARTH_ORBIT_RADIUS * Math.sin(t);
gc.drawImage(space, 0, 0);
gc.drawImage(earth, x, y);
gc.drawImage(sun, centerX, centerY);
}
};
Button btn = new Button("Start");
btn.setOnMousePressed(actionEvent -> timer.start());
root.getChildren().addAll(btn, canvas);
stage.show();
}
}
Your canvas is on top of the button, preventing the button from receiving mouse events.
You can either:
reverse the order you add the components, so that the button appears on top of the canvas:
root.getChildren().addAll(canvas, btn);
or make the canvas mouse-transparent (assuming you don't want to process mouse events on it):
canvas.setMouseTransparent(true);
or use a VBox instead of a Group, so the components don't overlay each other:
// Group root = new Group();
VBox root = new VBox();
The program I am working on simply paints over text or images by using a canvas layer over a StackPane. What I want to accomplish is that when I release the mouse the MouseEvent.MOUSE_RELEASED handler it will automatically get a snapshot of the Canvas, add the image to an ImageView Cover and display it on top of the TextArea, but it cannot add the changes to the class StackPane, namely the ImageView.
What I have here is a program that I will add to another one I'm working on, and I plan to take everything from the main class, TextCanvas, into the menu controller class from the main project.
Main class TextCanvas.java:
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.logging.Level;
import java.util.logging.Logger;
import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.embed.swing.SwingFXUtils;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.SnapshotParameters;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.Button;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.TextArea;
import javafx.scene.control.Toggle;
import javafx.scene.control.ToggleButton;
import javafx.scene.control.ToggleGroup;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.image.PixelReader;
import javafx.scene.image.PixelWriter;
import javafx.scene.image.WritableImage;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.stage.Screen;
import javafx.stage.Stage;
import javax.imageio.ImageIO;
public class TextCanvas extends Application
{
private ScrollPane Scroll = new ScrollPane();
Canvas Can = new Canvas(800, 400);
GraphicsContext GG = Can.getGraphicsContext2D();
TextArea TA = new TextArea();
ImageView Cover = new ImageView();
VBox ButtonBox = new VBox();
WordCanvas WC = new WordCanvas(Can, TA, GG);
#Override
public void start(Stage PrimaryStage)
{
ToggleGroup DrawErase = new ToggleGroup();
ToggleButton Draw = new ToggleButton("Draw");
ToggleButton Erase = new ToggleButton("Erase");
Button Clear = new Button("Clear");
Draw.setToggleGroup(DrawErase);
Erase.setToggleGroup(DrawErase);
double DotsPerInch = Screen.getPrimary().getDpi();
double W = DotsPerInch * 8.5;
double H = DotsPerInch * 11.0;
StackPane Stack = new WordCanvas(W, H);
WC.GetArea().setMaxWidth(W);
WC.GetArea().setMaxHeight(H);
WC.GetCan().setWidth(W);
WC.GetCan().setHeight(H);
DrawErase.selectedToggleProperty().addListener(new ChangeListener<Toggle>()
{
public void changed(ObservableValue<? extends Toggle> OV, Toggle TOld, Toggle TNew)
{
if(TNew == null)
{
GG.setStroke(Color.TRANSPARENT);
Stack.getChildren().remove(WC.GetCan());
}
else if(DrawErase.getSelectedToggle().equals(Draw))
{
GG.setStroke(Color.BLACK);
if(!Stack.getChildren().contains(WC.GetCan()))
{
Stack.getChildren().add(WC.GetCan());
}
}
else if(DrawErase.getSelectedToggle().equals(Erase))
{
GG.setStroke(Color.WHITE);
if(!Stack.getChildren().contains(WC.GetCan()))
{
Stack.getChildren().add(WC.GetCan());
}
}
}
});
Clear.setOnAction(new EventHandler<ActionEvent>()
{
#Override
public void handle(ActionEvent e)
{
WC.GetGC().clearRect(0, 0, W, H);
Stack.getChildren().remove(WC.GetCover());
}
});
Button Snap = new Button("Snap");
Snap.setOnAction(new EventHandler<ActionEvent>()
{
#Override
public void handle(ActionEvent e)
{
Cover.setMouseTransparent(true);
WritableImage WImage = Can.snapshot(new SnapshotParameters(), null);
ByteArrayOutputStream Bos = new ByteArrayOutputStream();
try
{
ImageIO.write(SwingFXUtils.fromFXImage(WImage, null), "png", Bos);
}
catch(IOException ex)
{
Logger.getLogger(WordCanvas.class.getName()).log(Level.SEVERE, null, ex);
}
InputStream Fis = new ByteArrayInputStream(Bos.toByteArray());
Image Imos = new Image(Fis);
int IW = (int) Imos.getWidth();
int IH = (int) Imos.getHeight();
WritableImage OutputImage = new WritableImage(IW, IH);
PixelReader PReader = Imos.getPixelReader();
PixelWriter PWriter = OutputImage.getPixelWriter();
for (int y = 0; y < IH; y++)
{
for (int x = 0; x < IW; x++)
{
int argb = PReader.getArgb(x, y);
int r = (argb >> 16) & 0xFF;
int g = (argb >> 8) & 0xFF;
int b = argb & 0xFF;
if(r >= 0xCF && g >= 0xCF && b >= 0xCF)
{
argb &= 0x00FFFFFF;
}
PWriter.setArgb(x, y, argb);
}
}
if(!Stack.getChildren().contains(WC.GetCover()))
{
WC.GetCover().setImage(OutputImage);
Stack.getChildren().add(WC.GetCover());
}
else
{
WC.GetCover().setImage(OutputImage);
}
}
});
ButtonBox.getChildren().addAll(Draw, Erase, Clear, Snap);
BorderPane Border = new BorderPane();
Border.setCenter(Stack);
Border.setBottom(ButtonBox);
Scroll.setContent(Border);
Scroll.setFitToWidth(true);
Scroll.setFitToHeight(true);
Scene MainScene = new Scene(Scroll);
PrimaryStage.setMaximized(true);
PrimaryStage.setTitle("Practice Canvas");
PrimaryStage.setScene(MainScene);
PrimaryStage.show();
}
public static void main(String[] args)
{
launch(args);
}
}
Secondary class that contains the TextArea and Canvas:
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.logging.Level;
import java.util.logging.Logger;
import javafx.embed.swing.SwingFXUtils;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.scene.Cursor;
import javafx.scene.SnapshotParameters;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.TextArea;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.image.PixelReader;
import javafx.scene.image.PixelWriter;
import javafx.scene.image.WritableImage;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Background;
import javafx.scene.layout.BackgroundFill;
import javafx.scene.layout.CornerRadii;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javax.imageio.ImageIO;
public class WordCanvas extends StackPane
{
private Canvas Can = new Canvas();
private GraphicsContext GC = Can.getGraphicsContext2D();
private TextArea Area = new TextArea();
private ImageView Cover = new ImageView();
double Width;
double Height;
public WordCanvas()
{
CreateUI();
}
public WordCanvas(double W, double H)
{
this.Width = W;
this.Height = H;
CreateUI();
}
public WordCanvas(ImageView IV)
{
this.Cover = IV;
}
public WordCanvas(Canvas C, TextArea TA, GraphicsContext GG)
{
this.Can = C;
this.Area = TA;
this.GC = GG;
CreateUI();
}
public void CreateUI()
{
Caligraphy();
Imagination();
Color C = Color.STEELBLUE;
BackgroundFill BFill = new BackgroundFill(C, CornerRadii.EMPTY, Insets.EMPTY);
Background BGround = new Background(BFill);
this.getChildren().addAll(Area);
this.setBackground(BGround);
}
public void Caligraphy()
{
Area.setMaxWidth(Width);
Area.setMaxHeight(Height);
}
public void Imagination()
{
double CanvasWidth = GC.getCanvas().getWidth();
double CanvasHeight = GC.getCanvas().getHeight();
GC.setFill(Color.TRANSPARENT);
GC.fillRect(0, 0, Can.getWidth(), Can.getHeight());
GC.rect(0, 0, CanvasWidth, CanvasHeight);
GC.setLineWidth(3);
Can.addEventHandler(MouseEvent.MOUSE_ENTERED, new EventHandler<MouseEvent>()
{
#Override
public void handle(MouseEvent event)
{
Can.setCursor(Cursor.CROSSHAIR);
}
});
Can.addEventHandler(MouseEvent.MOUSE_EXITED, new EventHandler<MouseEvent>()
{
#Override
public void handle(MouseEvent event)
{
Can.setCursor(Cursor.DEFAULT);
}
});
Can.addEventHandler(MouseEvent.MOUSE_PRESSED, new EventHandler<MouseEvent>()
{
#Override
public void handle(MouseEvent event)
{
GC.beginPath();
GC.lineTo(event.getX(), event.getY());
GC.moveTo(event.getX(), event.getY());
GC.stroke();
}
});
Can.addEventHandler(MouseEvent.MOUSE_DRAGGED, new EventHandler<MouseEvent>()
{
#Override
public void handle(MouseEvent event)
{
GC.lineTo(event.getX(), event.getY());
GC.stroke();
}
});
Can.addEventHandler(MouseEvent.MOUSE_RELEASED, new EventHandler<MouseEvent>()
{
#Override
public void handle(MouseEvent event)
{
ImageView IV = new ImageView();
WordCanvas Stack = new WordCanvas(IV);
Cover.setMouseTransparent(true);
WritableImage WImage = Can.snapshot(new SnapshotParameters(), null);
ByteArrayOutputStream Bos = new ByteArrayOutputStream();
try
{
ImageIO.write(SwingFXUtils.fromFXImage(WImage, null), "png", Bos);
}
catch(IOException ex)
{
Logger.getLogger(WordCanvas.class.getName()).log(Level.SEVERE, null, ex);
}
InputStream Fis = new ByteArrayInputStream(Bos.toByteArray());
Image Imos = new Image(Fis);
int IW = (int) Imos.getWidth();
int IH = (int) Imos.getHeight();
WritableImage OutputImage = new WritableImage(IW, IH);
PixelReader PReader = Imos.getPixelReader();
PixelWriter PWriter = OutputImage.getPixelWriter();
for (int y = 0; y < IH; y++)
{
for (int x = 0; x < IW; x++)
{
int argb = PReader.getArgb(x, y);
int r = (argb >> 16) & 0xFF;
int g = (argb >> 8) & 0xFF;
int b = argb & 0xFF;
if(r >= 0xCF && g >= 0xCF && b >= 0xCF)
{
argb &= 0x00FFFFFF;
}
PWriter.setArgb(x, y, argb);
}
}
if(!Stack.getChildren().contains(Cover))
{
Cover.setImage(OutputImage);
Stack.getChildren().add(Cover);
}
else
{
Cover.setImage(OutputImage);
}
}
});
}
public void SetCan(Canvas C)
{
this.Can = C;
}
public Canvas GetCan()
{
return Can;
}
public void SetGC(GraphicsContext GG)
{
this.GC = GG;
}
public GraphicsContext GetGC()
{
return GC;
}
public void SetArea(TextArea TA)
{
this.Area = TA;
}
public TextArea GetArea()
{
return Area;
}
public ImageView GetCover()
{
return Cover;
}
}
In the main class the Snap button handler does work as I intend to, but what I want is that in the secondary class the MouseEvent.MOUSE_RELEASED even handler automatically creates the snapshot and does what the Snap button in the main class does. However nothing I have tried works, and it won't even accept this.getChildren().add(Cover).
Another minor problem, I want the WritableImage to automatically become transparent with a more elegant solution. User #jewelsea gives a solution here which works perfectly but I would prefer something a bit shorter that doesn't have to read through every pixel. Existing png files do work as intended but when I make my own png files they are not transparent.
It's not clear why you're creating a canvas on every draw, play with the children of the parent, and why you need to capture the screen and create an image each time.
You simply want to draw over the text area, so have a single canvas over it on which you draw. The canvas's mouseTransparentProperty can be used to decide which layer gets the input.
public class TextCanvas extends Application {
private GraphicsContext gc;
#Override
public void start(Stage primaryStage) {
TextArea textArea = new TextArea();
Canvas canvas = createCanvas();
ToggleButton draw = new ToggleButton("Draw");
ToggleButton erase = new ToggleButton("Erase");
ToggleGroup drawErase = new ToggleGroup();
draw.setToggleGroup(drawErase);
erase.setToggleGroup(drawErase);
drawErase.selectedToggleProperty().addListener((ov, oldV, newV) -> {
if (newV == null) {
gc.setStroke(Color.TRANSPARENT);
canvas.setMouseTransparent(true);
} else if (drawErase.getSelectedToggle().equals(draw)) {
System.out.println("Fd");
gc.setStroke(Color.BLACK);
canvas.setMouseTransparent(false);
} else if (drawErase.getSelectedToggle().equals(erase)) {
gc.setStroke(Color.WHITE);
canvas.setMouseTransparent(false);
}
});
Button clear = new Button("Clear");
clear.setOnAction(e -> gc.clearRect(0, 0, canvas.getWidth(), canvas.getHeight()));
CheckBox cb = new CheckBox("Show canvas");
cb.setSelected(true);
canvas.visibleProperty().bind(cb.selectedProperty());
VBox buttonBox = new VBox(draw, erase, clear, cb);
StackPane stack = new StackPane(textArea, canvas);
ScrollPane scrollPane = new ScrollPane(stack);
scrollPane.setFitToHeight(true);
canvas.widthProperty().bind(scrollPane.widthProperty());
canvas.heightProperty().bind(scrollPane.heightProperty());
stack.setBackground(new Background(new BackgroundFill(Color.STEELBLUE, CornerRadii.EMPTY, Insets.EMPTY)));
BorderPane border = new BorderPane();
border.setCenter(scrollPane);
border.setBottom(buttonBox);
primaryStage.setMaximized(true);
primaryStage.setTitle("Practice Canvas");
primaryStage.setScene(new Scene(border));
primaryStage.show();
}
private Canvas createCanvas() {
Canvas canvas = new Canvas();
gc = canvas.getGraphicsContext2D();
gc.setLineWidth(3);
canvas.setOnMouseEntered(event -> canvas.setCursor(Cursor.CROSSHAIR));
canvas.setOnMouseExited(event -> canvas.setCursor(Cursor.DEFAULT));
canvas.setOnMousePressed(event -> {
gc.beginPath();
gc.lineTo(event.getX(), event.getY());
gc.moveTo(event.getX(), event.getY());
gc.stroke();
});
canvas.setOnMouseDragged(event -> {
gc.lineTo(event.getX(), event.getY());
gc.stroke();
});
canvas.setMouseTransparent(true);
return canvas;
}
public static void main(String[] args) {
launch(args);
}
}
Edit: I added a check box to toggle the canvas's visibility. You will have to clear up your usage requirements as to what each user operation does with each mode. In any case, this should be enough to play with.
Also, use proper Java naming conventions: local variables, (non-constant) field names and method argument should start with a lower case.
I am currently attempting to create a series of characters in a circle within an application of Java. Essentially, the phrase "Welcome to Java" will be placed in a circle, starting at the far right with W. As characters move down the circle, they are rotated in a fashion such that the bottoms of the letters are facing inwards, in order to form a circle.
My code seems to rotate the characters appropriately, but they will not appear anywhere other than the center of the pane. So my question is: How do you space the letters away from the center in a pane using javafx utilities? My code is below:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.stage.Stage;
import javafx.scene.layout.GridPane;
import javafx.scene.text.Font;
import javafx.scene.text.FontPosture;
import javafx.scene.text.FontWeight;
import javafx.scene.text.Text;
import javafx.geometry.Pos;
public class Characters extends Application{
#Override
public void start (Stage primaryStage){
//Create the pane
GridPane pane = new GridPane();
pane.setPrefSize(600, 600);
pane.setAlignment(Pos.CENTER);
//Font class instance
Font font = Font.font("Times New Roman", FontWeight.BOLD, FontPosture.REGULAR, 35);
//Welcome to Java string
String welcome = "Welcome to Java";
double rotation = 90;
double x = Math.cos(rotation)*100;
double y = Math.sin(rotation)*100;
//Loop
for (int i = 0; i < welcome.length(); i++){
x = Math.cos(Math.toRadians(rotation));
y = Math.sin(Math.toRadians(rotation));
System.out.println("Y: " + y);
System.out.println("X: " + x);
Text text = new Text(x, y, welcome.charAt(i)+"");
System.out.println("Actual X" + text.getX());
System.out.println("Actual Y" + text.getY());
text.setFont(font);
text.setRotate(rotation);
pane.getChildren().add(text);
rotation += 22.5;
}
//Create the scene for the application
Scene scene = new Scene(pane, 500, 500);
primaryStage.setTitle("Characters around circle");
primaryStage.setScene(scene);
//Display
primaryStage.show();
}
public static void main (String [] args){
launch(args);
}
}
This will get you a little bit closer to your desired result. There are several mistakes in there. You should not use a GridPane here because its layout will interfere with your text placement and your formula is wrong too.
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.text.Font;
import javafx.scene.text.FontPosture;
import javafx.scene.text.FontWeight;
import javafx.scene.text.Text;
import javafx.stage.Stage;
public class Characters extends Application {
#Override
public void start(Stage primaryStage) {
// Create the pane
// GridPane pane = new GridPane();
Pane pane = new Pane();
pane.setPrefSize(600, 600);
// pane.setAlignment(Pos.CENTER);
// Font class instance
Font font = Font.font("Times New Roman", FontWeight.BOLD, FontPosture.REGULAR, 35);
// Welcome to Java string
String welcome = "Welcome to Java";
double rotation = 90;
double offset = pane.getPrefWidth() / 2;
double radius = 100;
double x = offset + Math.cos(rotation) * radius;
double y = offset + Math.sin(rotation) * radius;
// Loop
for (int i = 0; i < welcome.length(); i++) {
x = offset + Math.cos(Math.toRadians(rotation)) * radius;
y = offset + Math.sin(Math.toRadians(rotation)) * radius;
System.out.println("Y: " + y);
System.out.println("X: " + x);
Text text = new Text(x, y, welcome.charAt(i) + "");
System.out.println("Actual X" + text.getX());
System.out.println("Actual Y" + text.getY());
text.setFont(font);
text.setRotate(rotation);
pane.getChildren().add(text);
rotation += 22.5;
}
// Create the scene for the application
// Scene scene = new Scene(pane, 500, 500);
Scene scene = new Scene(pane);
primaryStage.setTitle("Characters around circle");
primaryStage.setScene(scene);
// Display
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
GridPane will basically ignore your positioning and decide the position of it's children on it's own. In this case all Text elements are put in the center of the "cell" with column=0; row=0) of the GridPane.
I personally prefer putting the Text nodes in a Group and add the Group to the parent layout instead of placing it in a Parent with a complex layouting algorithm.
The rotation is slightly easier to achieve using a Rotate transform, since this allows you to specify the pivot point of the rotation:
#Override
public void start(Stage primaryStage) {
//Create the pane
GridPane pane = new GridPane();
pane.setAlignment(Pos.CENTER);
Group textGroup = new Group();
//Font class instance
Font font = Font.font("Times New Roman", FontWeight.BOLD, FontPosture.REGULAR, 35);
//Welcome to Java string
String welcome = "Welcome to Java";
double rotation = 90;
double radius = 100d;
//Loop
for (char c : welcome.toCharArray()) {
// ignore whitespace, otherwise add rotated char
if (!Character.isWhitespace(c)) {
Text text = new Text(Character.toString(c));
text.setFont(font);
Rotate rotationMatrix = new Rotate(rotation, 0, radius);
text.getTransforms().add(rotationMatrix);
textGroup.getChildren().add(text);
}
rotation += 22.5;
}
pane.getChildren().add(textGroup);
//Create the scene for the application
Scene scene = new Scene(pane, 500, 500);
primaryStage.setTitle("Characters around circle");
primaryStage.setScene(scene);
//Display
primaryStage.show();
}
Result
You can do it with a PathTransition, but there is no kerning for the Text. So the characters won't look pretty well placed, also I had to insert some whitespace. Maybe some other one will make it a bit nicer.
But as a nice add-on, if you comment the line timer.start();, then the text is rotating around the circle.
import javafx.animation.*;
import javafx.application.Application;
import javafx.collections.*;
import javafx.scene.*;
import javafx.scene.layout.AnchorPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.*;
import javafx.scene.text.Text;
import javafx.stage.Stage;
import javafx.util.Duration;
public class BezierTextPlotter extends Application {
private static final String CIRCLE_TEXT = " Welcome to Java ";
#Override
public void start(final Stage stage) throws Exception {
final Text text = new Text(CIRCLE_TEXT);
text.setStyle("-fx-font-size: 40px");
Circle circle = new Circle(200, 200, 100);
final ObservableList<Text> parts = FXCollections.observableArrayList();
final ObservableList<PathTransition> transitions
= FXCollections.observableArrayList();
for (char character : text.textProperty().get().toCharArray()) {
Text c = new Text(character + "");
c.setEffect(text.getEffect());
c.setStyle(text.getStyle());
parts.add(c);
transitions.add(createPathTransition(circle, c));
}
AnchorPane ap = new AnchorPane();
ap.getChildren().addAll(parts);
for (int i = 0; i < parts.size(); i++) {
final Transition t = transitions.get(i);
t.stop();
t.jumpTo(Duration.seconds(10).multiply((i + 0.5) * 1.0 / parts.size()));
AnimationTimer timer = new AnimationTimer() {
int frameCounter = 0;
#Override
public void handle(long l) {
frameCounter++;
if (frameCounter == 1) {
t.stop();
stop();
}
}
};
timer.start();
t.play();
}
stage.setTitle("Circle Text Sample");
stage.setScene(new Scene(ap, 400, 400, Color.ALICEBLUE));
stage.show();
}
private PathTransition createPathTransition(Shape shape, Text text) {
final PathTransition trans
= new PathTransition(Duration.seconds(10), shape, text);
trans.setAutoReverse(false);
trans.setCycleCount(PathTransition.INDEFINITE);
trans.setOrientation(PathTransition.OrientationType.ORTHOGONAL_TO_TANGENT);
trans.setInterpolator(Interpolator.LINEAR);
return trans;
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}
And with the line timer.start() commented:
JavaFX 2.x
What I want to do:
This script translated into Java source code. I tried that myself, but some of that stuff is deprecated (e.g. PerspectiveTransform#time - not found in JavaFX 2.2)
Flipping like this and like that.
What I don't want to do:
Use RotateTransition because it depends on the PerspectiveCamera. Since I'll have many flippable tiles next to each other, the front/back replacement halfway through the animation won't go well.
What I have so far:
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.effect.PerspectiveTransform;
import javafx.scene.effect.PerspectiveTransformBuilder;
import javafx.scene.image.ImageView;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import javafx.util.Duration;
/**
*
* #author ggrec
*
*/
public class FX_Tester extends Application
{
#Override
public void start(final Stage stage) throws Exception
{
final StackPane stackPane = new StackPane();
final ImageView img1 = new ImageView("http://img3.wikia.nocookie.net/__cb20120816162009/mario/images/thumb/1/15/MarioNSMB2.png/200px-MarioNSMB2.png");
final ImageView img2 = new ImageView("http://img2.wikia.nocookie.net/__cb20120518002849/mario/images/thumb/7/78/Tanooki_Mario_Artwork_-_Super_Mario_Bros._3.png/180px-Tanooki_Mario_Artwork_-_Super_Mario_Bros._3.png");
final FlipView flipPane = new FlipView(img1, img2);
stackPane.setOnMouseClicked(new EventHandler<MouseEvent>() {
#Override public void handle(final MouseEvent arg0)
{
flipPane.doFlip();
}
});
stackPane.getChildren().setAll(flipPane);
stage.setScene(new Scene(stackPane));
stage.show();
}
public static void main(final String[] args)
{
launch();
}
private class FlipView extends Group
{
private Node frontNode;
private Node backNode;
private boolean isFlipped = false;
private SimpleDoubleProperty time = new SimpleDoubleProperty(Math.PI / 2);
private Timeline anim = new Timeline(
new KeyFrame(Duration.ZERO, new KeyValue(time, Math.PI / 2)),
new KeyFrame(Duration.ONE, new KeyValue(time, - Math.PI / 2)),
new KeyFrame(Duration.ONE, new EventHandler<ActionEvent>() {
#Override public void handle(final ActionEvent arg0)
{
isFlipped = !isFlipped;
}
})
);
private FlipView(final Node frontNode, final Node backNode)
{
this.frontNode = frontNode;
this.backNode = backNode;
getChildren().setAll(frontNode, backNode);
frontNode.setEffect(getPT(time.doubleValue()));
backNode.setEffect(getPT(time.doubleValue()));
frontNode.visibleProperty().bind(time.greaterThan(0));
backNode.visibleProperty().bind(time.lessThan(0));
}
private PerspectiveTransform getPT(final double t)
{
final double width = 200;
final double height = 200;
final double radius = width / 2;
final double back = height / 10;
return PerspectiveTransformBuilder.create()
.ulx(radius - Math.sin(t)*radius)
.uly(0 - Math.cos(t)*back)
.urx(radius + Math.sin(t)*radius)
.ury(0 + Math.cos(t)*back)
.lrx(radius + Math.sin(t)*radius)
.lry(height - Math.cos(t)*back)
.llx(radius - Math.sin(t)*radius)
.lly(height + Math.cos(t)*back)
.build();
}
public void doFlip()
{
if (isFlipped)
{
anim.setRate(1.0);
anim.setDelay(Duration.ZERO);
}
else
{
anim.setRate(-1.0);
anim.setDelay(Duration.ONE);
}
anim.play();
}
}
}
After heavy R&D, I've managed to implement the flip functionality without PerspectiveCamera, using only PerspectiveTransform.
If you're too lazy to run this SSCCE, then go here to see a demo on how the below code works.
Q: But George, how is this different from the other methods???
A: Well, first of: since you're not using PerspectiveCamera, the user's perspective won't be affected if say you have 100 flipping tiles on the screen. Second and last: The back node is ALREADY flipped. So it's not mirrored, it's not rotate, it's not scaled. It's "normal". Ain't that great?
Cheers.
import javafx.animation.Interpolator;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.effect.PerspectiveTransform;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import javafx.util.Duration;
/**
*
* #author ggrec
*
*/
public class DFXFlipPaneTester extends Application
{
// ==================== 1. Static Fields ========================
/*
* Mmm... pie.
*/
private static final Double PIE = Math.PI;
private static final Double HALF_PIE = Math.PI / 2;
private static final double ANIMATION_DURATION = 10000;
private static final double ANIMATION_RATE = 10;
// ====================== 2. Instance Fields =============================
private Timeline animation;
private StackPane flipPane;
private SimpleDoubleProperty angle = new SimpleDoubleProperty(HALF_PIE);
private PerspectiveTransform transform = new PerspectiveTransform();
private SimpleBooleanProperty flippedProperty = new SimpleBooleanProperty(true);
// ==================== 3. Static Methods ====================
public static void main(final String[] args)
{
Application.launch(args);
}
// ==================== 5. Creators ====================
#Override
public void start(final Stage primaryStage) throws Exception
{
primaryStage.setScene(new Scene(createFlipPane()));
primaryStage.show();
}
private StackPane createFlipPane()
{
angle = createAngleProperty();
flipPane = new StackPane();
flipPane.setPadding(new Insets(30));
flipPane.setMinHeight(500);
flipPane.setMinWidth(500);
flipPane.getChildren().setAll(createBackNode(), createFrontNode());
flipPane.widthProperty().addListener(new ChangeListener<Number>() {
#Override public void changed(final ObservableValue<? extends Number> arg0, final Number arg1, final Number arg2)
{
recalculateTransformation(angle.doubleValue());
}
});
flipPane.heightProperty().addListener(new ChangeListener<Number>() {
#Override public void changed(final ObservableValue<? extends Number> arg0, final Number arg1, final Number arg2)
{
recalculateTransformation(angle.doubleValue());
}
});
return flipPane;
}
private StackPane createFrontNode()
{
final StackPane node = new StackPane();
node.setEffect(transform);
node.visibleProperty().bind(flippedProperty);
node.getChildren().setAll(createButton("Front Button")); //$NON-NLS-1$
return node;
}
private StackPane createBackNode()
{
final StackPane node = new StackPane();
node.setEffect(transform);
node.visibleProperty().bind(flippedProperty.not());
node.getChildren().setAll(createButton("Back Button")); //$NON-NLS-1$
return node;
}
private Button createButton(final String text)
{
final Button button = new Button(text);
button.setMaxHeight(Double.MAX_VALUE);
button.setMaxWidth(Double.MAX_VALUE);
button.setOnAction(new EventHandler<ActionEvent>() {
#Override public void handle(final ActionEvent arg0)
{
flip();
}
});
return button;
}
private SimpleDoubleProperty createAngleProperty()
{
// --------------------- <Angle> -----------------------
final SimpleDoubleProperty angle = new SimpleDoubleProperty(HALF_PIE);
angle.addListener(new ChangeListener<Number>() {
#Override public void changed(final ObservableValue<? extends Number> obsValue, final Number oldValue, final Number newValue)
{
recalculateTransformation(newValue.doubleValue());
}
});
return angle;
}
private Timeline createAnimation()
{
return new Timeline(
new KeyFrame(Duration.millis(0), new KeyValue(angle, HALF_PIE)),
new KeyFrame(Duration.millis(ANIMATION_DURATION / 2), new KeyValue(angle, 0, Interpolator.EASE_IN)),
new KeyFrame(Duration.millis(ANIMATION_DURATION / 2), new EventHandler<ActionEvent>() {
#Override public void handle(final ActionEvent arg0)
{
// TODO -- Do they another way or API to do this?
flippedProperty.set( flippedProperty.not().get() );
}
}),
new KeyFrame(Duration.millis(ANIMATION_DURATION / 2), new KeyValue(angle, PIE)),
new KeyFrame(Duration.millis(ANIMATION_DURATION), new KeyValue(angle, HALF_PIE, Interpolator.EASE_OUT))
);
}
// ==================== 6. Action Methods ====================
private void flip()
{
if (animation == null)
animation = createAnimation();
animation.setRate( flippedProperty.get() ? ANIMATION_RATE : -ANIMATION_RATE );
animation.play();
}
// ==================== 8. Business Methods ====================
private void recalculateTransformation(final double angle)
{
final double insetsTop = flipPane.getInsets().getTop() * 2;
final double insetsLeft = flipPane.getInsets().getLeft() * 2;
final double radius = flipPane.widthProperty().subtract(insetsLeft).divide(2).doubleValue();
final double height = flipPane.heightProperty().subtract(insetsTop).doubleValue();
final double back = height / 10;
/*
* Compute transform.
*
* Don't bother understanding these unless you're a math passionate.
*
* You may Google "Affine Transformation - Rotation"
*/
transform.setUlx(radius - Math.sin(angle) * radius);
transform.setUly(0 - Math.cos(angle) * back);
transform.setUrx(radius + Math.sin(angle) * radius);
transform.setUry(0 + Math.cos(angle) * back);
transform.setLrx(radius + Math.sin(angle) * radius);
transform.setLry(height - Math.cos(angle) * back);
transform.setLlx(radius - Math.sin(angle) * radius);
transform.setLly(height + Math.cos(angle) * back);
}
}
Oracle created a sample called DisplayShelf. It is similar to the PhotoFlip application you linked, but is implemented for Java 2+. The Oracle sample code is in the Ensemble Sample Application. You can review the DisplayShelf source in the JavaFX open source repository.
The DisplayShelf is a Cover Flow style implementation of PerspectiveTransform animations, so its not exactly the same as a full image flip. But many of the principles are the same, so you should be able to study the DisplayShelf example, then develop the code which you need to fit your requirement.
Related image flipping question for JavaFX => Flip a card animation.