Border-Radius and Shadow on ImageView - javafx

I want to apply a border-radius and a shadow in JavaFX.
In CSS3 it would be:
box-shadow: rgba(0,0,0,0.8) 0 0 10px;
border-radius: 3px;
Now I want this in JavaFX, but even the border-radius is not working in the JavaFX Scene Builder. Here is a screenshot of my problem:
(source: rapid-img.de)
On the screenshot you can see that I use:
-fx-border-radius: 10 10 10 10;
-fx-background-radius: 10 10 10 10;

Use the following css to get a drop shadow:
-fx-effect: dropshadow(three-pass-box, rgba(0,0,0,0.8), 10, 0, 0, 0);
See the JavaFX CSS Reference guide for details.
To get the border in addition to the drop shadow, place your ImageView containing your Image in a StackPane. And apply the effect css above to the StackPane, in addition to a background and padding on the StackPane.
For example the css below applied to the StackPane containing your ImageView will provide a red border around your image:
-fx-padding: 10;
-fx-background-color: firebrick;
If you want the background defining your border curved at the edges, then use:
-fx-background-radius: 5;
That gets you an image like below where your image is enclosed in a shadowed border:
If you want to actually round the image itself, it's a bit trickier. You need to apply some code to:
Clip the image to a rounded rectangle.
Snapshot the clipped image.
Store the snapshot image back in the ImageView.
Remove the clip from the ImageView.
Apply the drop shadow effect to the ImageView.
Then you can get something like below:
Some code for that "BatmanLost.java":
import javafx.application.Application;
import javafx.fxml.*;
import javafx.scene.*;
import javafx.scene.effect.DropShadow;
import javafx.scene.image.*;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
import java.io.IOException;
public class BatmanLost extends Application {
class WingClipper {
#FXML
private ImageView imageView;
#FXML
public void initialize() {
// set a clip to apply rounded border to the original image.
Rectangle clip = new Rectangle(
imageView.getFitWidth(), imageView.getFitHeight()
);
clip.setArcWidth(20);
clip.setArcHeight(20);
imageView.setClip(clip);
// snapshot the rounded image.
SnapshotParameters parameters = new SnapshotParameters();
parameters.setFill(Color.TRANSPARENT);
WritableImage image = imageView.snapshot(parameters, null);
// remove the rounding clip so that our effect can show through.
imageView.setClip(null);
// apply a shadow effect.
imageView.setEffect(new DropShadow(20, Color.BLACK));
// store the rounded image in the imageView.
imageView.setImage(image);
}
}
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage stage) throws IOException {
FXMLLoader loader = new FXMLLoader(
getClass().getResource(
"batmanlostinthemix.fxml"
)
);
loader.setController(new WingClipper());
Pane batman = loader.load();
stage.setTitle("Where's Batman?");
stage.setScene(new Scene(batman));
stage.show();
}
}
With some FXML "batmanlostinthemix.fxml":
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.image.Image?>
<?import javafx.scene.image.ImageView?>
<?import javafx.scene.layout.AnchorPane?>
<AnchorPane id="AnchorPane" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="313.0" prefWidth="477.0" style="-fx-background-color: azure;" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/2.2">
<children>
<ImageView fx:id="imageView" layoutX="29.0" layoutY="44.0" fitHeight="224.0" fitWidth="400.0" pickOnBounds="true" preserveRatio="true">
<image>
<Image url="http://collider.com/wp-content/uploads/lego-batman-movie-dc-super-heroes-unite-1.jpg" />
</image>
</ImageView>
</children>
</AnchorPane>

If you use the answer that jewelsea provided, then make sure to test first whether or not clipping is supported:
Platform.isSupported(ConditionalFeature.SHAPE_CLIP)
I try to avoid conditional features unless I have to use them. In my case, I wanted to make a picture round. So an alternative would be to use a Circle instead of an ImageView:
Circle circle = new Circle(14);
ImagePattern pattern = new ImagePattern(myImage);
circle.setFill(pattern);
The circle can be enhanced to use a shadow if supported:
if (Platform.isSupported(ConditionalFeature.EFFECT)) {
circle.setEffect(new DropShadow(8, Color.rgb(0, 0, 0, 0.8)));
}

Thanks martin for pointing ImagePattern
Rectangle rectangle = new Rectangle(0, 0, 280, 180);
rectangle.setArcWidth(30.0); // Corner radius
rectangle.setArcHeight(30.0);
ImagePattern pattern = new ImagePattern(
new Image("file:images/mustang-gt.jpg", 280, 180, false, false) // Resizing
);
rectangle.setFill(pattern);
rectangle.setEffect(new DropShadow(20, Color.BLACK)); // Shadow
Note that here Iam resizing the image to match the size of the rectangle during its load to ensure smoothness.

Related

JavaFx : Enable outer scrollview once the end of inner scrollview has been reached

I have a parent scrollpane which contains a webview as one of its children. When I scroll over the webview by having the mouse pointer on the webview area, it continues scrolling as expected till the end of the webview. However, is there a way where I can make the parent scrollpane scroll down, once the end of the child-webview is reached, while still the mouse pointer is on the webview-area ?
Sample Structure :
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.web.*?>
<ScrollPane prefHeight="455.0" prefWidth="602.0" xmlns="http://javafx.com/javafx/10.0.2-internal" xmlns:fx="http://javafx.com/fxml/1" fx:controller="wes.we.fx.view.ui.express.reports.generic.Demo">
<content>
<AnchorPane prefHeight="400.0" prefWidth="600.0">
<children>
<VBox layoutX="26.0">
<children>
<WebView prefHeight="316.0" prefWidth="530.0" />
</children>
</VBox>
</children>
</AnchorPane>
</content>
</ScrollPane>
Explanation of my solution: When using a WebView you can inject javascript which is what I used to pull values of the page to figure out when I was at the bottom. I am no JavaScript wiz so Im am not sure if there is a better solution here(I could not find one). I used the formula of screenTopPosition + screenTotalHeight to get the screenBottomPosition and compared that to the totalPageHeight if they are equal then you are at the bottom of the page this all worked as expected. This is where I feel it gets "Hacky" I couldn't figure out how to get the focus of the scrollbar from the scrollPane when at the bottom of the page. So my only other idea was to increment the scrollPane Vvalue by a fixed value when you were at the bottom of the webpage. The problem is the value you choose to increment it by may be different than the actual scrolling value incrementing I chose .1 once the Vvalue hits 1 it is at the bottom of the scrollPane
This is not exactly how I wanted to solve this problem hopefully someone can recommend a better solution that feels less "Hacky" and I would be glad to edit my answer if provided with guidance on how to figure out what the default incrementing is for the Vvalue
public class Main extends Application {
#Override
public void start(Stage stage) {
ScrollPane scrollPane = new ScrollPane();
scrollPane.setPrefSize(602, 455);
AnchorPane anchorPane = new AnchorPane();
anchorPane.setPrefSize(600, 400);
scrollPane.setContent(anchorPane);
VBox vBox = new VBox();
vBox.setPrefWidth(530);
vBox.setLayoutX(26);
anchorPane.getChildren().add(vBox);
WebView webView = new WebView();
webView.setPrefSize(530,316);
webView.getEngine().load("http://www.google.com");
webView.setOnScroll(event -> {
Integer screenTopPosition = (Integer) (webView.getEngine().executeScript("document.body.scrollTop"));
Integer screenTotalHeight = (Integer) webView.getEngine().executeScript("document.body.clientHeight");
Integer screenBottomPosition = screenTopPosition + screenTotalHeight;
Integer totalPageHeight = (Integer) webView.getEngine().executeScript("document.body.scrollHeight");
if(screenBottomPosition.equals(totalPageHeight)){
System.out.println("Bottom of Page Reached");
scrollPane.setVvalue(scrollPane.getVvalue()+.1);
}
});
vBox.getChildren().add(webView);
Label label = new Label("Content\nContent");
label.setFont(new Font(100));
vBox.getChildren().add(label);
stage.setScene(new Scene(scrollPane));
stage.show();
}
}

How to align Labels with different Text sizes to Botttom?

I have two different Labels, one large one which displays a changing text and a smaller one which displays a % sign, and has to be smaller.
I have these two in a GridPane and aligned the left text to BOTTOM_RIGHT and aligned the right text BOTTOM_LEFT, so there is no gap in between:
How can I get the two texts to align so they look like the following image?
In hindsight, a TextFlow would have been a better solution, but I'm much too far into my project to change this now.
This depends on how you've built your Scene. If you're using FXML/Scene Builder, you'd set the valignment property for your GridPane row to BASELINE.
Here's a complete FXML that replicates your screenshots (without styling):
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.*?>
<GridPane xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/11.0.1">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0"/>
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0"/>
</columnConstraints>
<rowConstraints>
<RowConstraints minHeight="-Infinity" valignment="BASELINE" vgrow="SOMETIMES"/>
</rowConstraints>
<children>
<Label style="-fx-font-size: 5.0em;" text="44"/>
<Label style="-fx-font-size: 3.0em;" text="\%" GridPane.columnIndex="1"/>
</children>
</GridPane>
If you're designing the Scene in Java, the end goal is the same: set the valignment of the row to BASELINE:
import javafx.application.Application;
import javafx.geometry.VPos;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Region;
import javafx.scene.layout.RowConstraints;
import javafx.stage.Stage;
public class TextAlignSample extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
// Simple Interface
GridPane root = new GridPane();
RowConstraints constraints = new RowConstraints(
Region.USE_PREF_SIZE,
Region.USE_COMPUTED_SIZE,
Region.USE_COMPUTED_SIZE);
constraints.setValignment(VPos.BASELINE);
root.getRowConstraints().add(constraints);
// Add our Labels
root.add(new Label("44") {{
setStyle("-fx-font-size: 5.0em");
}}, 0, 0);
root.add(new Label("%") {{
setStyle("-fx-font-size: 3.0em");
}}, 1, 0);
// Show the stage
primaryStage.setScene(new Scene(root));
primaryStage.setTitle("TextAlignSample Sample");
primaryStage.show();
}
}
The Result:
Sorry for the odd color; something is wrong with my screen cap software

How to draw a line that intersects a text node without seeing the line inside the node

I would like to draw a vertical line, and that vertical line may intersect a text object. When I do so without any modification, the line goes right through the word, making the word hard to read. I want to make it so that the line disappears when it reaches the text, and continues immediately after the text node. I've tried using toBack() with a css background for the text to make a square around it, but it seems like text nodes have transparent backgrounds, so the line is still visible behind the letters. Is there another way of doing this so that the text is not intersected by the line? Note that the text may or may not be there all the time, and it can be at any coordinate due to the nature of my program, so I can't just draw two lines (One before the text and one after).
Using a Label instead of a Text will make things easier. Label is (ultimately) a subclass of Region, so it has properties such as a background color which can be styled, allowing it to have an opaque background. Using the CSS rule
-fx-background-color: -fx-background ;
on a label will give it the default background color. By contrast, Text is a subclass of Shape and only has properties such as fill and stroke, which color the interior and exterior of the glyphs that make up the text, respectively.
So you can do
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.scene.shape.Line;
import javafx.scene.text.Text;
import javafx.stage.Stage;
public class TextVsLabel extends Application {
#Override
public void start(Stage primaryStage) {
StackPane labelPane = new StackPane();
labelPane.setMinSize(250, 250);
Label label = new Label("A Label");
label.setStyle("-fx-background-color: -fx-background;");
labelPane.getChildren().add(label);
addLineToPane(labelPane);
StackPane textPane = new StackPane();
textPane.setMinSize(250, 250);
textPane.getChildren().add(new Text("A Text"));
addLineToPane(textPane);
HBox root = new HBox(5, labelPane, textPane);
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
}
private void addLineToPane(Pane pane) {
Line line = new Line();
line.startXProperty().bind(pane.widthProperty().divide(2));
line.endXProperty().bind(line.startXProperty());
line.setStartY(0);
line.endYProperty().bind(pane.heightProperty());
// add line to beginning of pane's parent list, so it appears
// behind everything else
pane.getChildren().add(0, line);
}
public static void main(String[] args) {
launch(args);
}
}
which results in
Note that if you need more space around the text in the label, you can add padding to the label: -fx-padding: 1em;, etc.

No rounded corners with background image in JavaFX [duplicate]

I want to apply a border-radius and a shadow in JavaFX.
In CSS3 it would be:
box-shadow: rgba(0,0,0,0.8) 0 0 10px;
border-radius: 3px;
Now I want this in JavaFX, but even the border-radius is not working in the JavaFX Scene Builder. Here is a screenshot of my problem:
(source: rapid-img.de)
On the screenshot you can see that I use:
-fx-border-radius: 10 10 10 10;
-fx-background-radius: 10 10 10 10;
Use the following css to get a drop shadow:
-fx-effect: dropshadow(three-pass-box, rgba(0,0,0,0.8), 10, 0, 0, 0);
See the JavaFX CSS Reference guide for details.
To get the border in addition to the drop shadow, place your ImageView containing your Image in a StackPane. And apply the effect css above to the StackPane, in addition to a background and padding on the StackPane.
For example the css below applied to the StackPane containing your ImageView will provide a red border around your image:
-fx-padding: 10;
-fx-background-color: firebrick;
If you want the background defining your border curved at the edges, then use:
-fx-background-radius: 5;
That gets you an image like below where your image is enclosed in a shadowed border:
If you want to actually round the image itself, it's a bit trickier. You need to apply some code to:
Clip the image to a rounded rectangle.
Snapshot the clipped image.
Store the snapshot image back in the ImageView.
Remove the clip from the ImageView.
Apply the drop shadow effect to the ImageView.
Then you can get something like below:
Some code for that "BatmanLost.java":
import javafx.application.Application;
import javafx.fxml.*;
import javafx.scene.*;
import javafx.scene.effect.DropShadow;
import javafx.scene.image.*;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
import java.io.IOException;
public class BatmanLost extends Application {
class WingClipper {
#FXML
private ImageView imageView;
#FXML
public void initialize() {
// set a clip to apply rounded border to the original image.
Rectangle clip = new Rectangle(
imageView.getFitWidth(), imageView.getFitHeight()
);
clip.setArcWidth(20);
clip.setArcHeight(20);
imageView.setClip(clip);
// snapshot the rounded image.
SnapshotParameters parameters = new SnapshotParameters();
parameters.setFill(Color.TRANSPARENT);
WritableImage image = imageView.snapshot(parameters, null);
// remove the rounding clip so that our effect can show through.
imageView.setClip(null);
// apply a shadow effect.
imageView.setEffect(new DropShadow(20, Color.BLACK));
// store the rounded image in the imageView.
imageView.setImage(image);
}
}
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage stage) throws IOException {
FXMLLoader loader = new FXMLLoader(
getClass().getResource(
"batmanlostinthemix.fxml"
)
);
loader.setController(new WingClipper());
Pane batman = loader.load();
stage.setTitle("Where's Batman?");
stage.setScene(new Scene(batman));
stage.show();
}
}
With some FXML "batmanlostinthemix.fxml":
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.image.Image?>
<?import javafx.scene.image.ImageView?>
<?import javafx.scene.layout.AnchorPane?>
<AnchorPane id="AnchorPane" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="313.0" prefWidth="477.0" style="-fx-background-color: azure;" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/2.2">
<children>
<ImageView fx:id="imageView" layoutX="29.0" layoutY="44.0" fitHeight="224.0" fitWidth="400.0" pickOnBounds="true" preserveRatio="true">
<image>
<Image url="http://collider.com/wp-content/uploads/lego-batman-movie-dc-super-heroes-unite-1.jpg" />
</image>
</ImageView>
</children>
</AnchorPane>
If you use the answer that jewelsea provided, then make sure to test first whether or not clipping is supported:
Platform.isSupported(ConditionalFeature.SHAPE_CLIP)
I try to avoid conditional features unless I have to use them. In my case, I wanted to make a picture round. So an alternative would be to use a Circle instead of an ImageView:
Circle circle = new Circle(14);
ImagePattern pattern = new ImagePattern(myImage);
circle.setFill(pattern);
The circle can be enhanced to use a shadow if supported:
if (Platform.isSupported(ConditionalFeature.EFFECT)) {
circle.setEffect(new DropShadow(8, Color.rgb(0, 0, 0, 0.8)));
}
Thanks martin for pointing ImagePattern
Rectangle rectangle = new Rectangle(0, 0, 280, 180);
rectangle.setArcWidth(30.0); // Corner radius
rectangle.setArcHeight(30.0);
ImagePattern pattern = new ImagePattern(
new Image("file:images/mustang-gt.jpg", 280, 180, false, false) // Resizing
);
rectangle.setFill(pattern);
rectangle.setEffect(new DropShadow(20, Color.BLACK)); // Shadow
Note that here Iam resizing the image to match the size of the rectangle during its load to ensure smoothness.

Add dropShadow only to border of grid pane JavaFx 2.2

Want to add drop shadow only to the border of Grid pane not to inner child elements
here is the image of screen showing the effect.
Use a StackPane and place your GridPane in it.
Style your StackPane with CSS to apply a background color, insets and a drop shadow.
See Drop Shadow in an undecorated Pane! JAVAFX for some sample CSS.
Here is a small standalone sample app (used Java 8b120 on OS X 10.9), to demonstrate the effect:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class Shadowed extends Application {
public static void main(String[] args) { launch(args); }
#Override
public void start(Stage stage) throws Exception {
Label clear = new Label("Clear, with no shadow");
StackPane shadowedPane = new StackPane(clear);
shadowedPane.setStyle(
"-fx-background-color: palegreen; " +
"-fx-background-insets: 10; " +
"-fx-background-radius: 10; " +
"-fx-effect: dropshadow(three-pass-box, purple, 10, 0, 0, 0);"
);
shadowedPane.setPrefSize(200, 50);
stage.setScene(new Scene(shadowedPane));
stage.show();
}
}
Addressing additional questions
Is CSS the only option?
No, this could be done in code instead of CSS, by using the DropShadow effect.
shadowedPane.setEffect(new DropShadow(10, Color.PURPLE));
Why does it work?
Because setting a background provides a clear edge to the node to which the shadow effect is applied.
Why does DropShadow work differently depending on if it's applied on a root node or a nested container?
There is no real differing behavior in the drop shadow processing between a root node or nested container. The drop shadow effect depends on whether the item having the effect applied has a transparent (or null) background or not. Note though that a root node usually fills a stage. So, if the root node has a non-transparent background color and no insets are supplied, then the shadow on the root node will not be seen as it will fall outside the visible area of the stage.
I'll offer a guess at what is happening. I think what the drop shadow effect is doing is calculating the exterior shape of the node, then applying the shadow to it. When that node has a background color, then the you will see the shadow of the node background, which is what is seen in the image supplied. If the node has no background color, then the edges of the nodes are calculated from the child nodes, so all of the children get shadows.
Now what happened is that between JavaFX 2.2 when the question was asked and JavaFX 8, the default theme for JavaFX moved from Caspian to Modena. With Caspian panes did not have any background color by default. However for Modena, panes do have a slight off-white background color by default. This means that the original poster's blurry text inside the grid pane won't occur by default in Java 8, as the GridPane will have a background which is shadowed, instead of interior text being shadowed. This can be verified via the running the following program and varying the commented out line for setting the stylesheet to Caspian:
import javafx.application.Application;
import javafx.geometry.HPos;
import javafx.geometry.VPos;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.effect.DropShadow;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Priority;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
public class Shadowed extends Application {
public static void main(String[] args) { launch(args); }
#Override
public void start(Stage stage) throws Exception {
// setUserAgentStylesheet(STYLESHEET_CASPIAN);
Label clear = new Label("Clear, with no shadow");
GridPane shadowedPane = new GridPane();
shadowedPane.add(clear, 0, 0);
GridPane.setHalignment(clear, HPos.CENTER);
GridPane.setValignment(clear, VPos.CENTER);
GridPane.setHgrow(clear, Priority.ALWAYS);
GridPane.setVgrow(clear, Priority.ALWAYS);
shadowedPane.setStyle(
// "-fx-background-color: transparent; " +
// "-fx-background-color: palegreen; " +
"-fx-background-insets: 10; " +
"-fx-background-radius: 10; "
);
shadowedPane.setPrefSize(200, 50);
shadowedPane.setEffect(new DropShadow(10, Color.PURPLE));
stage.setScene(new Scene(shadowedPane));
stage.show();
}
}
jewelsea's answer is correct, but please note that it does not work when
// explicitly transparent
-fx-background-color: transparent;
or
// effectively transparent
-fx-background-color: #FFFFFF00;
This can be really frustrating if you are trying to apply the drop shadow to the (Stack)Pane's entire bounding box area and not it's child contents! Setting a non-transparent background color works, but if you have many elements side by side or on top of each other (i.e. close together), you may see undesirable overlap effects with the drop shadows.

Resources