I want to apply a texture to a custom shape. I thought that this would work:
.myTextureShape {
-fx-shape: "M0 0 L0 50 L25 25 L50 50 L50 0 Z";
-fx-background-image: url("resources/texture_bg.png");
-fx-background-repeat: repeat;
}
But it does not! I get the correct shape but without the texture. All my other regions are correctly texturized so I know that the syntaxe is correct. I guess it's because they are rectangular by default.
If I use the following style instead:
.myTextureShape {
-fx-shape: "M0 0 L0 50 L25 25 L50 50 L50 0 Z";
-fx-background-color: red;
}
Then I get a correct red background.
If both attributes are incompatible, how can I apply a texture to a custom SVG-pathed-shape?
P.S: My texture PNG file, is a small 10*10 raster that I repeat to fill my regions with.
As far as I am concerned -fx-shape
and -fx-background-image
don't work together.
JavaFX CSS offers a lot of styling options ... however there are some limitations and some things are only possible with programming. JavaFX CSS uses impl_setShape()
to coarse the Region class to the defined shape (which is market as deprecated in the api but you can still try it out in javafx 2.2 - which I am using at the moment). Adding the background image ignores the shape and fills the whole pane/region, the same happens if you use addChildren to add ImageView objects into it. So - the css string will not translate into the result you would like.
So I can think of at least 2 ways of applying a pattern to a custom SVGPath shape. The one I would use is done with ImagePattern. First build the shape like this
SVGPath shape = new SVGPath();
shape.setContent("M0 0 L0 50 L25 25 L50 50 L50 0 Z");
or using shape builder
SVGPath shape = SVGPathBuilder.create()
.content("M0 0 L0 50 L25 25 L50 50 L50 0 Z")
.build();
and then set the image pattern as fill
Image img = new Image("myjavafxapp/resources/texture_bg.png");
shape.setFill(new ImagePattern(img,0,0,10,10,false));
An alternative would use clipping (adding a custom shape clip to a pane with -fx-background-image set in the css style), which would look somewhat like this
Pane test = new Pane();
test.setStyle(" -fx-background-image: url(\"myjavafxapp/res/index.jpeg\");");
SVGPath shape = new SVGPath();
shape.setContent("M0 0 L0 50 L25 25 L50 50 L50 0 Z");
test.setClip(shape);
One of these should do the trick - you get your shape with the textured background. This is the equivalent to what you would expect from a Region or Pane class with style
.myTextureShape {
-fx-shape: "M0 0 L0 50 L25 25 L50 50 L50 0 Z";
-fx-scale-shape: false;
-fx-background-image: url("myjavafxapp/resources/texture_bg.png");
-fx-background-repeat: repeat;
}
So the SVGPath shape is like all Shape classes nonresizable, in contrast to Region/Pane, this is why I set here the -fx-scale-shape
attribute to false. The way you had it was with the default setting - which is setting it to true - which makes the shape fill out the whole parent object ... from which I guess that this would be also your desired result. ... there are now again multiple ways of making the shape scale to the parent object.
I got it to work using Group to embed the shape and with a method translating the group (the image size needs to be adjusted in the ImagePattern, so that the texture doesn't scale with the shape). I'm adding a tiny working app, which I used to see on the above example if I remember everything correctly. (if you would like the shape to scale proportionally, you use the same scale factor instead of scalex and sclaey in the addScale method)
package myjavafxapp;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.scene.shape.SVGPath;
import javafx.scene.shape.SVGPathBuilder;
import javafx.scene.image.Image;
import javafx.scene.paint.*;
import javafx.scene.transform.Scale;
import javafx.scene.transform.Translate;
import javafx.stage.Stage;
/**
* @author martint
*/
public class MyJavaFXApp extends Application {
private final static String svg = "M0 0 L0 50 L25 25 L50 50 L50 0 Z";
private SVGPath shape;
private Image img;
private Pane resizePane;
private Group shapeGroup;
private Node content;
@Override
public void start(final Stage primaryStage) {
//build the SVGPath shape
shape = SVGPathBuilder.create().content(svg).build();
img = new Image("myjavafxapp/res/index.jpeg");
resizePane = new Pane();
shapeGroup = new Group();
resizePane.getChildren().add(shapeGroup);
StackPane root = new StackPane();
root.getChildren().add(resizePane);
Scene scene = new Scene(root, 200, 200);
primaryStage.setScene(scene);
primaryStage.show();
//fill node content
content = nodeCont();
shapeGroup.getChildren().add(content);
//resizing listening
resizePane.widthProperty().addListener(new ChangeListener<Number>(){
@Override
public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) {
addScaling();
}});
resizePane.heightProperty().addListener(new ChangeListener<Number>(){
@Override
public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) {
addScaling();
}});
Platform.runLater(new Runnable() {
@Override
public void run() {
addScaling();
}
});
}
private Node nodeCont() {
Group cont = new Group();
cont.getChildren().add(shape);
return cont;
}
private void addScaling() {
shapeGroup.getTransforms().clear();
//shape boundary
double cx = content.getBoundsInParent().getWidth();
double cy = content.getBoundsInParent().getHeight();
//resizePane boundary
double px = resizePane.getWidth();
double py = resizePane.getHeight();
//if pane set
if (px > 0.0 && py > 0.0) {
//scale
double scalex = px/cx;
double scaley = py/cy;
//center
double centerx = 0.5 * (px - cx*scalex);
double centery = 0.5 * (py - cy*scaley);
//transform
shapeGroup.getTransforms().add(new Translate(centerx, centery));
shapeGroup.getTransforms().add(new Scale(scalex, scaley));
shape.setFill(new ImagePattern(img,0,0,10/scalex,10/scaley,false));
}
}
public static void main(String[] args) {
launch(args);
}
}
The inablity to apply a background texture to a custom shape via css was a platform bug with JavaFX 2.2 (Java 7).
In Java 8, the platform bug is fixed.
Sample css:
/**
* file textured-shape.css
* place in same directory as TexturedShape.java and ensure build system copies
* this file to the build output directory.
*/
.textured-shape {
/** (a square with the bottom triangle chunk taken out of it) */
-fx-shape: "M0 0 L0 50 L25 25 L50 50 L50 0 Z";
-fx-background-image: url('http://icons.iconarchive.com/icons/tooschee/misc/128/Present-icon.png');
/**
* Image license:
* CC Attribution-Noncommercial 3.0 http://creativecommons.org/licenses/by-nc/3.0/
* Buy commercial license here: http://tooschee.com/portfolio?worksCategory=icons
*/
}
Sample App:
import static javafx.application.Application.launch;
import javafx.application.*;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.stage.Stage;
public class TexturedShape extends Application {
@Override public void start(Stage stage) {
Pane pane = new Pane();
pane.setPrefSize(128, 128);
pane.getStyleClass().add("textured-shape");
Scene scene = new Scene(pane);
scene.getStylesheets().add(
getClass().getResource("textured-shape.css").toExternalForm()
);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) { launch(args); }
}
Running the sample code on Java 7, the shape is not applied to the background texture:
Running the same sample code on Java 8b80, the shape is applied to the background texture:
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With