I am trying to simulate the effect one would get from this css example:
border-radius: 50%;
From searching the API and reading posts on forums including this one, I found that I should be using -fx-background-radius. This however is not giving me the wanted effect.
I setup a picture as the background using -fx-background-image:url(...) and then I want to make it into a circle.
How can I achieve this?
Update
So I see that I was not being too specific so let me try to elaborate:
I created a Pane object, that does extend the Region class from JavaFX.
main.fxml:
...
<Pane styleClass="wrapper">
<Pane layoutX="34.0" layoutY="28.0" styleClass="image" />
</Pane>
For this pane I created the styleclass image as seen above.
main.css:
.list-contact .image {
-fx-alignment:left;
-fx-pref-height:40px;
-fx-pref-width:40px;
-fx-background-radius:50%;
-fx-background-image:url(...);
-fx-background-repeat:no-repeat;
}
The effect I get:
The effect I want:
I hope this explains it better.
This is not possible from CSS alone, since ImageView does not support any of Region's CSS properties.
However you can use a Ellipse as clip for the ImageView:
#Override
public void start(Stage primaryStage) throws MalformedURLException {
Image img = new Image("https://upload.wikimedia.org/wikipedia/commons/thumb/2/23/Space_Needle_2011-07-04.jpg/304px-Space_Needle_2011-07-04.jpg");
ImageView iv = new ImageView(img);
Ellipse ellipse = new Ellipse(img.getWidth() / 2, img.getHeight() / 2, img.getWidth() / 2, img.getHeight() / 2);
iv.setClip(ellipse);
Text text = new Text("Space Needle, Seattle, Washington, USA");
StackPane.setAlignment(text, Pos.TOP_CENTER);
StackPane root = new StackPane(text, iv);
Scene scene = new Scene(root, img.getWidth(), img.getHeight());
scene.setFill(Color.AQUAMARINE);
primaryStage.setScene(scene);
primaryStage.show();
}
I know it doesn't look good to let the image cover the text. This is only done for the purpose of demonstration.
It looks like a CSS border-radius: 50% should create an elliptical border, and JavaFX CSS does support the % shorthand for either -fx-border-radius or -fx-background-radius. To get the desired effect, however, use Path.subtract() to create an elliptical matte for the image, as shown below.
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Ellipse;
import javafx.scene.shape.Path;
import javafx.scene.shape.Rectangle;
import javafx.scene.shape.Shape;
import javafx.stage.Stage;
/**
* #see http://stackoverflow.com/a/38008678/230513
*/
public class Test extends Application {
private final Image IMAGE = new Image("http://i.imgur.com/kxXhIH1.jpg");
#Override
public void start(Stage primaryStage) {
primaryStage.setTitle("Test");
int w = (int) (IMAGE.getWidth());
int h = (int) (IMAGE.getHeight());
ImageView view = new ImageView(IMAGE);
Rectangle r = new Rectangle(w, h);
Ellipse e = new Ellipse(w / 2, h / 2, w / 2, h / 2);
Shape matte = Path.subtract(r, e);
matte.setFill(Color.SIENNA);
StackPane root = new StackPane();
root.getChildren().addAll(view, matte);
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
In one Line with Circle as Clip.You can use setClip(any shape).:
imageView.setClip(new Circle(width,height,radius);
The width,height,radius have to be slighty smaller that ImageView size to work.
Inspired by GuiGarage web site.
Related
I have a simple piechart without name, labels, legend. I need only a circle itself. But I can't get rid of that padding between borders and content. I've tried these all and received no result (TornadoFX CSS):
diagram {
padding = box(0.px)
labelPadding = box(0.px)
borderImageInsets += box(0.px)
borderInsets += box(0.px)
backgroundInsets += box(0.px)
maxWidth = 25.px
maxHeight = 25.px
labelLineLength = 0.px
borderColor += box(Color.GREEN)
}
I want to get rid of this extra-space between a circle and green borders. Does anybody know any Java / CSS / TornadoFX solutions/options here ?
You can use a negative value for e. g. padding. Please have a look at this small example (JavaFX):
package sample;
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.chart.PieChart;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class Main extends Application {
#Override
public void start(Stage stage) {
VBox vBox = new VBox();
ObservableList<PieChart.Data> pieChartData =
FXCollections.observableArrayList(
new PieChart.Data("", 75),
new PieChart.Data("", 25));
final PieChart chart = new PieChart(pieChartData);
chart.setLegendVisible(false);
// Negative value for padding:
chart.setStyle("-fx-padding: -35; -fx-border-color: green; -fx-border-width: 3;");
vBox.getChildren().addAll(chart);
stage.setScene(new Scene(vBox));
stage.show();
//chart.setMaxWidth(400d); // careful
}
public static void main(String[] args) {
launch(args);
}
}
You can also quickly set a maximum width for the chart to make the green box a square but be careful please, it could mess with the overall layout:
Javafx linear-gradient repeat seems to reflect the colours rather than repeat.
I wrote a simple application to show what I see when using linear-gradient with repeat to create a striped pattern in my application on a custom Node (a StackPane). In my application this are added as overlays to a XYChart and their height varies. Using a Rectangle wasn't working well which is why I use a Stackpane and set a style on it rather than creating the LinearGradient programmatically.
The colour list is dynamic and varies in size in the application.
The issue is the way linear-gradient flips the list and reflects the colours on each repeat rather than just repeat.
This link describes a similar issue but just adding in endless stops seemless like a messy solution for my issue, it would be much better to add the colours once and repeat.
linear gradient repeat on css for javafx
java.util.List;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
try {
BorderPane root = new BorderPane();
List<Color> colors = Arrays.asList( Color.RED,Color.BLUE,Color.YELLOW,Color.GREEN);
StackPane stackPane = new StackPane();
stackPane.setStyle(getLinearGradientStyle(colors));
root.setCenter(stackPane);
Scene scene = new Scene(root, 400, 400);
primaryStage.setScene(scene);
primaryStage.show();
}
catch (Exception e) {
e.printStackTrace();
}
}
private String getLinearGradientStyle(List<Color> colors) {
StringBuilder stringBuilder = new StringBuilder("-fx-background-color: linear-gradient(from 0px 0px to 10px 10px, repeat,");
for (int i = 0; i < colors.size(); i++) {
stringBuilder.append("rgb(")
.append((int) (colors.get(i).getRed() * 255)).append(",")
.append((int) (colors.get(i).getGreen() * 255)).append(",")
.append((int) (colors.get(i).getBlue() * 255))
.append(")")
.append(" ").append(getPercentage(i+1, colors.size()+1) );
if (i < colors.size() - 1) {
stringBuilder.append(",");
}
}
stringBuilder.append(");");
System.out.println("Main.getLinearGradientStyle():"+stringBuilder);
return stringBuilder.toString();
}
private String getPercentage(float i, int size) {
return (((1.0f / size) * 100 )*i)+ "%";
}
public static void main(String[] args) {
launch(args);
}
Here's a CSS3 example using repeating-linear-gradient:
https://tympanus.net/codrops/css_reference/repeating-linear-gradient/
scroll down to the following text: will create a striped background, where each linear gradient is a three-stripe gradient, repeated infinitely (this is the example)
My example uses a diagonal pattern which is what I need but the above example shows what I'd like to see in terms of solid repeating colours with out reflection in normal css.
Thanks for any help
This looks like a bug. If you run the following example (moved the CSS into a file):
Main.java
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.Region;
import javafx.stage.Stage;
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
Region region = new Region();
region.backgroundProperty().addListener((obs, ov, nv) ->
System.out.println(nv.getFills().get(0).getFill()));
Scene scene = new Scene(region, 500, 300);
scene.getStylesheets().add("Main.css");
primaryStage.setScene(scene);
primaryStage.show();
}
}
Main.css
.root {
-fx-background-color: linear-gradient(from 0px 0px to 10px 10px, repeat, red 20%, blue 40%, yellow 60%, green 80%);
}
You'll see the following printed out:
linear-gradient(from 0.0px 0.0px to 10.0px 10.0px, reflect, 0xff0000ff 0.0%, 0xff0000ff 20.0%, 0x0000ffff 40.0%, 0xffff00ff 60.0%, 0x008000ff 80.0%, 0x008000ff 100.0%)
As you can see, despite using "repeat" in the CSS the LinearGradient that is created uses "reflect".
There is likely nothing you can do about this bug yourself, but if you don't mind setting the background in code (or probably even FXML) then the following should do what you want:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.Background;
import javafx.scene.layout.BackgroundFill;
import javafx.scene.layout.Region;
import javafx.scene.paint.Color;
import javafx.scene.paint.CycleMethod;
import javafx.scene.paint.LinearGradient;
import javafx.scene.paint.Stop;
import javafx.stage.Stage;
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
LinearGradient gradient = new LinearGradient(0, 0, 10, 10, false, CycleMethod.REPEAT,
new Stop(0.2, Color.RED),
new Stop(0.4, Color.BLUE),
new Stop(0.6, Color.YELLOW),
new Stop(0.8, Color.GREEN)
);
Region region = new Region();
region.setBackground(new Background(new BackgroundFill(gradient, null, null)));
Scene scene = new Scene(region, 500, 300);
primaryStage.setScene(scene);
primaryStage.show();
}
}
You can move the creation of the LinearGradient into a method that takes an arbitrary number of Colors, just like you're currently doing.
If you're interested, I believe the bug is located in javafx.css.CssParser around line 1872 (in JavaFX 12):
CycleMethod cycleMethod = CycleMethod.NO_CYCLE;
if ("reflect".equalsIgnoreCase(arg.token.getText())) {
cycleMethod = CycleMethod.REFLECT;
prev = arg;
arg = arg.nextArg;
} else if ("repeat".equalsIgnoreCase(arg.token.getText())) {
cycleMethod = CycleMethod.REFLECT;
prev = arg;
arg = arg.nextArg;
}
As you can see, it erroneously sets the CycleMethod to REFLECT when the text is equal to "repeat".
A bug report has been filed: JDK-8222222 (GitHub #437). Fix version: openjfx13.
I'm trying to Slice an image to show on a screen.
For example if I change the right margin to 20 of the flower picture on the left and the bottom margin of the flower picture in the right to 20 they should behave as follow:
(the original pictures are shown in the first column)
enter image description here
ViewPort method does exactly what I want, cutting the image and leaving the margin empty but it uses image's original size displaying a zoom effect as it says in the javafx website:
"The rectangular viewport into the image. The viewport is specified in the coordinates of the image, prior to scaling or any other transformations.
If viewport is null, the entire image is displayed. If viewport is non-null, only the portion of the image which falls within the viewport will be displayed. If the image does not fully cover the viewport then any remaining area of the viewport will be empty."
I already tried to use imageView.resize(screenWidth, screenHeight) before setting the viewport but it doesn't work.
Also tried to imageView.setFitWidth(screenWidth), imageView.setFitHeight(screenHeight)
Is it possible to rescale the image so it's displayed as described?
Or any other work-around?
Here is my code for left margin as an example:
mediaContent.resize(screenWidth, screen.getHeight());
// mediaContent.setFitWidth(screenWidth);
// mediaContent.setFitHeight(screen.getHeight());
mediaContent.setViewport(new Rectangle2D(screenWidth-dXLeft, 0, screenWidth, screen.getHeight()));
Update: Tried setting setFitWidth and setFitHeightas suggested stills displays the image zoomed in
`imageView.setFitWidth(space.getX());
imageView.setFitHeight(space.getY());
imageView.setViewport(new Rectangle2D(screen.getWidth()-space.getX(), 0, screen.getWidth(), screen.getHeight()));
imageView.setFitWidth(space.getX());
imageView.setFitHeight(space.getY());
return imageView;`
This is the result when I use the setViewport method when not
setting margins: enter image description here
How it should be when not setting margins: enter image description here
Update 2: Example (modified from java-buddy) zooming in
package javafx_imageview_viewport;
import javafx.application.Application;
import javafx.beans.value.ObservableValue;
import javafx.geometry.Rectangle2D;
import javafx.scene.Scene;
import javafx.scene.control.Slider;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
/**
* #web http://java-buddy.blogspot.com/
*/
public class JavaFX_ImageView_Viewport extends Application {
#Override
public void start(Stage primaryStage) {
ImageView imageView1 = new ImageView(new Image("https://i.imgur.com/6Zl0eQB.jpg"));
imageView1.setFitWidth(150);
imageView1.setFitHeight(100);
//Example to rotate ImageView
Image image2 = new Image("https://i.imgur.com/6Zl0eQB.jpg");
Rectangle2D viewportRect2 = new Rectangle2D(
image2.getWidth()/4,
image2.getHeight()/4,
image2.getWidth()*3/4,
image2.getHeight()*3/4);
ImageView imageView2 = new ImageView(image2);
imageView2.setFitWidth(150);
imageView2.setFitHeight(100);
imageView2.setViewport(viewportRect2);
Slider sliderRotate = new Slider();
sliderRotate.setMin(0);
sliderRotate.setMax(360);
sliderRotate.setValue(0);
sliderRotate.valueProperty().addListener(
(ObservableValue<? extends Number> observable,
Number oldValue, Number newValue) -> {
imageView2.setRotate((double)newValue);
});
//Example to change ViewPort
Image image3 = new Image("https://i.imgur.com/6Zl0eQB.jpg");
Rectangle2D viewportRect3 = new Rectangle2D(
0,
0,
image3.getWidth(),
image3.getHeight());
ImageView imageView3 = new ImageView(image3);
imageView3.setFitWidth(150);
imageView3.setFitHeight(100);
imageView3.setViewport(viewportRect3);
Slider sliderViewPort = new Slider();
sliderViewPort.setMin(0);
sliderViewPort.setMax(1.0);
sliderViewPort.setValue(1.0);
sliderViewPort.valueProperty().addListener(
(ObservableValue<? extends Number> observable,
Number oldValue, Number newValue) -> {
Rectangle2D newViewportRect3 = new Rectangle2D(
0,
0,
(double)newValue*image3.getWidth(),
(double)newValue*image3.getHeight());
imageView3.setViewport(newViewportRect3);
});
VBox vBox = new VBox();
vBox.getChildren().addAll(imageView1,
imageView2, sliderRotate,
imageView3, sliderViewPort);
StackPane root = new StackPane();
root.getChildren().add(vBox);
Scene scene = new Scene(root, 300, 350);
primaryStage.setTitle("java-buddy: ImageVIew ViewPort");
primaryStage.setScene(scene);
primaryStage.show();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}
Example 2 from java-buddy how I want it to behave, cutting the image:
package javafx_imageview_viewport;
import javafx.application.Application;
import javafx.beans.value.ObservableValue;
import javafx.geometry.Rectangle2D;
import javafx.scene.Scene;
import javafx.scene.control.Slider;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
/**
* #web http://java-buddy.blogspot.com/
*/
public class JavaFX_ImageView_Viewport extends Application {
#Override
public void start(Stage primaryStage) {
ImageView imageView1 = new ImageView(new Image("https://i.imgur.com/294AEFU.png"));
//Example to rotate ImageView
Image image2 = new Image("https://i.imgur.com/294AEFU.png");
Rectangle2D viewportRect2 = new Rectangle2D(
image2.getWidth()/4,
image2.getHeight()/4,
image2.getWidth()*3/4,
image2.getHeight()*3/4);
ImageView imageView2 = new ImageView(image2);
imageView2.setViewport(viewportRect2);
Slider sliderRotate = new Slider();
sliderRotate.setMin(0);
sliderRotate.setMax(360);
sliderRotate.setValue(0);
sliderRotate.valueProperty().addListener(
(ObservableValue<? extends Number> observable,
Number oldValue, Number newValue) -> {
imageView2.setRotate((double)newValue);
});
//Example to change ViewPort
Image image3 = new Image("https://i.imgur.com/294AEFU.png");
Rectangle2D viewportRect3 = new Rectangle2D(
0,
0,
image3.getWidth(),
image3.getHeight());
ImageView imageView3 = new ImageView(image3);
imageView3.setViewport(viewportRect3);
Slider sliderViewPort = new Slider();
sliderViewPort.setMin(0);
sliderViewPort.setMax(1.0);
sliderViewPort.setValue(1.0);
sliderViewPort.valueProperty().addListener(
(ObservableValue<? extends Number> observable,
Number oldValue, Number newValue) -> {
Rectangle2D newViewportRect3 = new Rectangle2D(
0,
0,
(double)newValue*image3.getWidth(),
(double)newValue*image3.getHeight());
imageView3.setViewport(newViewportRect3);
});
VBox vBox = new VBox();
vBox.getChildren().addAll(imageView1,
imageView2, sliderRotate,
imageView3, sliderViewPort);
StackPane root = new StackPane();
root.getChildren().add(vBox);
Scene scene = new Scene(root, 300, 350);
primaryStage.setTitle("java-buddy: ImageVIew ViewPort");
primaryStage.setScene(scene);
primaryStage.show();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}
It seems that what I wanted does not make sense.
If I want the image to fit the screen I have to use fitScreenWidth(screenWidth)and fitScreenHeight(screenHeight) but it does not make sense to use viewPort method in this case since the image has been resized already.
viewPort method should be used only with the original image's dimensions.
I would like to show a photo as an ImageView in a ScrollPane with an ZoomIn and ZoomOut Function. But if I reduce by means of scale the imageview, an undesirable empty edge is created in the ScrollPane. How can you make sure that the ScrollPane is always the size of the scaled ImageView?
See the following example. For simplicity, I replaced the ImageView with a rectangle.
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ScrollPane;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
public class ScrollPaneDemo extends Application {
double scale;
Pane contPane = new Pane();
#Override
public void start(Stage primaryStage) {
BorderPane pane = new BorderPane();
ScrollPane sp = new ScrollPane();
sp.setContent(contPane);
sp.setVvalue(0.5);
sp.setHvalue(0.5);
Rectangle rec = new Rectangle(2820, 1240,Color.RED);
scale = 0.2;
contPane.setScaleX(scale);
contPane.setScaleY(scale);
contPane.getChildren().add(rec);
Button but1 = new Button("+");
but1.setOnAction((ActionEvent event) -> {
scale*=2;
contPane.setScaleX(scale);
contPane.setScaleY(scale);
});
Button but2 = new Button("-");
but2.setOnAction((ActionEvent event) -> {
scale/=2;
contPane.setScaleX(scale);
contPane.setScaleY(scale);
});
HBox buttons = new HBox(but1, but2);
pane.setTop(buttons);
pane.setCenter(sp);
Scene scene = new Scene(pane, 800, 600);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
contPane scaled by using transform don't change its layoutBounds automatically. If you want not to make empty space in contPane, you'd better wrap the node in Group.
See this post. Layout using the transformed bounds
sp.setContent(new Group(contPane));
In addition, if you don't want to make empty space in ScrollPane, limit minimum scale to rate which width or height of the content fits viewport's one.
Button but1 = new Button("+");
but1.setOnAction((ActionEvent event) -> {
updateScale(scale * 2.0d);
});
Button but2 = new Button("-");
but2.setOnAction((ActionEvent event) -> {
updateScale(scale / 2.0d);
});
HBox buttons = new HBox(but1, but2);
pane.setTop(buttons);
pane.setCenter(sp);
Scene scene = new Scene(pane, 800, 600);
primaryStage.setScene(scene);
primaryStage.show();
updateScale(0.2d);
private void updateScale(double newScale) {
scale = Math.max(newScale, Math.max(sp.getViewportBounds().getWidth() / rec.getWidth(), sp.getViewportBounds().getHeight() / rec.getHeight()));
contPane.setScaleX(scale);
contPane.setScaleY(scale);
}
Consider a case of the image is smaller than ScrollPane's viewport. Because for showing no empty space, this code will stretch contents when it doesn't have enough size.
In a case of huge images, TravisF's comment helps you.
I have been trying to learn more on using JavaFX and in this program I am trying to display a 3 by 3 game of tic tac toe that has already been played. I have created my ImageViews and set the images I want to use but once I started plugging them into columns and rows I noticed I cannot use the same one twice. I have an image for an empty space, an X, and an O. Once I use one more than once I get an "Exception while running application". Might be a rookie mistake, but an explanation would be greatly appreciated.
package Fresh;
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.GridPane;
import javafx.stage.Stage;
public class Fresh extends Application {
#Override
public void start(Stage primaryStage) {
//Create a pane and set its properties
GridPane pane = new GridPane();
pane.setAlignment(Pos.CENTER);
pane.setPadding(new Insets(11.5, 12.5, 13.5, 14.5));
pane.setHgap(5.5);
pane.setVgap(5.5);
//imv0 = X image
final ImageView imv0 = new ImageView();
final Image image0 = new Image(Fresh.class.getResourceAsStream("images/x.gif"));
imv0.setImage(image0);
//imv1 = O image
final ImageView imv1 = new ImageView();
final Image image1 = new Image(Fresh.class.getResourceAsStream("images/o.gif"));
imv1.setImage(image1);
//imv2 = empty image
final ImageView imv2 = new ImageView();
final Image image2 = new Image(Fresh.class.getResourceAsStream("images/empty.gif"));
imv2.setImage(image2);
//Place nodes in the pane
pane.add((imv0),0,0);
pane.add((imv1), 1, 0);
//Once I try to use imv0 again "I get an exception while running".
pane.add((imv0),0,1);
//Create a scene and place it in the stage
Scene scene = new Scene(pane);
primaryStage.setTitle("ShowGridPane");
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
You can use the same Image as many times as you like; however, you can only place a given ImageView in one place in the scene graph. From the Javadocs:
A node may occur at most once anywhere in the scene graph.
To see why this must be true, what would you expect
GridPane.getColumnIndex(imv0)
to return, given the code you have?
So you can do:
final Image image0 = new Image(Fresh.class.getResourceAsStream("images/x.gif"));
ImageView imv00 = new ImageView(image0);
ImageView imv01 = new ImageView(image0);
pane.add(imv00, 0, 0);
pane.add(imv01, 0, 1);
// etc
The overhead here is not too bad; you use the same image data for each ImageView.