I would like to embedd a video in my JavaFx 2.x scene and also resize it and relocate it to my needs. The problem I'm having is following. If I build a MediaView component and then translate X or Y coordinate, then the whole view is being properly moved like so:
MediaView mv = (...)
mv.setTranslateX(200);
mv.setTranslateY(200);
I could do a similar transformation with scaling property:
MediaView mv = (...)
mv.setScaleX(2);
mv.setScaleY(2);
which will properly scale mv instance two times in dimension.
However, the problem is when I combine those two translations. The mv instance is being scaled but it always ends up in screen coordinates (0,0). This is of course incorrect behavior from my perspective.
I have also tried to wrap my MediaView component within some wrapper node, like Group and perform translations on this element. The behavior is the same.
How can I properly move and scale MediaView component at the same time?
Edit:
Here is my code, although I'm using here ImageView. This is irrelevant, however. After running this code, image will be placed at (0,0) instead of (100,100).
#Override
public void start(Stage stage) throws Exception {
Group root = new Group();
// Img's dimension is 200x200
javafx.scene.image.Image img = new javafx.scene.image.Image("/home/bachman/projects/cs-player/src/main/resources/content.png");
ImageView iv = new ImageView(img);
root.getChildren().add(iv);
// Move Image View
iv.setTranslateX(100);
iv.setTranslateY(100);
// Scale Image View
iv.setScaleX(2.0);
iv.setScaleY(2.0);
Scene scene = new Scene(root);
stage.setWidth(600);
stage.setHeight(600);
stage.setScene(scene);
stage.show();
}
When you use setScaleX, setScaleY, scaling occurs from the center of the node.
If you want to translate by an amount in addition to scaling, you need to take the scaling expansion into account when you set the required translation values. For example, if the node doubles in size (and you want to translate the node to a position relative to the upper left corner of the unscaled node), you need to translate by an additional half the node width and height.
import javafx.application.Application;
import javafx.scene.*;
import javafx.scene.media.*;
import javafx.scene.transform.*;
import javafx.stage.Stage;
public class TransformedVideo extends Application {
public static void main(String[] args) throws Exception { launch(args); }
#Override public void start(final Stage stage) throws Exception {
final MediaPlayer oracleVid = new MediaPlayer(new Media("http://download.oracle.com/otndocs/products/javafx/oow2010-2.flv"));
final MediaView mediaView = new MediaView(oracleVid);
mediaView.setFitWidth(320); mediaView.setFitHeight(240); mediaView.setPreserveRatio(false);
mediaView.setTranslateX(mediaView.getFitWidth() / 2 + 200);
mediaView.setTranslateY(mediaView.getFitHeight() / 2 + 200);
mediaView.setScaleX(2); mediaView.setScaleY(2);
// alternative method of scaling and translating.
// mediaView.getTransforms().addAll(
// new Translate(200, 200),
// new Scale(2, 2)
// );
Group group = new Group(mediaView);
stage.setScene(new Scene(group, 1250, 800));
stage.show();
oracleVid.play();
System.out.println(group.getBoundsInParent());
}
}
When performing multiple transforms on a node, rather than using the setScale/setTransform/setRotate methods, it is often easier to just supply a list of transforms to the getTransforms() method.
Related
I have a program that first starts up a little splash screen, and after 5 seconds it continues to a new Stage.
The strange thing is, until now it worked fine, but now that I'm starting to build the new Stage I'm declaring new Nodes in the class to use it.
Now all of the sudden the splash screen has a white background, commenting the before created nodes out will revert the image back to being transparent.
public static Pane pane = new Pane(); //this is fine
//Uncommenting this Node will change the splash screen background white
//public static TextArea textArea = new TextArea();
public static Image splashImage = new Image(Class.class.getClassLoader().getResourceAsStream("image.png"));
public static ImageView splashImageView = new ImageView(splashImage);
public static BorderPane splashPane = new BorderPane(splashImageView);
public static Scene splashScene = new Scene(splashPane);
#Override
public void start(Stage primaryStage) throws Exception {
splashScene.setFill(Color.TRANSPARENT);
primaryStage.setScene(splashScene);
primaryStage.initStyle(StageStyle.TRANSPARENT);
primaryStage.show();
}
Here a shortened version of my code
Could this be because of memory issues or something? I have never encountered anything like this.
Any help is appreciated.
The default style sheet is only loaded if a control (i.e an instance of Control or one of its subclasses) is created. (The idea behind this is to avoid the performance overhead of loading CSS for applications that manage all their own graphics and don't use any controls, such as games or simulations.)
The default stylesheet sets the background color of the root node (the splashPane in your example) to a very light grey (specifically, it is 26.4% brighter than the color #ececec).
Since the text area is the only control in your class, creating it causes the default style sheet to be loaded, which sets the background color of the splashPane to a very light grey.
If you need to instantiate controls and want the background of the pane to be transparent, you need to specify that in an external CSS:
splashStyle.css:
.root {
-fx-background-color: transparent ;
}
And
#Override
public void start(Stage primaryStage) throws Exception {
splashScene.setFill(Color.TRANSPARENT);
splashScene.getStylesheets().add("splashStyle.css");
primaryStage.setScene(splashScene);
primaryStage.initStyle(StageStyle.TRANSPARENT);
primaryStage.show();
}
(As a quick check that this will work, you can just test with
splashPane.setStyle("-fx-background-color: transparent; ");
)
Maybe I'm misunderstanding something basic, but I'm experimenting with JavaFX and am baffled why scaling a Canvas (using .setScaleX/Y) with value of 2 doesn't result in canvas with two times bigger width/height.
The relevant code is this: (I'm not using any .fxml at this point)
public class Main extends Application {
#Override
public void start(Stage primaryStage){
AnchorPane pane = new AnchorPane();
Canvas canvas = new Canvas();
canvas.setWidth(100);
canvas.setHeight(100);
canvas.getGraphicsContext2D().setFill(Color.BLUE);
canvas.getGraphicsContext2D().fillRect(0,0,100,100);
canvas.setScaleX(2);
canvas.setScaleY(2);
pane.getChildren().add(canvas);
primaryStage.setScene(new Scene(pane, 200, 200));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
This results in the canvas taking about 2/3 (in terms of width/heighth, not area) of the window. Using scale of 3 makes it fill the whole window (bar one pixel gap at the edge) and I don't understand why. I looked at the documentation but it seems like scaling factor of 2 should result in two times the dimensions.
EDIT:
So per suggestion below, translating the canvas by half it's width/height fixes the issue. But so does centering it in a StackPane, so I don't understand why the AnchorPane won't anchor it by it's top/left edge if it really is 200x200 after scaling, it's as if it was anchoring it by it's 100x100 edges. But if the Canvas is 100x100 then how is it drawing outside of those bounds if simply centered in StackPane?
EDIT2:
With some experimenting I think I confirmed that the AnchorPane is positioning the Canvas "as if" it was still 100x100. So by setting the top and left anchors to 50 I can get the same results as translating by 50 or as if centering it in StackPane.
But that means that effectively the Canvas is "drawing" outside of it's "edges". Surely this can't be right?
The information added in your edits in the original post give most of the answer to the question.
First note that scaleX and scaleY scale a node around its center. From the documentation: "Defines the factor by which coordinates are scaled about the center of the object along the X axis of this Node."
Second, note that transformations applied to a node are not taken into account in the layout calculations for that node. From the layout documentation (See the section "Visual Bounds vs Layout Bounds"):
So for example ... if a ScaleTransition is used to pulse the size of a
button, that pulse animation will not disturb layout around that
button. If an application wishes to have the effect, clip, or
transform factored into the layout of a node, it should wrap that node
in a Group.
The last sentence in that quote from the documentation refers to the fact that the layout bounds of a Group includes any transformations applied to the group's children (but not to the group itself).
To visualize what is happening, start with a 100x100 canvas. The layout ignores the scaling, so you can visualize this as anchoring the canvas to the top left corner of the anchor pane before applying the scaling. Then the canvas is scaled about its center, pushing all four corners 50 pixels away from the center in both dimensions. Consequently the bounds of the canvas in the anchor pane's coordinate system are from (-50, -50) to (150, 150). You can verify this by adding
System.out.println(canvas.getBoundsInParent());
after the stage is shown.
There are two solutions. One is to wrap the canvas in a Group. The layout bounds of the Group will account for the scaling on the canvas:
public class Main extends Application {
#Override
public void start(Stage primaryStage){
AnchorPane pane = new AnchorPane();
Canvas canvas = new Canvas();
canvas.setWidth(100);
canvas.setHeight(100);
canvas.getGraphicsContext2D().setFill(Color.BLUE);
canvas.getGraphicsContext2D().fillRect(0,0,100,100);
canvas.setScaleX(2);
canvas.setScaleY(2);
Group group = new Group(canvas);
AnchorPane.setTopAnchor(group, 0.0);
AnchorPane.setLeftAnchor(group, 0.0);
pane.getChildren().add(group);
primaryStage.setScene(new Scene(pane, 200, 200));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
The other solution is to apply a transformation which scales the canvas about its top-left corner:
public class Main extends Application {
#Override
public void start(Stage primaryStage){
AnchorPane pane = new AnchorPane();
Canvas canvas = new Canvas();
canvas.setWidth(100);
canvas.setHeight(100);
canvas.getGraphicsContext2D().setFill(Color.BLUE);
canvas.getGraphicsContext2D().fillRect(0,0,100,100);
Scale scale = new Scale(2,2);
scale.setPivotX(0);
scale.setPivotY(0);
canvas.getTransforms().add(scale);
pane.getChildren().add(canvas);
primaryStage.setScene(new Scene(pane, 200, 200));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
You've already added canvas to the pane, try to apply .setScaleX/Y before pane.getChildren().add(canvas).
I've been working in JavaFX for the first time to try to make an app which I can use to demonstrate a simple animation with button controls. To do this I've used a BoarderPane for the primary stage, with both the left,right, and bottom using Gridpanes.
However, for the center I need to be able to draw a sphere with a line within it which I can rotate for different views while simultaneously being able to animated, or at the very least snap-move, the line within.
I've tried using a Pane for the center which doesn't work. I've tried making it it's own scene and sub scene which doesn't work. And I can't use a canvas as that is only for 2D animation.
Is there a way I can animate the line or rotate the camera while maintaining the BoarderPane layout I've created?
I've tried looking at the following before to understand what I could do but most just seem incompatible with the BoarderPane:
JavaFX Rotating Camera Around a Pivot
JavaFX Canvas rotate image with fixed center (and without bouncing)
Whenever you want to mix 2D and 3D (and camera) you have to use a SubScene container for the 3D content:
SubScene provides separation of different parts of a scene, each of which can be rendered with a different camera, depth buffer, or scene anti-aliasing. A SubScene is embedded into the main scene or another sub-scene.
If you have a BorderPane container, you can perfectly add the subScene to its center.
For a similar use case, you can check the Qubit3D class from here, which is mainly a group that holds a sub scene with an Sphere and a cylinder (both from the FXyz 3D library).
You can add this group easily to your 2D scene:
private final Rotate rotate = new Rotate(0, 0, 0, 0, javafx.geometry.Point3D.ZERO.add(1, 1, 1));
#Override
public void start(Stage primaryStage) throws Exception {
final Timeline timeline = new Timeline(
new KeyFrame(Duration.seconds(10),
new KeyValue(rotate.angleProperty(), 360)));
final Qubit3D qubit = new Qubit3D();
final BorderPane root = new BorderPane(qubit);
final Button buttonAnimate = new Button("Animate");
buttonAnimate.setOnAction(e -> {
rotate.setAngle(0);
timeline.playFromStart();
});
root.setLeft(new StackPane(buttonAnimate));
final Button buttonStop = new Button("Stop");
buttonStop.setOnAction(e -> timeline.stop());
root.setRight(new StackPane(buttonStop));
Scene scene = new Scene(root, 600, 400, true, SceneAntialiasing.BALANCED);
scene.setFill(Color.BISQUE);
primaryStage.setScene(scene);
primaryStage.setTitle("Qubit3D Sample");
primaryStage.show();
qubit.rotateRod(rotate);
}
The only modification I've added to Qubit3D is:
public void rotateRod(Rotate rotate) {
rodSphere.getTransforms().setAll(rotate);
}
If you run it:
Note that you can interact with the sphere (via mouse dragged events), while you can also start/stop a full rotation of sphere and rod.
I have a program that first starts up a little splash screen, and after 5 seconds it continues to a new Stage.
The strange thing is, until now it worked fine, but now that I'm starting to build the new Stage I'm declaring new Nodes in the class to use it.
Now all of the sudden the splash screen has a white background, commenting the before created nodes out will revert the image back to being transparent.
public static Pane pane = new Pane(); //this is fine
//Uncommenting this Node will change the splash screen background white
//public static TextArea textArea = new TextArea();
public static Image splashImage = new Image(Class.class.getClassLoader().getResourceAsStream("image.png"));
public static ImageView splashImageView = new ImageView(splashImage);
public static BorderPane splashPane = new BorderPane(splashImageView);
public static Scene splashScene = new Scene(splashPane);
#Override
public void start(Stage primaryStage) throws Exception {
splashScene.setFill(Color.TRANSPARENT);
primaryStage.setScene(splashScene);
primaryStage.initStyle(StageStyle.TRANSPARENT);
primaryStage.show();
}
Here a shortened version of my code
Could this be because of memory issues or something? I have never encountered anything like this.
Any help is appreciated.
The default style sheet is only loaded if a control (i.e an instance of Control or one of its subclasses) is created. (The idea behind this is to avoid the performance overhead of loading CSS for applications that manage all their own graphics and don't use any controls, such as games or simulations.)
The default stylesheet sets the background color of the root node (the splashPane in your example) to a very light grey (specifically, it is 26.4% brighter than the color #ececec).
Since the text area is the only control in your class, creating it causes the default style sheet to be loaded, which sets the background color of the splashPane to a very light grey.
If you need to instantiate controls and want the background of the pane to be transparent, you need to specify that in an external CSS:
splashStyle.css:
.root {
-fx-background-color: transparent ;
}
And
#Override
public void start(Stage primaryStage) throws Exception {
splashScene.setFill(Color.TRANSPARENT);
splashScene.getStylesheets().add("splashStyle.css");
primaryStage.setScene(splashScene);
primaryStage.initStyle(StageStyle.TRANSPARENT);
primaryStage.show();
}
(As a quick check that this will work, you can just test with
splashPane.setStyle("-fx-background-color: transparent; ");
)
I am a beginner who just recently started learning JavaFX, and I seem to be making the same reoccurring mistake within my programs. For example, in the following code I am trying to draw the X-Axis and Y-Axis and have it binded to half the width and height of the pane. When executed, the axes are very small and located at the topleft corner, but as you resize the window of the application, the axes slowly increase in size until the window not being resized anymore.
public class Debug extends Application {
#Override
public void start(Stage primaryStage) {
Pane pane = new Pane();
GraphFunc test = new GraphFunc();
pane.getChildren().add(test);
Scene scene = new Scene(pane, 500, 500);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
class GraphFunc extends Pane {
private double xAxisSpan, yAxisSpan;
public GraphFunc() {
xAxisSpan = 100;
yAxisSpan = 100;
drawAxes();
}
private void drawAxes() {
Line xAxis = new Line(0, 50, 100, 50);
Line yAxis = new Line(50, 0, 50, 100);
xAxis.setStartX(0);
xAxis.startYProperty().bind(heightProperty().divide(2));
xAxis.endXProperty().bind(widthProperty());
xAxis.endYProperty().bind(heightProperty().divide(2));
yAxis.startXProperty().bind(widthProperty().divide(2));
yAxis.setStartY(0);
yAxis.endXProperty().bind(widthProperty().divide(2));
yAxis.endYProperty().bind(heightProperty());
getChildren().addAll(xAxis, yAxis);
}
}
I am confused because when pane is change to a StackPane, this does not happen. Also if I moved the code in drawAxes() to start() and added the lines to pane it would also not do this. Please explain, I cannot seem to understand what is happening after researching and playing around with it.
This is happening because of Pane. Pane is meant to be used when absolute positioning of children is required.
This is from Oracle documentation:
This class may be used directly in cases where absolute positioning of children is required since it does not perform layout beyond resizing resizable children to their preferred sizes. It is the application's responsibility to position the children since the pane leaves the positions alone during layout.
Pane pane = new Pane();
GraphFunc test = new GraphFunc();
pane.getChildren().add(test);
Scene scene = new Scene(pane, 500, 500);
If you remove the width and height from the scene and instead set the width and height directly on to test like this:
public void start(Stage primaryStage) {
GridPane pane = new GridPane();
GraphFunc test = new GraphFunc();
test.setPrefSize(500, 500);
pane.getChildren().add(test);
Scene scene = new Scene(pane);
primaryStage.setScene(scene);
primaryStage.show();
}
You will get this:
drawn image
However you will not be able to span.
From Oracle documentation:
Note: if an application needs children to be kept aligned within a
parent (centered, positioned at top-left, etc), it should use a
StackPane instead.
Also from Oracle documentation:
Pane does not clip its content by default, so it is possible that
childrens' bounds may extend outside its own bounds, either if
children are positioned at negative coordinates or the pane is resized
smaller than its preferred size.