the image does not shrink when the window/stage is shrunk
Yes, that's by design. Images and ImageViews have a fixed size. If you want to dynamically resize ImageViews then there are a few options as outlined in this answer:
How could one fix the example program?
Use one of the techniques outlined in the resizable image post above.
Is the window/stage simply not meant meant to be shrunk after startup?
Depends on how you write your code. You can write in such a way that it is not meant to be shrunk, or you can write it so that the layout and content adjusts appropriately as the stage size changes. How you accomplish this is up to you as the developer. JavaFX defines numerous methods for resizable layouts, some of which are discussed in:
The standard way to accomplish resizable layouts would be to use appropriate layout panes.
even the position of the stackpane seems to be offset and not properly aligned with the split pane
I didn't really analyze this issue with your code in detail. I think it is probably caused by a couple of things:
- You are binding the fitWidth/Height of your ImageView to the width/height of the StackPane containing the ImageView. This probably causes the default layout code to get a bit confused, because part of the determinant of the width/height of the StackPane is the preferred size of the children, but by varying fitWidth/Height you are changing the preferred size of the ImageView, so this makes it hard to reason about what the width and height of the StackPane should be.
- When there is not enough room to display the StackPane's content, the StackPane can be larger than the available area (this is explained a bit in the StackPane javadoc). So, when you make the scene bigger, the fitWidth/Height is changing to get bigger with the available area for the StackPane, but when resize to get smaller the StackPane never gets smaller because it needs to be at least as large to fit the enlarged image view that was calculated before.
Anyway, moral of the story is: don't bind the width/height of a child to the width/height of a parent container. Sometimes you can get this approach to work and sometimes it will have weird side-effects such as you observe in your example code.
Instead, you can override the layout pass logic in a parent node and perform the appropriate layout calculations there. An example of this approach is provided in the sample below. Unfortunately, there is zero official documentation by Oracle (or almost anywhere else on the net) on techniques for overriding the layout pass logic of parent nodes. This is kind of made up by the fact that, most of the time, you don't need to write custom layout logic, but can instead use the standard layout panes and controls to get the layout you need (though unfortunately, you can't in this case and must use either CSS background settings or custom layout code).
Smooth scaling using -fx-shape
Scaling a bitmap image can result in some pixellation and an image that is not very smooth. In your case the input bitmap image is very large at 512x512, so the smooth scaling is not really an issue for the large image as it would be if you only had, for instance, a smaller 32x32 image and tried to scale that up to 512x512.
As you have a simple black and white image, you can convert the image to an svg, extract the path out of the svg and use the region property -fx-shape
. This is how the default JavaFX stylesheet defines shapes such as checks for checkboxes in a way that they will smoothly scale across sizes. See the file modena.css
inside jfxrt.jar
in your JRE implementation for samples of definitions and usage of -fx-shape
.
Something like this will do that for your image (CSS style can be extracted to a CSS file for easier maintenance):
Pane tile = new Pane();
tile.setStyle("" +
"-fx-background-color: forestgreen;" +
"-fx-shape: \"M2470 4886 c-83 -22 -144 -60 -220 -136 -41 -41 -91 -102 -111 -135\n" +
"-121 -200 -2027 -3510 -2046 -3554 -55 -125 -68 -272 -33 -377 56 -162 205\n" +
"-260 431 -284 127 -13 4011 -13 4138 0 226 24 375 122 431 284 35 105 22 252\n" +
"-33 377 -32 72 -1994 3472 -2064 3577 -135 202 -320 295 -493 248z m223 -1182\n" +
"c122 -43 210 -155 237 -301 12 -69 3 -156 -45 -428 -34 -196 -101 -636 -120\n" +
"-785 -8 -69 -18 -144 -21 -168 l-5 -42 -177 2 -177 3 -8 75 c-20 203 -66 500\n" +
"-158 1030 -38 218 -40 240 -29 305 15 94 47 161 107 221 58 58 126 94 197 104\n" +
"61 9 147 2 199 -16z m30 -1989 c64 -32 144 -111 175 -172 49 -96 49 -231 0\n" +
"-332 -29 -59 -102 -133 -165 -164 -275 -140 -595 91 -543 391 14 80 39 130 93\n" +
"189 86 94 160 124 293 120 76 -2 99 -7 147 -32z\";"
);
tile.setScaleY(-1);
tile.setPrefSize(64, 64);
I don't know how to make the pane only show a proportional size if the pane is resized. So I doubt that helps you much.
This would be simpler if JavaFX directly supported SVG images, but it does not.
Some layout creation and debugging device
- For creation of layouts, try using Gluon SceneBuilder. Even if you don't use FXML, you can learn a lot easily by dynamically playing around with the UI elements and properties in SceneBuilder and observing the resultant differences such changes make in a generated FXML file.
- To dynamically debug the layout of an existing application at runtime, use FXExperience ScenicView.
Sample Solution
Sample output at varying stage sizes:


This uses the ImageViewPane that was defined in the answer to the linked question on resizing images in JavaFX.
ResizableImageSample.java
import javafx.application.Application;
import javafx.geometry.*;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.image.*;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
public class ResizableImageSample extends Application {
@Override
public void start(Stage stage) {
// Top section with image
ImageView imageView = new ImageView(new Image(
"http://wfarm2.dataknet.com/static/resources/icons/set114/b4f29385.png"
));
imageView.setPreserveRatio(true);
ImageViewPane imagePane = new ImageViewPane(imageView);
imagePane.getStyleClass().add("stack-pane");
// Bottom Section with CSS toggle
ToggleButton toggle = new ToggleButton("Show CSS");
HBox hbox = new HBox(toggle);
hbox.setAlignment(Pos.CENTER);
hbox.getStyleClass().add("h-box");
// Combines the two sections
SplitPane root = new SplitPane(imagePane, hbox);
root.setDividerPositions(0.8);
root.setOrientation(Orientation.VERTICAL);
SplitPane.setResizableWithParent(hbox, false);
Scene scene = new Scene(root);
toggle.setOnMouseClicked(e -> {
if (toggle.isSelected()) {
scene.getStylesheets().add(getClass().getResource("StyleSheet.css").toExternalForm());
toggle.setText("Disable CSS");
} else {
scene.getStylesheets().remove(0);
toggle.setText("Show CSS");
}
});
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
ImageViewPane.java
/*
* Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*/
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.geometry.HPos;
import javafx.geometry.VPos;
import javafx.scene.image.ImageView;
import javafx.scene.layout.Region;
/**
*
* @author akouznet
*/
class ImageViewPane extends Region {
private ObjectProperty<ImageView> imageViewProperty = new SimpleObjectProperty<>();
public ObjectProperty<ImageView> imageViewProperty() {
return imageViewProperty;
}
public ImageView getImageView() {
return imageViewProperty.get();
}
public void setImageView(ImageView imageView) {
this.imageViewProperty.set(imageView);
}
public ImageViewPane() {
this(new ImageView());
}
@Override
protected void layoutChildren() {
ImageView imageView = imageViewProperty.get();
if (imageView != null) {
if (imageView.isPreserveRatio()) {
if (getHeight() > getWidth()) {
imageView.setFitWidth(getWidth());
imageView.setFitHeight(0);
} else {
imageView.setFitWidth(0);
imageView.setFitHeight(getHeight());
}
} else {
imageView.setFitWidth(getWidth());
imageView.setFitHeight(getHeight());
}
layoutInArea(imageView, 0, 0, getWidth(), getHeight(), 0, HPos.CENTER, VPos.CENTER);
}
super.layoutChildren();
}
public ImageViewPane(ImageView imageView) {
imageViewProperty.addListener((observable, oldIV, newIV) -> {
if (oldIV != null) {
getChildren().remove(oldIV);
}
if (newIV != null) {
getChildren().add(newIV);
}
});
this.imageViewProperty.set(imageView);
}
}