My JavaFX Slider does not change value, it appears frozen - java

My JavaFX slider does not change value when I'm trying to move.
My slider is inside a GridPane layout.
My code looks like this, if you need more just ask :
root = new GridPane();
root.setLayoutY(canvasHeight);
root.setGridLinesVisible(true);
root.setPadding(new Insets(10, 10, 10, 10));
root.setVgap(2);
Label lblAmount = new Label("Amount of fireworks : ");
GridPane.setConstraints(lblAmount, 0, 0);
root.getChildren().add(lblAmount);
Slider sliAmount = new Slider();
sliAmount.setMin(1);
sliAmount.setMax(10);
sliAmount.setValue(5);
sliAmount.setMaxWidth(100);
root.getChildren().add(sliAmount);
GridPane.setConstraints(sliAmount, 1, 0);
Label lblPSize = new Label("Size of particles : ");
GridPane.setConstraints(lblPSize, 0, 1);
root.getChildren().add(lblPSize);
root.getRowConstraints().add(new RowConstraints(15));
root.getColumnConstraints().add(new ColumnConstraints(140));
Here's a GIF to better understand what the problem is
Here's what it looks like in my small program :
EDIT : More code
Since my program is very small and has pretty much only the slider implemented here is all my code.
Main class
public class Main extends Application {
private final int CANVAS_WIDTH = 1600;
private final int CANVAS_HEIGHT = 750;
private final int MENU_HEIGHT = 150;
private GraphicsContext graphics;
private GUI gui;
#Override
public void start(Stage primaryStage) throws Exception{
gui = new GUI(CANVAS_WIDTH, CANVAS_HEIGHT, MENU_HEIGHT);
gui.setup();
Canvas canvas = new Canvas(CANVAS_WIDTH, CANVAS_HEIGHT);
graphics = canvas.getGraphicsContext2D();
clearCanvas();
gui.getRoot().getChildren().add(canvas);
primaryStage.setTitle("FireworkSim by Cedric Martens");
primaryStage.setScene(new Scene(gui.getRoot(), CANVAS_WIDTH, CANVAS_HEIGHT + MENU_HEIGHT, Color.rgb(137, 182, 255)));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
private void clearCanvas()
{
//graphics.setFill(Color.BLACK);
//graphics.fillRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
}
}
GUI class
public class GUI {
private GridPane root;
private int canvasWidth;
private int canvasHeight;
private int menuHeight;
public GUI(int canvasWidth, int canvasHeight, int menuHeight)
{
this.canvasWidth = canvasWidth;
this.canvasHeight = canvasHeight;
this.menuHeight = menuHeight;
}
public void setup()
{
root = new GridPane();
root.setLayoutY(canvasHeight);
root.setGridLinesVisible(true);
root.setPadding(new Insets(10, 10, 10, 10));
root.setVgap(2);
Label lblAmount = new Label("Amount of fireworks : ");
GridPane.setConstraints(lblAmount, 0, 0);
root.getChildren().add(lblAmount);
Slider sliAmount = new Slider();
sliAmount.setMin(1);
sliAmount.setMax(10);
sliAmount.setValue(5);
sliAmount.setMaxWidth(100);
root.getChildren().add(sliAmount);
GridPane.setConstraints(sliAmount, 1, 0);
Label lblPSize = new Label("Size of particles : ");
GridPane.setConstraints(lblPSize, 0, 1);
root.getChildren().add(lblPSize);
root.getRowConstraints().add(new RowConstraints(15));
root.getColumnConstraints().add(new ColumnConstraints(140));
}
public GridPane getRoot() {
return root;
}
}

You're placing the canvas directly in the grid pane created by the GUI class, without specifying any layout properties (row and column indexes, for example). So the canvas is directly overlaying the grid pane, and receives the mouse events, preventing the slider from receiving them. Instead, use a layout pane that lays things out appropriately. For example:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.layout.BorderPane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
public class Main extends Application {
private final int CANVAS_WIDTH = 1600;
private final int CANVAS_HEIGHT = 750;
private final int MENU_HEIGHT = 150;
private GraphicsContext graphics;
private GUI gui;
#Override
public void start(Stage primaryStage) throws Exception{
BorderPane root = new BorderPane();
gui = new GUI(CANVAS_WIDTH, CANVAS_HEIGHT, MENU_HEIGHT);
gui.setup();
Canvas canvas = new Canvas(CANVAS_WIDTH, CANVAS_HEIGHT);
graphics = canvas.getGraphicsContext2D();
clearCanvas();
root.setCenter(canvas);
root.setBottom(gui.getRoot());
primaryStage.setTitle("FireworkSim by Cedric Martens");
primaryStage.setScene(new Scene(root, CANVAS_WIDTH, CANVAS_HEIGHT + MENU_HEIGHT, Color.rgb(137, 182, 255)));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
private void clearCanvas()
{
graphics.setFill(Color.BLACK);
graphics.fillRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
}
}

Related

JavaFX AnimationTimer doesn't start when it is called as a result of a Button click

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

Communication between classes/Listeners

I want to create an application with a button and a colour picker on the top and a canvas in the centre of a BorderPane. I created a main Class TestSceneBuilder and 2 listeners: one for the button and one for the ColorPicker. The question is: when I detect change in colour how do I pass it to my CerchioListener ?
Main Class:
public class TestSceneBuilder extends Application {
final int H = 300, W = 300; //height and width
BorderPane root;
#Override
public void start(Stage primaryStage) {
root = this.setScene();
Scene scene = new Scene(root, H, W);
primaryStage.setTitle("Test");
primaryStage.setScene(scene);
primaryStage.show();
}
/**
* This method is supposed to build the scene with all components:
* a button "Draw" that draws the rectangle
* a canvas
* a colorPicker
* #return
*/
BorderPane setScene(){
BorderPane border = new BorderPane();
final ColorPicker cp = new ColorPicker(Color.AQUA);
Canvas canvas = new Canvas(H, W);
Button btn = new Button("Draw");
/*CerchioListner should get the mouse clicked event and draw the circle*/
final CerchioListner l = new CerchioListner(canvas, cp.getValue());
btn.addEventHandler(MouseEvent.MOUSE_CLICKED, l);
/*ColorListener intercept the color change in ColorPicker cp and change the color of the
shape drawn*/
ColorListener cl = new ColorListener(cp);
cp.setOnAction(cl);
HBox hb = new HBox();
hb.getChildren().addAll(btn, cp);
border.setTop(hb);
BorderPane.setAlignment(hb, Pos.CENTER);
border.setCenter(canvas);
return border;
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}
Button listener: CerchioListener
public class CerchioListner implements javafx.event.EventHandler{
Canvas canvas = null;
Color colore;
public CerchioListner(Canvas c, Color colore) {
this.canvas = c;
this.colore = colore;
}
public void changeColor(Color c) {
this.colore = c;
}
#Override
public void handle(Event t) {
disegna();
}
public void disegna(){
canvas.getGraphicsContext2D().setFill(colore);
canvas.getGraphicsContext2D().fillOval(20, 20, 20, 20);
}
}
Color Picker listener: ColorListener
public class ColorListener implements javafx.event.EventHandler{
ColorPicker cp = null;
public ColorListener(ColorPicker cp) {
this.cp = cp;
}
#Override
public void handle(Event t) {
Color c = cp.getValue();
System.out.println("handle CP "+cp.getValue());
//restituisciColoreSelezionato(c);
}
/*public Color restituisciColoreSelezionato(Color c){
return c;
}*/
}
There are several things that is not the best you can have:
You have a Canvas, which is not member of Main, just a local variable in setScene(), therefore it is only accessible in that method. As the Canvas is the most important part of your class, you should have it as a class member because you want to access it anywhere from the class.
The listener for the Button should not store any reference to the selected color and to the Canvas, it is stored by Main and the listener should use that member.
The listener of the ColorPicker should not store any reference to the ColorPicker itself. The ColorPicker should be a member to make it able to access the currently selected color anywhere in Main.
I have updated your code to include these modifications:
public class TestSceneBuilder extends Application {
final int H = 300, W = 300;
BorderPane root;
Canvas canvas;
ColorPicker cp;
Button btn;
#Override
public void start(Stage primaryStage) {
root = this.setScene();
Scene scene = new Scene(root, H, W);
primaryStage.setTitle("Test");
primaryStage.setScene(scene);
primaryStage.show();
}
BorderPane setScene(){
BorderPane border = new BorderPane();
cp = new ColorPicker(Color.AQUA);
canvas = new Canvas(H, W);
btn = new Button("Draw");
btn.setOnAction((event) -> {
canvas.getGraphicsContext2D().setFill(cp.getValue());
canvas.getGraphicsContext2D().fillOval(20, 20, 20, 20);
});
HBox hb = new HBox();
hb.getChildren().addAll(btn, cp);
border.setTop(hb);
BorderPane.setAlignment(hb, Pos.CENTER);
border.setCenter(canvas);
return border;
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}
If you want to stay with external listeners:
Exchange this:
btn.setOnAction((event) -> {
canvas.getGraphicsContext2D().setFill(cp.getValue());
canvas.getGraphicsContext2D().fillOval(20, 20, 20, 20);
});
with
CerchioListener cerchioListener = new CerchioListener(canvas);
btn.setOnAction(cerchioListener);
cerchioListener.colorProperty.bind(cp.valueProperty());
and add the listener:
CerchioListener.java
public class CerchioListener implements EventHandler<ActionEvent> {
private Canvas canvas = null;
public ObjectProperty<Color> colorProperty = new SimpleObjectProperty<Color>(Color.WHITE);
public CerchioListener(Canvas c) {
this.canvas = c;
}
public Canvas getCanvas() {
return canvas;
}
public void setCanvas(Canvas canvas) {
this.canvas = canvas;
}
#Override
public void handle(ActionEvent t) {
canvas.getGraphicsContext2D().setFill(colorProperty.get());
canvas.getGraphicsContext2D().fillOval(20, 20, 20, 20);
}
}

Javafx drag and drop shapes on canvas

I am quite beginner in Java and need some help with my application.
I am would like to use drag and drop on custom made shapes with javafx canvas i.e multiple polygons that make up a bow tie.
I have created a method that draws a bow tie that looks like this:
public void joonistaBowTie(GraphicsContext gc) {
// Bowtie left side
gc.setFill(Color.RED);
double xpoints[] = { 242, 242, 200 };
double ypoints[] = { 245, 290, 270 };
int num = 3;
gc.fillPolygon(xpoints, ypoints, num);
// Bowtie right side
gc.setFill(Color.RED);
double xpoints1[] = { 160, 160, 200 };
double ypoints1[] = { 245, 290, 270 };
int num1 = 3;
gc.fillPolygon(xpoints1, ypoints1, num1);
// Bowtie middle part
gc.setFill(Color.RED);
gc.fillOval(190, 255, 20, 30);
}
I have moved that method into a separate class called BowTie.
I also have a main class that looks like this:
public class GraafikaNaide extends Application {
Bowtie bowtie;
#Override
public void start(Stage primaryStage) {
primaryStage.setTitle("JavaFX-iga joonistatud kloun");
Group root = new Group();
Canvas canvas = new Canvas(1000, 1000);
GraphicsContext gc = canvas.getGraphicsContext2D();
joonista(gc);
root.getChildren().add(canvas);
primaryStage.setScene(new Scene(root));
primaryStage.show();
}
private void joonista(GraphicsContext gc) {
Bowtie bowtie = new Bowtie();
bowtie.joonistaBowTie(gc);
}
I also found somewhat example on how to do drag and drop, but i just lack knowledge on how to implement this code to mine.
Could someone please help me with this?
Thanks
Using the link you provided, here is how you would incorporate it with your work:
public class GraafikaNaide extends Application
{
joonistaBowTie bowtie;
double orgSceneX, orgSceneY;
double orgTranslateX, orgTranslateY;
#Override
public void start(Stage primaryStage)
{
Canvas canvas = new Canvas(1000, 1000);
GraphicsContext gc = canvas.getGraphicsContext2D();
joonista(gc);
canvas.setOnMousePressed(canvasOnMousePressedEventHandler);
canvas.setOnMouseDragged(canvasOnMouseDraggedEventHandler);
Group root = new Group();
root.getChildren().add(canvas);
primaryStage.setTitle("JavaFX-iga joonistatud kloun");
primaryStage.setScene(new Scene(root));
primaryStage.show();
}
private void joonista(GraphicsContext gc)
{
joonistaBowTie bowtie = new joonistaBowTie();
bowtie.joinBowTie(gc);
}
EventHandler<MouseEvent> canvasOnMousePressedEventHandler = new EventHandler<MouseEvent>()
{
#Override
public void handle(MouseEvent mouseEvent)
{
orgSceneX = mouseEvent.getSceneX();
orgSceneY = mouseEvent.getSceneY();
orgTranslateX = ((Canvas)(mouseEvent.getSource())).getTranslateX();
orgTranslateY = ((Canvas) (mouseEvent.getSource())).getTranslateY();
}
};
EventHandler<MouseEvent> canvasOnMouseDraggedEventHandler = new EventHandler<MouseEvent>()
{
#Override
public void handle(MouseEvent mouseEvent)
{
double offsetX = mouseEvent.getSceneX() - orgSceneX;
double offsetY = mouseEvent.getSceneY() - orgSceneY;
double newTranslateX = orgTranslateX + offsetX;
double newTranslateY = orgTranslateY + offsetY;
((Canvas) (mouseEvent.getSource())).setTranslateX(newTranslateX); //transform the object
((Canvas) (mouseEvent.getSource())).setTranslateY(newTranslateY);
}
};
}
Hope this helps.

Flip a card animation

I'm trying to flip a coloured rectangle. Is it possible to use the rotateTransition to do this?
I have tried the following code:
public void rotateField(){
RotateTransition rt = new RotateTransition(Duration.millis(3000), field[4][4]);
rt.setByAngle(360);
rt.setCycleCount(1);
rt.play();
}
However, this doesn't flip the rectangle, it just rotates it.
I would like to actually flip the rectangle as you would flip a playing card.
Is it possible to use the rotateTransition class for this?
I like Sergey's solution, it is such a clever use of ScaleTransition and working in 2D means you don't need to deal with some of the complexities of 3D.
Here a couple of 3D rotation samples.
Rotates a 2D Node (an ImageView) round the Y axis (requires JavaFX 2.2 + 3D support).
import javafx.animation.*;
import javafx.application.Application;
import javafx.scene.*;
import javafx.scene.image.*;
import javafx.scene.layout.StackPane;
import javafx.scene.transform.Rotate;
import javafx.stage.Stage;
import javafx.util.Duration;
public class QuickFlip extends Application {
#Override
public void start(Stage stage) throws Exception {
Node card = createCard();
stage.setScene(createScene(card));
stage.show();
RotateTransition rotator = createRotator(card);
rotator.play();
}
private Scene createScene(Node card) {
StackPane root = new StackPane();
root.getChildren().addAll(card);
Scene scene = new Scene(root, 600, 700, true, SceneAntialiasing.BALANCED);
scene.setCamera(new PerspectiveCamera());
return scene;
}
private Node createCard() {
return new ImageView(
new Image(
"http://www.ohmz.net/wp-content/uploads/2012/05/Game-of-Throne-Magic-trading-cards-2.jpg"
)
);
}
private RotateTransition createRotator(Node card) {
RotateTransition rotator = new RotateTransition(Duration.millis(10000), card);
rotator.setAxis(Rotate.Y_AXIS);
rotator.setFromAngle(0);
rotator.setToAngle(360);
rotator.setInterpolator(Interpolator.LINEAR);
rotator.setCycleCount(10);
return rotator;
}
public static void main(String[] args) {
launch();
}
}
Rotates a 3D Node (a TriangleMesh) round the Y axis (requires Java 8 + 3D support).
import javafx.animation.*;
import javafx.application.Application;
import javafx.scene.*;
import javafx.scene.image.Image;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.*;
import javafx.scene.shape.*;
import javafx.scene.transform.Rotate;
import javafx.stage.Stage;
import javafx.util.Duration;
public class CardFlip extends Application {
final Image CARD_IMAGE = new Image(
"http://fc05.deviantart.net/fs70/i/2010/345/7/7/vitam_et_mortem_by_obviouschild-d34oni2.png"
// sourced from: http://obviouschild.deviantart.com/art/Vitam-et-Mortem-189267194
);
final int W = (int) (CARD_IMAGE.getWidth() / 2);
final int H = (int) CARD_IMAGE.getHeight();
#Override
public void start(Stage stage) throws Exception {
Node card = createCard();
stage.setScene(createScene(card));
stage.show();
RotateTransition rotator = createRotator(card);
rotator.play();
}
private Scene createScene(Node card) {
StackPane root = new StackPane();
root.getChildren().addAll(card, new AmbientLight(Color.WHITE));
Scene scene = new Scene(root, W + 200, H + 200, true, SceneAntialiasing.BALANCED);
scene.setFill(Color.MIDNIGHTBLUE.darker().darker().darker().darker());
scene.setCamera(new PerspectiveCamera());
return scene;
}
private RotateTransition createRotator(Node card) {
RotateTransition rotator = new RotateTransition(Duration.millis(10000), card);
rotator.setAxis(Rotate.Y_AXIS);
rotator.setFromAngle(0);
rotator.setToAngle(360);
rotator.setInterpolator(Interpolator.LINEAR);
rotator.setCycleCount(10);
return rotator;
}
private Node createCard() {
MeshView card = new MeshView(createCardMesh());
PhongMaterial material = new PhongMaterial();
material.setDiffuseMap(CARD_IMAGE);
card.setMaterial(material);
return card;
}
private TriangleMesh createCardMesh() {
TriangleMesh mesh = new TriangleMesh();
mesh.getPoints().addAll(-1 * W/2, -1 * H/2 , 0, 1 * W/2, -1 * H/2, 0, -1 * W/2, 1 * H/2, 0, 1 * W/2, 1 * H/2, 0);
mesh.getFaces().addAll(0, 0, 2, 2, 3, 3, 3, 3, 1, 1, 0, 0);
mesh.getFaces().addAll(0, 4, 3, 7, 2, 6, 3, 7, 0, 4, 1, 5);
mesh.getTexCoords().addAll(0, 0, 0.5f, 0, 0, 1, 0.5f, 1, 0.5f, 0, 1, 0, 0.5f, 1, 1, 1);
return mesh;
}
public static void main(String[] args) {
launch();
}
}
Not in 2D world. But you can imitate card flip using two ScaleTransitions:
Rectangle front = new Rectangle(50, 50);
ScaleTransition stHideFront = new ScaleTransition(Duration.millis(1500), front);
stHideFront.setFromX(1);
stHideFront.setToX(0);
Rectangle back = new Rectangle(50, 50, Color.RED);
back.setScaleX(0);
ScaleTransition stShowBack = new ScaleTransition(Duration.millis(1500), back);
stShowBack.setFromX(0);
stShowBack.setToX(1);
stHideFront.setOnFinished(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent t) {
stShowBack.play();
}
});
StackPane root = new StackPane();
root.getChildren().addAll(front, back);
Scene scene = new Scene(root, 300, 250);
primaryStage.setScene(scene);
primaryStage.show();
stHideFront.play();
Here is an extension of #jewelsea's answer. This example shows one way to flip and show the back of the card. The way I thought of this was to change the ImageView's image at half the duration of the RotateTransition.
import javafx.animation.*;
import javafx.application.Application;
import javafx.scene.*;
import javafx.scene.image.*;
import javafx.scene.layout.StackPane;
import javafx.scene.transform.Rotate;
import javafx.stage.Stage;
import javafx.util.Duration;
public class App extends Application {
boolean isFrontShowing = true;
Image frontImage = new Image(getClass().getResourceAsStream("image/front.jpg"));
Image backImage = new Image(getClass().getResourceAsStream("image/back.jpg"));
#Override
public void start(Stage stage) throws Exception {
ImageView card = createCard();
card.setOnMouseClicked((t) -> {
RotateTransition rotator = createRotator(card);
PauseTransition ptChangeCardFace = changeCardFace(card);
ParallelTransition parallelTransition = new ParallelTransition(rotator, ptChangeCardFace);
parallelTransition.play();
isFrontShowing = !isFrontShowing;
});
stage.setScene(createScene(card));
stage.show();
}
private Scene createScene(Node card) {
StackPane root = new StackPane();
root.getChildren().addAll(card);
Scene scene = new Scene(root, 600, 700, true, SceneAntialiasing.BALANCED);
scene.setCamera(new PerspectiveCamera());
return scene;
}
private ImageView createCard() {
ImageView card = new ImageView(frontImage);
return card;
}
private RotateTransition createRotator(ImageView card) {
RotateTransition rotator = new RotateTransition(Duration.millis(1000), card);
rotator.setAxis(Rotate.Y_AXIS);
if (isFrontShowing) {
rotator.setFromAngle(0);
rotator.setToAngle(180);
} else {
rotator.setFromAngle(180);
rotator.setToAngle(360);
}
rotator.setInterpolator(Interpolator.LINEAR);
rotator.setCycleCount(1);
return rotator;
}
private PauseTransition changeCardFace(ImageView card) {
PauseTransition pause = new PauseTransition(Duration.millis(500));
if (isFrontShowing) {
pause.setOnFinished(
e -> {
card.setImage(backImage);
});
} else {
pause.setOnFinished(
e -> {
card.setImage(frontImage);
});
}
return pause;
}
public static void main(String[] args) {
launch();
}
}

Stretching Polygon to other Polygon with Java

My problem is that I have a rectangle presented with a small perspective, and I would like to stretch it back to be presented as a rectangle again.
To represent it visually, I currently have within my image something like the red shape, and I have 4 Points (each corner of this shape). As result I would like to have something like the blue shape, and I already have the Rectangle object for it.
I was wondering if there is a method to copy a polygon and draw it as another polygon stretched. I found something for Android (setPolyToPoly), but I couldn't find something like this for java.
Is there some reference or code sample that performs this operation, or maybe some idea how can I solve this problem?
I think I understand what you need: a so-called perspective transformation that can be applied to an image. Java has the built-in AffineTransform, but an affine transform always preserves the "parallelness" of lines, so you cannot use that.
Now if you search the web for "java perspective transformation", you will find lots of options like the JavaFX PerspectiveTransform, the JAI PerspectiveTransform. If you only need to stretch images, you can also use the JHLabs PerspectiveFilter and there are other options as well.
Here is some code that will turn stretch a polygon with four points to a rectangle.
public static Rectangle2D polyToRect(Polygon polygon) {
if (polygon.xpoints.length != 4 || polygon.ypoints.length != 4)
throw new IllegalArgumentException(
"More than four points, this cannot be fitted to a rectangle");
Rectangle2D rect = new Rectangle2D.Double();
for (int i = 0; i < 4; i++) {
Point2D point = new Point2D.Double(polygon.xpoints[i],
polygon.ypoints[i]);
rect.add(point);
}
return rect;
}
public static Polygon rectangleToPolygon(Rectangle2D rect) {
Polygon poly = new Polygon();
poly.addPoint((int) rect.getX(), (int) rect.getY());
poly.addPoint((int) (rect.getX() + rect.getWidth()), (int) rect.getY());
poly.addPoint((int) (rect.getX() + rect.getWidth()),
(int) (rect.getY() + rect.getHeight()));
poly.addPoint((int) rect.getX(), (int) (rect.getY() + rect.getHeight()));
return poly;
}
public static class drawPolyAndRect extends JPanel {
Polygon poly = new Polygon();
public drawPolyAndRect() {
poly.addPoint(0, 0);
poly.addPoint(400, 40);
poly.addPoint(400, 250);
poly.addPoint(0, 400);
}
#Override
#Transient
public Dimension getPreferredSize() {
return new Dimension(1000, 1000);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.setColor(Color.green);
g2d.fill(poly);
Composite c = AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
0.5f);
g2d.setColor(Color.blue);
g2d.setComposite(c);
Rectangle2D polyToRect = polyToRect(poly);
g2d.fill(polyToRect);
// displace for drawing
polyToRect.setFrame(polyToRect.getX() + 100,
polyToRect.getY() + 100, polyToRect.getWidth(),
polyToRect.getHeight());
Polygon polyToRectToPoly = rectangleToPolygon(polyToRect);
g2d.fill(polyToRectToPoly);
g2d.dispose();
}
}
public static void main(String[] args) {
JFrame frame = new JFrame("Poly to rect");
frame.getContentPane().add(new drawPolyAndRect());
frame.pack();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
Essentially this uses how adding points to an empty rectangle2d works, it constructs the smallest possible rectangle containing all four points, stretching the polygon. Check the second static method if you want the returned rectangle as a polygon. Picture:
A JavaFX based solution using a PerspectiveTransform as suggested by #lbalazscs answer.
Toggle Perspective switches on and off the perspective effect on the content.
Morph Perspective smoothly animates a transition between the perspective transformed and non-perspective transformed content.
import javafx.animation.*;
import javafx.application.*;
import javafx.beans.value.*;
import javafx.geometry.Pos;
import javafx.scene.*;
import javafx.scene.control.ToggleButton;
import javafx.scene.effect.PerspectiveTransform;
import javafx.scene.image.*;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.scene.text.*;
import javafx.stage.Stage;
import javafx.util.Duration;
public class PerspectiveMovement extends Application {
// perspective transformed group width and height.
private final int W = 280;
private final int H = 96;
// upper right and lower right co-ordinates of perspective transformed group.
private final int URY = 35;
private final int LRY = 65;
#Override public void start(Stage stage) {
final PerspectiveTransform perspectiveTransform = createPerspectiveTransform();
final Group group = new Group();
group.setCache(true);
setContent(group);
final ToggleButton perspectiveToggle = createToggle(
group,
perspectiveTransform
);
VBox layout = new VBox(10);
layout.setAlignment(Pos.CENTER);
layout.getChildren().setAll(
perspectiveToggle,
createMorph(perspectiveToggle, group, perspectiveTransform),
group
);
layout.setStyle("-fx-padding: 10px; -fx-background-color: rgb(17, 20, 25);");
stage.setScene(new Scene(layout));
stage.show();
}
private void setContent(Group group) {
Rectangle rect = new Rectangle(0, 5, W, 80);
rect.setFill(Color.web("0x3b596d"));
Text text = new Text();
text.setX(4.0);
text.setY(60.0);
text.setText("A long time ago");
text.setFill(Color.ALICEBLUE);
text.setFont(Font.font(null, FontWeight.BOLD, 36));
Image image = new Image(
"http://icons.iconarchive.com/icons/danrabbit/elementary/96/Star-icon.png"
);
ImageView imageView = new ImageView(image);
imageView.setX(50);
group.getChildren().addAll(rect, imageView, text);
}
private PerspectiveTransform createPerspectiveTransform() {
PerspectiveTransform perspectiveTransform = new PerspectiveTransform();
perspectiveTransform.setUlx(0.0);
perspectiveTransform.setUly(0.0);
perspectiveTransform.setUrx(W);
perspectiveTransform.setUry(URY);
perspectiveTransform.setLrx(W);
perspectiveTransform.setLry(LRY);
perspectiveTransform.setLlx(0.0);
perspectiveTransform.setLly(H);
return perspectiveTransform;
}
private ToggleButton createToggle(final Group group, final PerspectiveTransform perspectiveTransform) {
final ToggleButton toggle = new ToggleButton("Toggle Perspective");
toggle.selectedProperty().addListener(new ChangeListener<Boolean>() {
#Override public void changed(ObservableValue<? extends Boolean> observable, Boolean wasSelected, Boolean selected) {
if (selected) {
perspectiveTransform.setUry(URY);
perspectiveTransform.setLry(LRY);
group.setEffect(perspectiveTransform);
} else {
group.setEffect(null);
}
}
});
return toggle;
}
private ToggleButton createMorph(final ToggleButton perspectiveToggle, final Group group, final PerspectiveTransform perspectiveTransform) {
final Timeline distorter = new Timeline(
new KeyFrame(
Duration.seconds(0),
new KeyValue(perspectiveTransform.uryProperty(), 0, Interpolator.LINEAR),
new KeyValue(perspectiveTransform.lryProperty(), H, Interpolator.LINEAR)
),
new KeyFrame(
Duration.seconds(3),
new KeyValue(perspectiveTransform.uryProperty(), URY, Interpolator.LINEAR),
new KeyValue(perspectiveTransform.lryProperty(), LRY, Interpolator.LINEAR)
)
);
final ToggleButton morphToggle = new ToggleButton("Morph Perspective");
morphToggle.selectedProperty().addListener(new ChangeListener<Boolean>() {
#Override public void changed(ObservableValue<? extends Boolean> observable, Boolean wasSelected, Boolean selected) {
if (!perspectiveToggle.isSelected()) {
perspectiveToggle.fire();
}
if (selected) {
distorter.setRate(1);
distorter.play();
} else {
distorter.setRate(-1);
distorter.play();
}
}
});
return morphToggle;
}
}
I don't know if this could help you but let me give you what i made with what i understand:
import javax.swing.*;
import java.awt.*;
public class PolyToRectangle extends JPanel {
public static final int SPEED = 50; //less = more fast.
private int ax = 0, bx = 800, cx = 800, dx = 0,
ay = 0, by = 40, cy = 250, dy = 400;
private Polygon poly;
public PolyToRectangle() {
setPreferredSize(new Dimension(1200, 720));
poly = new Polygon(new int[]{ax, bx, cx, dx}, new int[]{ay, by, cy, dy}, 4);
}
#Override
public void paintComponent(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
g2d.draw(poly);
g2d.fill(poly);
}
public void polyToRectangle() throws InterruptedException {
int flag = 0;
for (int i = 0; i < 150; i++) {
flag++;
poly.addPoint(ax, ay);
poly.addPoint(bx, (by = flag % 3 == 0 ? --by : by));
poly.addPoint(cx, cy++);
poly.addPoint(dx, dy);
Thread.sleep(SPEED);
repaint();
}
}
protected void clear(Graphics g) {
super.paintComponent(g);
}
public static void main(String[] args) throws InterruptedException {
Frame frame = new JFrame();
PolyToRectangle se = new PolyToRectangle();
frame.add(se);
frame.pack();
frame.setVisible(true);
se.polyToRectangle();
}
}
Ok as you can see this code is more a "PolyToSquare" more than PolyToRectangle, but the main was to show the "effect" of repainting with a Thread.sleep in a for, maybe that's the "visual stretch" you are talking about, note that the numbers of iterations on the for depends on numbers of pixels from point 1 and 2 to "stretch" from polygon to rectangle, this is a "hand made" effect, maybe what #lbalazscs is suggesting is the best solution, hope to be helpful, regards.
EDIT: edited the code to be more clean and to follow in a more specific way your goal
(now is more a PolyToRectangle, fixed the bx and cx values).

Categories