I have a group which contains a rectangle and an image on top. I want the rectangle to be re-sizable and the image should have a fixed size except for the case when the rectangle is smaller than the image. Then the image should down-size with the rectangle.
The image should also always be centered and have some padding.
I have most of these parts done, except the down-sizing part of the image. I don't know why, but the image will not down-size at all. This is what I've got.
Group group = new Group()
GeometryNode<Rectangle> rectangle = new GeometryNode<>();
rectangle.setGeometry(new Rectangle(0, 0, 60, 60));
ImageView imageView = new ImageView(image);
imageView.setPreserveRatio(true);
ImageViewPane imagePane = new ImageViewPane(imageView);
imagePane.setMinSize(0, 0);
imagePane.setMaxSize(50, 50);
StackPane stackPane = new StackPane();
stackPane.getChildren().add(rectangle);
stackPane.getChildren().add(imagePane);
group.getChildren().add(stackPane);
You want the fitWidth and fitHeight properties of the ImageView to change if the dimensions of the StackPane change. So you can do
double padding = ... ;
imageView.setPreserveRatio(true);
imageView.fitWidthProperty().bind(
Bindings.min(stackPane.widthProperty().subtract(padding), image.widthProperty()));
imageView.fitHeightProperty().bind(
Bindings.min(stackPane.heightProperty().subtract(padding), image.heightProperty()));
Related
I am trying to draw a texture on a sphere with JavaFX (16). I add the material with the texture but the texture is stretched to the whole surface. It is possible to set the texture on only a portion of the surface? Like the image below (not mine, taken from SO):
My code so far (very trivial):
public void start(Stage stage) throws Exception {
Sphere sphere = new Sphere(200);
PhongMaterial material = new PhongMaterial();
material.setDiffuseMap(new Image(new File("picture.png").toURI().toURL().toExternalForm()));
sphere.setMaterial(material);
Group group = new Group(sphere);
Scene scene = new Scene( new StackPane(group), 640, 480, true, SceneAntialiasing.BALANCED);
scene.setCamera(new PerspectiveCamera());
stage.setScene(scene);
stage.show();
}
The reason why the texture you apply is stretched to the whole sphere is that the texture coordinates that define the Sphere mesh are mapping the whole sphere surface, and therefore, when you apply a diffuse image, it is translated 1-1 to that surface.
You could create a custom mesh, with custom texture coordinates values, but that can be more complex.
Another option is to create the diffuse image "on demand", based on your needs.
For a sphere, a 2D image that can be wrapped around the 3D sphere can be defined by a 2*r*PI x 2*r rectangular container (a JavaFX Pane for our purposes).
Then, you can draw inside your images, scaling and translating them accordingly.
Finally, you need a way to convert that drawing into an image, and for that you can use Scene::snapshot.
Just to play around with this idea, I'll create a rectangular grid that will be wrapped around the sphere, in order to have some kind of a coordinate system.
private Image getTexture(double r) {
double h = 2 * r;
double w = 2 * r * 3.125; // 3.125 is ~ PI, rounded to get perfect squares.
Pane pane = new Pane();
pane.setPrefSize(w, h);
pane.getStyleClass().add("pane-grid");
Group rootAux = new Group(pane);
Scene sceneAux = new Scene(rootAux, rootAux.getBoundsInLocal().getWidth(), rootAux.getBoundsInLocal().getHeight());
sceneAux.getStylesheets().add(getClass().getResource("/style.css").toExternalForm());
SnapshotParameters sp = new SnapshotParameters();
return rootAux.snapshot(sp, null);
}
where style.css has:
.pane-grid {
-fx-background-color: #D3D3D333,
linear-gradient(from 0.5px 0.0px to 50.5px 0.0px, repeat, black 5%, transparent 5%),
linear-gradient(from 0.0px 0.5px to 0.0px 50.5px, repeat, black 5%, transparent 5%);
}
.pane-solid {
-fx-background-color: black;
}
(based on this answer)
With a radius of 400, you get this image:
each square is 50x50, and there are 50x16 squares.
If you apply this diffuse map to an Sphere:
#Override
public void start(Stage stage) {
PhongMaterial earthMaterial = new PhongMaterial();
earthMaterial.setDiffuseMap(getTexture(400));
final Sphere earth = new Sphere(400);
earth.setMaterial(earthMaterial);
Scene scene = new Scene(root, 800, 600, true);
scene.setFill(Color.WHITESMOKE);
stage.setScene(scene);
stage.show();
}
you get:
In theory, now you could fill any of the grid squares, like:
private Image getTexture(double r) throws IOException {
double h = 2 * r;
double w = 2 * r * 3.125;
Pane pane = new Pane();
pane.setPrefSize(w, h);
pane.getStyleClass().add("pane-grid");
Rectangle rectangle = new Rectangle(50, 50, Color.RED);
rectangle.setStroke(Color.WHITE);
rectangle.setStrokeWidth(2);
// fill rectangle at 20 x 10
rectangle.setTranslateX(20 * 50 + 1);
rectangle.setTranslateY(10 * 50 + 1);
Group rootAux = new Group(pane, rectangle);
...
with the result:
Now that you have a well positioned image (for now just a red rectangle), you can get rid of the grid, and simply use a black color for the texture image:
private Image getTexture(double r) throws IOException {
double h = 2 * r;
double w = 2 * r * 3.125;
Pane pane = new Pane();
pane.setPrefSize(w, h);
// pane.getStyleClass().add("pane-grid");
pane.getStyleClass().add("pane-solid");
resulting in:
Now it is up to you to apply this idea to your needs. Note that you can use an ImageView with size 50x50, or 100x100, ... instead of the red rectangle, so you can use a more complex image.
I have a method that rotates an image and uses the drawImage method to display it onto a canvas. However, when rotating the image, the image shrinks and grows because the width and height change (say you rotate a square, the width and height of the image changes). Here is the method:
public void rotateImage(GraphicsContext gc, double speed) {
erase(gc); // erases the previous image
imgView.setRotate(imgView.getRotate() + speed);
SnapshotParameters params = new SnapshotParameters();
params.setFill(Color.TRANSPARENT);
image = imgView.snapshot(params, null);
gc.drawImage(image, pos.x, pos.y, width, height);
}
Any help would be appreciated, and I can post the rest of the code if needed.
A snapshot with the provided parameters uses the dimensions of the node in the parent to determine the size of the image. Rotating an image yields dimensions different to those of the original image in most cases. In those cases the snapshot is bigger than the original image. (Consider a square image rotated by 45°; The width and height of the rotated image is the size of the diagonal of the original image, i.e. larger by a factor of sqrt(2) = 1.41...).
Since drawImage scales the drawn image to fit into a rectangle of size width x height, the snapshot that is larger than this size is scaled down.
Use the transforms of the GraphicsContext instead to avoid creating a new Image instance with each call of the method and avoid scaling the image.
Example
#Override
public void start(Stage primaryStage) {
Image image = new Image("https://upload.wikimedia.org/wikipedia/commons/thumb/8/85/Smiley.svg/240px-Smiley.svg.png");
Canvas canvas = new Canvas(500, 500);
GraphicsContext context = canvas.getGraphicsContext2D();
Slider slider = new Slider(0, 360, 0);
Button btn = new Button("draw");
VBox root = new VBox(canvas, slider, btn);
btn.setOnAction(evt -> {
context.setFill(Color.TRANSPARENT);
context.fillRect(0, 0, canvas.getWidth(), canvas.getHeight());
double posX = 200;
double posY = 150;
context.save();
// apply transformation that puts makes (posX, posY) the point
// where (0,0) is drawn and rotate
context.translate(posX, posY);
context.rotate(slider.getValue());
// draw with center at (0, 0)
context.drawImage(image, -image.getWidth()/2, -image.getHeight()/2);
// undo transformations
context.restore();
});
primaryStage.setScene(new Scene(root));
primaryStage.show();
}
I am new to the libgdx framework trying to make a game but the problem here is I don't know how to scale the ImageButton for different screen sizes it looks big on desktop but looking small on the android devices. I am using several ImageButtons of different sizes and using table to organize them. Can anyone please make some code changes so I could understand how to scale the images here with the viewports?
public class BabyPhone extends Game {
public static final int WIDTH = 480;
public static final int HEIGHT = 800;
public void create () {
bg = new BabyActor();
bg.setTexture(new Texture("background-orange.png"));
bg.setSize(Gdx.graphics.getWidth(),Gdx.graphics.getHeight());
phone = new BabyActor();
phone.setTexture(new Texture("blue-phone.png"));
phone.setSize(Gdx.graphics.getWidth(),Gdx.graphics.getHeight());
TextureRegion btLeft = new TextureRegion(new Texture("10OFF.png"));
Drawable drawableLeft = new TextureRegionDrawable(new TextureRegion(btLeft));
buttonLeft = new ImageButton(drawableLeft);
TextureRegion btRight = new TextureRegion(new Texture("12OFF.png"));
Drawable drawableRight = new TextureRegionDrawable(new TextureRegion(btRight));
buttonRight = new ImageButton(drawableRight);
TextureRegion btCenter = new TextureRegion(new Texture("11OFF.png"));
Drawable drawableCenter = new TextureRegionDrawable(new TextureRegion(btCenter));
buttonCenter = new ImageButton(drawableCenter);
TextureRegion bt1 = new TextureRegion(new Texture("1OFF.png"));
Drawable drawable = new TextureRegionDrawable(new TextureRegion(bt1));
button1 = new ImageButton(drawable);
TextureRegion bt2 = new TextureRegion(new Texture("2OFF.png"));
Drawable drawable1 = new TextureRegionDrawable(new TextureRegion(bt2));
button2 = new ImageButton(drawable1);
TextureRegion bt3 = new TextureRegion(new Texture("3OFF.png"));
Drawable drawable2 = new TextureRegionDrawable(new TextureRegion(bt3));
button3 = new ImageButton(drawable2);
TextureRegion bt4 = new TextureRegion(new Texture("4OFF.png"));
Drawable drawable3 = new TextureRegionDrawable(new TextureRegion(bt4));
button4 = new ImageButton(drawable3);
TextureRegion bt5 = new TextureRegion(new Texture("5OFF.png"));
Drawable drawable4 = new TextureRegionDrawable(new TextureRegion(bt5));
button5 = new ImageButton(drawable4);
TextureRegion bt6 = new TextureRegion(new Texture("6OFF.png"));
Drawable drawable5 = new TextureRegionDrawable(new TextureRegion(bt6));
button6 = new ImageButton(drawable5);
TextureRegion bt7 = new TextureRegion(new Texture("7OFF.png"));
Drawable drawable6 = new TextureRegionDrawable(new TextureRegion(bt7));
button7 = new ImageButton(drawable6);
TextureRegion bt8 = new TextureRegion(new Texture("8OFF.png"));
Drawable drawable7 = new TextureRegionDrawable(new TextureRegion(bt8));
button8 = new ImageButton(drawable7);
TextureRegion bt9 = new TextureRegion(new Texture("9OFF.png"));
Drawable drawable8 = new TextureRegionDrawable(new TextureRegion(bt9));
button9 = new ImageButton(drawable8);
gamecam = new OrthographicCamera(WIDTH,HEIGHT);
gamecam.position.set(WIDTH/2,HEIGHT/2,0);
gamePort = new ScreenViewport(gamecam);
backStage = new Stage(gamePort);
fitPort = new FitViewport(gamecam.viewportWidth,gamecam.viewportHeight,gamecam);
frontStage = new Stage(fitPort);
backStage.addActor(bg);
frontStage.addActor(phone);
Table table = new Table();
table.padLeft(40);
table.setPosition(phone.getWidth()/2,phone.getHeight()/2*0.6f);
table.row().size(95,95);
table.add(buttonLeft);
table.add(buttonCenter);
table.add(buttonRight);
table.row().size(95,95);
table.add(button1);
table.add(button2);
table.add(button3);
table.row().size(95,95);
table.add(button4);
table.add(button5);
table.add(button6);
table.row().size(95,95);
table.add(button7);
table.add(button8);
table.add(button9);
frontStage.addActor(table);
}
public void render(){
backStage.act(Gdx.graphics.getDeltaTime());
frontStage.act(Gdx.graphics.getDeltaTime());
backStage.draw();
frontStage.draw();
}
#Override
public void resize(int width, int height) {
gamePort.update(width, height);
frontStage.getViewport().update(width, height);
}
I'm just setting it as a percentage of a device size. It's loks like this.
float BUTTON_SIZE = 0.1f;
table.add(button2).size(Gdx.graphics.getWidth()*BUTTON_SIZE,Gdx.graphics.getWidth()*BUTTON_SIZE);
If you want to use a Viewport you must choose which Viewport you will use.
ScreenViewport
The ScreenViewport will always fill the whole Screen and will stretch your Textures etc. to the Fullscreen.
FitViewport
The FitViewport will not stretch your Textures it will look that the ratio between width and height will always be the same. This Viewport will produce Sidebars on the sides if the Screen doesn't match the defined ratio. So the shorter side will match the screen.
FillViewport
The FillViewport will not stretch your Textures it will look that the ratio between width and height will always be the same. This Viewport does not create Sidebars like FitViewport because it will match the longer side to the screen. So it can happen that something is cut off.
More Viewports: https://github.com/libgdx/libgdx/wiki/Viewports
In my Example, I will use FitViewport.
To use a FitViewport we must create a Camera. We create an OrthographicCamera. With this Camera, we can define the Virtual width and height of our Viewport so the width and height of what we can see.
Camera camera = new OrthographicCamera(100,100);
Now it's important that you think in this virtual size we defined and not in pixels. So we have a size of 100 x 100 units.
Now let's create our FitViewport with our Camera:
FitViewport viewport = new FitViewport(camera.viewportWidth, camera.viewportHeight, camera);
And finally create our Stage with the Viewport:
Stage stage = new Stage(viewport);
So if we now add our buttons to the table we can define the Size of the Buttons in our Virtual size.
So if we add our Button like this:
table.add(button1).size(10,10);
The Button will always be 10% of the Screen because the Button is 10 units big in a world of 100 units
The advantage now of FitViewport is: we defined the Button to be 10x10 so a Ratio of 1:1, a square. If we now resize the Screen or have a different Screen size the Button will always be a square and will always fill 10 of 100 units of the Screen.
This will maybe be a little bit confusing with Virtual size. If you will read more about it, here is a little bit better description: How Camera works in Libgdx and together with Viewport
One more thing we must do before the Viewport works perfectly.
We must update the Viewport in the resize method:
#Override
public void resize(int width, int height) {
viewport.update(width, height);
}
I hope this will help you to work with Viewports.
You have written that you are new to LibGdx so I will give you some additional suggestions.
This won't work:
Gdx.input.setInputProcessor(stage);
Gdx.input.setInputProcessor(stage1);
Gdx.input can only hold one InputProcessor per time so stage1 will override stage. If you will use more than one InputProcessor use InputMultiplexer: https://github.com/libgdx/libgdx/wiki/Event-handling
If you use a Table and every actor in a row have for example the same size. Instead of writing by every actor: table.add(button1).size(10,10);
Table has a special feature, if you set something to row() this will apply to all Actors in this row.
So instead of writing this:
table.add(button1).size(10,10);
table.add(button2).size(10,10);
table.add(button3).size(10,10);
table.row();
table.add(button4).size(10,10);
table.add(button5).size(10,10);
table.add(button6).size(10,10);
You can write this:
table.row().size(10,10);
table.add(button1);
table.add(button2);
table.add(button3);
table.row().size(10,10);
table.add(button4);
table.add(button5);
table.add(button6);
I hope these little suggestions will help you.
I have a scroll pane in java with content as an anchorpane , the anchorpane has children which are an imageview binded with the anchorpanes height and width so it can fit the anchorpane exactly and also some circles and lines representing a graph. If i enlarge the height or width of the anchorpane then the scroll pane t becomes automatically scrollable giving me the ability to view the image as if it was zoomed , but what im trying to do is zoom in on a specified area on the pic and also , to make the circles and lines change position based on the zoom (maybe using binding properties). The whole idea im trying to implement is have a picture as a map and a graph representing links between cities , but at the same time i was to zoom in on a certain path and have it take the whole pane and navigating between the scroll pane after zooming in.
AnchorPane other;
ImageView image;
ScrollPane scroll;
Button bp;
Button bm;
public void initialize(URL arg0, ResourceBundle arg1) {
System.out.println("OK");
Circle c = new Circle(250, 250, 10);
Circle c2 = new Circle(20, 20, 10);
Circle c3 = new Circle(40, 200, 10);
c3.setFill(Color.RED);
c3.setVisible(true);
c2.setFill(Color.RED);
Line line = new Line();
Line line2 = new Line();
Line line3 = new Line();
line.setStartX(c.getCenterX());
line.setStartY(c.getCenterY());
line.setEndX(c2.getCenterX());
line.setEndY(c2.getCenterY());
line2.setStartX(c.getCenterX());
line2.setStartY(c.getCenterY());
line2.setEndX(c3.getCenterX());
line2.setEndY(c3.getCenterY());
line2.setVisible(true);
line2.setStroke(Color.BLUE);
line3.setStartX(c2.getCenterX());
line3.setStartY(c2.getCenterY());
line3.setEndX(c3.getCenterX());
line3.setEndY(c3.getCenterY());
line3.setVisible(true);
line3.setStroke(Color.BLUE);
line.setVisible(true);
line.setStroke(Color.BLUE);
c.setFill(Color.RED);
c2.setVisible(true);
c.setVisible(true);
Rectangle rect = new Rectangle(100, 100, 70, 70);
other.getChildren().addAll(line, line2, line3, c, c2, c3, rect);
System.out.println(other.getChildren());
image.setVisible(true);
image.fitHeightProperty().bind(other.prefHeightProperty());
image.fitWidthProperty().bind(other.prefWidthProperty());
scroll.setPannable(true);
}
public void bpAction() {
other.setPrefHeight(other.getPrefHeight() + 30);
other.setPrefWidth(other.getPrefWidth() + 30);
}
public void bmAction() {
other.setPrefHeight(other.getPrefHeight() - 30);
other.setPrefWidth(other.getPrefWidth() - 30);
}
i am trying to code a HUD for my game but i cannot figure out how to scale a Label properly I am using this code:
Label hpLabel = new Label("HP: ",new Label.LabelStyle(new BitmapFont(),Color.BROWN));
table.add(hpLabel);
viewport = new FitViewport(Gdx.graphics.getWidth()/ FacultyWars.PPM, Gdx.graphics.getHeight()/ FacultyWars.PPM, cam);
stage = new Stage(viewport, batch);
stage.addActor(table);
//other ellements added after this
The HP tag is enormous on the screen. I tried using setScale on the label and on the table to no avail. Any help is appreciated!
Thanks
Here is a picture of the current screen https://gyazo.com/57c190a9d7516bb8b2256bf1a7d17b4c
Simply add the Label to a Group then apply the scale to the group.
Label label = new Label("hello", skin);
Group group = new Group();
group.addActor(label);
group.setScale(2f, 2f);
I only scale Groups with Labels in animations where at the end of the animation the scale is 1. One should not scale fonts, because it looks pixelated. For example, a simple scale up scale down animation:
group.addAction(Actions.sequence(Actions.scaleTo(2f, 2f, 1f), Actions.scaleTo(1f, 1f, 1f)));
Use setFontScale(float fontScale) method of Label