JavaFX Node Snapshot X/Y Params Have To Be Negative - javafx

Execute the test below - the cyan rectangle is displayed centered. That's the intended result. However, why minX and minY of the viewport have to be negative values?
public class NodeScreenshotTest extends Application
{
public static void main(String[] args)
{
Application.launch(args);
}
#Override
public void start(Stage stage)
{
stage.setScene(new Scene(createContent()));
stage.getScene().setFill(Color.BEIGE);
stage.setTitle(getClass().getSimpleName());
stage.show();
}
private Parent createContent()
{
BorderPane content = new BorderPane();
Rectangle rectangle = new Rectangle(50, 50);
rectangle.setFill(Color.AQUA);
SnapshotParameters params = new SnapshotParameters();
params.setFill(Color.YELLOW);
// Viewport x/y negative???
params.setViewport(new Rectangle2D(-15, -15, 80, 80));
ImageView iv = new ImageView(rectangle.snapshot(params, null));
content.setCenter(iv);
return content;
}
}

The origin (0,0) of the relevant coordinate system here is the upper left corner of the node for which you take the snapshot, i.e. the rectangle in your case. Now you seem to want a border arround your rectangle with a width of 15 raster units. So it's clear that your snapshot has to start at the location -15,-15 and has to have a width and height of 50 + 2*15.

Related

Frosted Glass Clipping in JavaFX

I'm trying to achieve this effect here but on a static background (without the scrolling). I'm getting this weird clip on my results though (without the frost effect). I think I know where the problem is in my code but I'm not sure how to solve it.
public class App extends Application {
private static final double BLUR_AMOUNT = 80;
private static final Effect frostEffect = new BoxBlur(BLUR_AMOUNT, BLUR_AMOUNT, 3);
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
ImageView background = new ImageView(bgImage);
StackPane centerBlurredPane = (StackPane) frozenCenterUI();
BorderPane centerContent = new BorderPane();
centerContent.setCenter(new Text("Center"));
centerContent.setTop(new Text("Top"));
centerContent.setLeft(new Text("Left"));
centerContent.setRight(new Text("Right"));
centerBlurredPane.getChildren.add(centerContent);
Scene scene = new Scene(new StackPane(background,centerBlurredPane),414, 849);
primaryStage.setScene(scene);
primaryStage.show();
}
private Node frozenCenterUI() {
Image frostImage = background.snapshot(new SnapshotParameters(), null);
ImageView frost = new ImageView(frostImage);
Rectangle filler = new Rectangle(24, 762, 366, 696);
filler.setArcHeight(50);
filler.setArcWidth(50);
filler.setFill(Color.AZURE);
Pane frostPane = new Pane(frost);
frostPane.setEffect(frostEffect);
StackPane frostView = new StackPane(filler, frostPane);
Rectangle clipShape = new Rectangle(24, 762, 366, 696);
frostView.setClip(clipShape);
return frostView;
}
}
This is what i'm getting, however. I want to apply frost on the white area here.
What should my clip shape be?
Here's my background image, backgroundImage
Edit: I found that if I change my clipShape to have the same dimensions as the scene, then I get the desired effect. However, the BorderPane I added to the frozen pane is not constrained to it, but actually stretches and fills the entire window.

JavaFX - Child Nodes Overlapping Parent Node (Parent's width smaller than children)

Below there is a quick sample of a JavaFX application where the brown region (parent node) contains two child nodes, a red square and a blue circle. When I am reducing the width of the parent node to a size smaller than those of its children, I was expecting the child nodes to be partially visible. However, this is not the case but instead the child nodes are shown fully over parent's region. Any ideas on how to achieve that on the below example?
public class Test extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
primaryStage.setTitle("Parent Child Relationship!");
ParentNode parentNode = new ParentNode();
StackPane root = new StackPane();
root.getChildren().add(parentNode);
primaryStage.setScene(new Scene(root, 300, 250));
primaryStage.show();
}
}
class ParentNode extends Region {
private Rectangle square = new Rectangle();
private Circle circle = new Circle();;
public ParentNode() {
square.setWidth(40);
square.setHeight(40);
square.setFill(Color.RED);
circle.radiusProperty().bind(square.heightProperty().divide(3));
circle.centerXProperty().bind(circle.radiusProperty());
circle.centerYProperty().bind(square.heightProperty().divide(2));
circle.setFill(Color.BLUE);
circle.setStroke(Color.LIGHTGRAY);
getChildren().addAll(square, circle);
setBackground(new Background(new BackgroundFill(Color.CHOCOLATE, null, null)));
this.setMaxHeight(100);
this.setMaxWidth(200);
this.setMinHeight(0);
this.setMinWidth(0);
this.setOnMousePressed((e) -> this.setMaxWidth(20));
}
}
The only way i can think of, would be using a rectangle as the clip for the parentNode and binding its height and width to the parentNode's width and height properties, here is a working example:
#Override
public void start(Stage primaryStage) {
primaryStage.setTitle("Parent Child Relationship!");
ParentNode parentNode = new ParentNode();
parentNode.maxWidthProperty().bind(primaryStage.widthProperty().subtract(200));
Rectangle clip = new Rectangle();
clip.widthProperty().bind(parentNode.widthProperty());
clip.heightProperty().bind(parentNode.heightProperty());
parentNode.setClip(clip);
StackPane root = new StackPane();
root.getChildren().add(parentNode);
primaryStage.setScene(new Scene(root, 400, 250));
primaryStage.show();
}
(i bound the parentNode's width to the windows width just so you see it working while you resize the window)

Fitting rotated ImageView into Application Window / Scene

In JavaFX I am trying to show an rotated ImageView in an Application Window.
Therefore I have put it into a stackPane to have it always centered and I have bound the widths/heights of the ImageView and the stackPane to the scene's width/height to view it just as large as possible.
This works fine as soon as the Image is not rotated.
As soon as I rotate the Image by 90° using stackPane.setRotate(90) (and exchange binding for width/height) then the stackPane is no longer bound to the upper left corner of the Application Window (or scene).
What can I do to place the rotated image correctly?
In the example code [any key] will toggle the rotation 90°/0° so the location problem of the rotated image becomes visible:
public class RotationTest extends Application {
boolean rotated = false;
public static void main(String[] args) {
Application.launch(args);
}
#Override
public void start(Stage primaryStage) {
primaryStage.setTitle("Rotation test");
Group root = new Group();
Scene scene = new Scene(root, 1024,768);
//a stackPane is used to center the image
StackPane stackPane = new StackPane();
stackPane.setStyle("-fx-background-color: black;");
stackPane.prefHeightProperty().bind(scene.heightProperty());
stackPane.prefWidthProperty().bind(scene.widthProperty());
scene.setOnKeyPressed(new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent event) {
//toggle rotate 90° / no rotation
rotated = !rotated;
stackPane.prefHeightProperty().unbind();
stackPane.prefWidthProperty().unbind();
if (rotated){
stackPane.setRotate(90);
//rotation: exchange width and height for binding to scene
stackPane.prefWidthProperty().bind(scene.heightProperty());
stackPane.prefHeightProperty().bind(scene.widthProperty());
}else{
stackPane.setRotate(0);
//no rotation: height is height and width is width
stackPane.prefHeightProperty().bind(scene.heightProperty());
stackPane.prefWidthProperty().bind(scene.widthProperty());
}
}
});
final ImageView imageView = new ImageView("file:D:/test.jpg");
imageView.setPreserveRatio(true);
imageView.fitWidthProperty().bind(stackPane.prefWidthProperty());
imageView.fitHeightProperty().bind(stackPane.prefHeightProperty());
stackPane.getChildren().add(imageView);
root.getChildren().add(stackPane);
primaryStage.setScene(scene);
primaryStage.show();
}
}
Results:
Without rotation the stackPane (black) fits the window perfectly and the image has the correct size even if the window is resized with the mouse.
After pressing [any key] the stackPane is rotated. The stackPane (black) seems to have the correct width/height and also the image seems to be correctly rotated. But the stackPane is no longer in the upper left corner??? It moves around when the window is resized with the mouse???
Why not simply leave the Group and the preferred sizes out of the equation?
The root is automatically resized to fit the scene and you can use it's width/height properties to bind the fitWidth and fitHeight properties:
private static void setRotated(boolean rotated, ImageView targetNode, Pane parent) {
double angle;
if (rotated) {
angle = 90;
targetNode.fitWidthProperty().bind(parent.heightProperty());
targetNode.fitHeightProperty().bind(parent.widthProperty());
} else {
angle = 0;
targetNode.fitWidthProperty().bind(parent.widthProperty());
targetNode.fitHeightProperty().bind(parent.heightProperty());
}
targetNode.setRotate(angle);
}
#Override
public void start(Stage primaryStage) {
Image image = new Image("file:D:/test.jpg");
ImageView imageView = new ImageView(image);
imageView.setPreserveRatio(true);
StackPane root = new StackPane(imageView);
root.setStyle("-fx-background-color: black;");
// initialize unrotated
setRotated(false, imageView, root);
Scene scene = new Scene(root, 1024, 768);
scene.setOnKeyPressed(evt -> {
// toggle between 0° and 90° rotation
setRotated(imageView.getRotate() == 0, imageView, root);
});
primaryStage.setScene(scene);
primaryStage.show();
}
Note that this may not result in correct layout, if placed in some other layout, since the size constraints may be calculated wrong.
You could implement your own region though to fix this:
public class CenteredImage extends Region {
private final BooleanProperty rotated = new SimpleBooleanProperty();
private final ImageView imageView = new ImageView();
public CenteredImage() {
// make sure layout gets invalidated when the image changes
InvalidationListener listener = o -> requestLayout();
imageProperty().addListener(listener);
rotated.addListener((o, oldValue, newValue) -> {
imageView.setRotate(newValue ? 90 : 0);
requestLayout();
});
getChildren().add(imageView);
imageView.setPreserveRatio(true);
}
public final BooleanProperty rotatedProperty() {
return rotated;
}
public final void setRotated(boolean value) {
this.rotated.set(value);
}
public boolean isRotated() {
return rotated.get();
}
public final void setImage(Image value) {
imageView.setImage(value);
}
public final Image getImage() {
return imageView.getImage();
}
public final ObjectProperty<Image> imageProperty() {
return imageView.imageProperty();
}
#Override
protected double computeMinWidth(double height) {
return 0;
}
#Override
protected double computeMinHeight(double width) {
return 0;
}
#Override
protected double computePrefWidth(double height) {
Image image = getImage();
Insets insets = getInsets();
double add = 0;
if (image != null && height > 0) {
height -= insets.getBottom() + insets.getTop();
add = isRotated()
? height / image.getWidth() * image.getHeight()
: height / image.getHeight() * image.getWidth();
}
return insets.getLeft() + insets.getRight() + add;
}
#Override
protected double computePrefHeight(double width) {
Image image = getImage();
Insets insets = getInsets();
double add = 0;
if (image != null && width > 0) {
width -= insets.getLeft() + insets.getRight();
add = isRotated()
? width / image.getHeight() * image.getWidth()
: width / image.getWidth() * image.getHeight();
}
return insets.getTop() + insets.getBottom() + add;
}
#Override
protected double computeMaxWidth(double height) {
return Double.MAX_VALUE;
}
#Override
protected double computeMaxHeight(double width) {
return Double.MAX_VALUE;
}
#Override
protected void layoutChildren() {
Insets insets = getInsets();
double left = insets.getLeft();
double top = insets.getTop();
double availableWidth = getWidth() - left - insets.getRight();
double availableHeight = getHeight() - top - insets.getBottom();
// set fit sizes
if (isRotated()) {
imageView.setFitWidth(availableHeight);
imageView.setFitHeight(availableWidth);
} else {
imageView.setFitWidth(availableWidth);
imageView.setFitHeight(availableHeight);
}
// place image
layoutInArea(imageView, left, top, availableWidth, availableHeight, 0, null, false,
false, HPos.CENTER, VPos.CENTER);
}
}
#Override
public void start(Stage primaryStage) {
Image image = new Image("file:D:/test.jpg");
ImageView imageView = new ImageView(image);
imageView.setPreserveRatio(true);
CenteredImage imageArea = new CenteredImage();
imageArea.setImage(image);
imageArea.setStyle("-fx-background-color: black;");
imageArea.setPrefWidth(300);
SplitPane splitPane = new SplitPane(new Region(), imageArea);
SplitPane.setResizableWithParent(imageArea, true);
Scene scene = new Scene(splitPane, 1024, 768);
scene.setOnKeyPressed(evt -> {
// toggle between 0° and 90° rotation
imageArea.setRotated(!imageArea.isRotated());
});
primaryStage.setScene(scene);
primaryStage.show();
}
I found a solution :-) Fabian's approach inspired me (thank you!!) And my old friend Pit helped me with debugging (also thank you!!)
It seems that the layout location algorithm of JavaFX has a problem when resize() is applied to rotated Panes (or even Nodes - I have not tried):
Following Fabian's idea I debugged into the layoutChildren() method of class Pane. I found that the relocation after setRotate() is correct and keeps the center of the child pane as expected. But as soon as resize() is called (which is done because of fitting the rotated child pane again into its father and additionally always when the window is resized by the user) the origin calculation goes wrong:
The picture above depicts a sequence of setRotate(90), resize() and relocate() in green and the same for setRotate(270) in blue. A little blue/green circle depicts the corresponding origin together with its coordinates in the 1024x786 example.
Analysis
It seems that for calculation the position of the Pane resize() does not use the height and width from BoundsInParent-Property (see JavaFX-Docu of Node) but from getWidth() and getHeight() which seem to reflect BoundsInLocal. As a consequence, for rotations of 90° or 270° height and width seem to be interchanged. Therefore the error in the calculation for the new origin is just the half of the difference between width and height (delta=(width-height)/2) when resize() tries to center the child pane again after the resizing.
Solution
A relocation(delta,-delta) needs to be applied after resizing for Panes with rotation=90 or 270 degrees.
The structure of my implementation follows Fabian's basic idea: I have build a layouter RotatablePaneLayouter:Region that just overwrites the layoutChildren() method. In its constructor it gets a Pane (in my example a StackPane) which can contain any number of children (in my example an ImageView) and that can be rotated.
LayoutChildren() then just executes resize() and relocate() for the child pane to fit it completely into the RotateablePaneLayouter respecting the orientation of the child pane.
The Layouter Helper (RotateablePaneLayouter:Region)
public class RotatablePaneLayouter extends Region {
private Pane child;
public RotatablePaneLayouter(Pane child) {
getChildren().add(child);
this.child = child;
// make sure layout gets invalidated when the child orientation changes
child.rotateProperty().addListener(new ChangeListener<Number>() {
#Override
public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) {
requestLayout();
}
});
}
#Override
protected void layoutChildren() {
// set fit sizes:
//resize child to fit into RotatablePane and correct movement caused by resizing if necessary
if ((child.getRotate() == 90)||(child.getRotate() == 270)) {
//vertical
child.resize( getHeight(), getWidth() ); //exchange width and height
// and relocate to correct movement caused by resizing
double delta = (getWidth() - getHeight()) / 2;
child.relocate(delta,-delta);
} else {
//horizontal
child.resize( getWidth(), getHeight() ); //keep width and height
//with 0° or 180° resize does no movement to be corrected
child.relocate(0,0);
}
}
}
To use it: Place the Pane to be rotated into the Layouter first instead of placing the Pane directly.
Here the code for the example's main program. You can use the space bar to rotate the child pane by 90, 180, 270 and again 0 degrees. You can also resize the window with the mouse. The layouter always manages to place the rotated pane correctly.
Expample for using the Layouter
public class RotationTest extends Application {
public static void main(String[] args) {
Application.launch(args);
}
#Override
public void start(Stage primaryStage) {
//image in a StackPane to be rotated
final ImageView imageView = new ImageView("file:D:/Test_org.jpg");
imageView.setPreserveRatio(true);
StackPane stackPane = new StackPane(imageView); //a stackPane is used to center the image
stackPane.setStyle("-fx-background-color: black;");
imageView.fitWidthProperty().bind(stackPane.widthProperty());
imageView.fitHeightProperty().bind(stackPane.heightProperty());
//container for layouting rotated Panes
RotatablePaneLayouter root = new RotatablePaneLayouter(stackPane);
root.setStyle("-fx-background-color: blue;");
Scene scene = new Scene(root, 1024,768);
scene.setOnKeyPressed(new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent event) {
if (event.getCode() == KeyCode.SPACE) {
//rotate additionally 90°
stackPane.setRotate((stackPane.getRotate() + 90) % 360);
}
}
});
primaryStage.setTitle("Rotation test");
primaryStage.setScene(scene);
primaryStage.show();
}
}
For me this seems like a workaround of a javaFX bug in resize().

How to keep angle of DropShadow when node is rotated

When you apply a DropShadow on a node which is rotated, then the DropShadow rotates with it. Is there a simple way to keep the DropShadow angle where it is, e. g. bottom right even when the node is rotated?
I know that it would work if I put all the nodes into a group and apply the shadow on the group, but that's unfortunately not an option in my case.
Sample image:
left rectangle with drop shadow
right rectangle with same drop shadow, but rotated by 180 degrees
You see, it looks wrong with the shadows being in opposite directions.
Code
public class HelloEffects extends Application {
Stage stage;
Scene scene;
#Override
public void start(Stage stage) {
Group group = new Group();
DropShadow ds1 = new DropShadow();
ds1.setOffsetY(4.0f);
ds1.setOffsetX(4.0f);
ds1.setColor(Color.BLACK);
Rectangle rect1 = new Rectangle( 100, 200);
rect1.relocate(100, 100);
rect1.setEffect(ds1);
rect1.setFill(Color.RED);
Rectangle rect2 = new Rectangle( 100, 200);
rect2.relocate(300, 100);
rect2.setEffect(ds1);
rect2.setFill(Color.RED);
rect2.setRotate(180);
group.getChildren().addAll(rect1, rect2);
scene = new Scene( group, 840, 680);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
Application.launch(args);
}
}
You should add a container to rect2 and apply the effect on the container, the container can be either Pane or Group:
Group rect2Container = new Group(rect2);
rect2Container.setEffect(ds1);
group.getChildren().addAll(rect1, rect2Container);

How to create dynamically Resizable shapes in javafx?

I have three problems:
I want to create resizable shapes with box bounding...
I also want to know how to get child seleted in a Pane.
I'm creating multiple shapes on a pane. I want to change some property of that shape say Fill.. How do i do it??
Thanx
Next example will answer your questions:
for (1) it uses binding, connecting pane size with rectangle size
for (2) it adds setOnMouseClick for each rectangle which stores clicked one in the lastOne field.
for (3) see code of setOnMouseClick() handler
public class RectangleGrid extends Application {
private Rectangle lastOne;
public void start(Stage stage) throws Exception {
Pane root = new Pane();
int grid_x = 7; //number of rows
int grid_y = 7; //number of columns
// this binding will find out which parameter is smaller: height or width
NumberBinding rectsAreaSize = Bindings.min(root.heightProperty(), root.widthProperty());
for (int x = 0; x < grid_x; x++) {
for (int y = 0; y < grid_y; y++) {
Rectangle rectangle = new Rectangle();
rectangle.setStroke(Color.WHITE);
rectangle.setOnMouseClicked(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent t) {
if (lastOne != null) {
lastOne.setFill(Color.BLACK);
}
// remembering clicks
lastOne = (Rectangle) t.getSource();
// updating fill
lastOne.setFill(Color.RED);
}
});
// here we position rects (this depends on pane size as well)
rectangle.xProperty().bind(rectsAreaSize.multiply(x).divide(grid_x));
rectangle.yProperty().bind(rectsAreaSize.multiply(y).divide(grid_y));
// here we bind rectangle size to pane size
rectangle.heightProperty().bind(rectsAreaSize.divide(grid_x));
rectangle.widthProperty().bind(rectangle.heightProperty());
root.getChildren().add(rectangle);
}
}
stage.setScene(new Scene(root, 500, 500));
stage.show();
}
public static void main(String[] args) { launch(); }
}

Resources