I am developing an image processing application in JavaFX that involves zooming images using touchpad gestures. To achieve this, I have created a custom implementation of ScrollPane that handles both image scrolling and zooming with familiar gestures. I have included relevant code snippets and the FXML configuration of the scene.
public class ZoomableScrollPane extends ScrollPane {
private double scaleValue = 1;
private double zoomIntensity = 0.02;
private Node target;
private Node zoomNode;
public ZoomableScrollPane() {
super();
}
public void setTarget(Node target) {
this.target = target;
this.zoomNode = new Group(target);
setContent(outerNode(zoomNode));
setPannable(true);
setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
setVbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
setFitToHeight(true);
setFitToWidth(true);
updateScale();
}
private Node outerNode(Node node) {
Node outerNode = centeredNode(node);
outerNode.setOnScroll(e -> {
e.consume();
onScroll(e.getTextDeltaY(), new Point2D(e.getX(), e.getY()));
});
return outerNode;
}
private Node centeredNode(Node node) {
VBox vBox = new VBox(node);
vBox.setAlignment(Pos.CENTER);
return vBox;
}
private void updateScale() {
target.setScaleX(scaleValue);
target.setScaleY(scaleValue);
}
private void onScroll(double wheelDelta, Point2D mousePoint) {
double zoomFactor = Math.exp(wheelDelta * zoomIntensity);
Bounds innerBounds = zoomNode.getLayoutBounds();
Bounds viewportBounds = getViewportBounds();
double valX = this.getHvalue() * (innerBounds.getWidth() - viewportBounds.getWidth());
double valY = this.getVvalue() * (innerBounds.getHeight() - viewportBounds.getHeight());
scaleValue = scaleValue * zoomFactor;
updateScale();
this.layout();
Point2D posInZoomTarget = target.parentToLocal(zoomNode.parentToLocal(mousePoint));
Point2D adjustment = target.getLocalToParentTransform().deltaTransform(posInZoomTarget.multiply(zoomFactor - 1));
Bounds updatedInnerBounds = zoomNode.getBoundsInLocal();
this.setHvalue((valX + adjustment.getX()) / (updatedInnerBounds.getWidth() - viewportBounds.getWidth()));
this.setVvalue((valY + adjustment.getY()) / (updatedInnerBounds.getHeight() - viewportBounds.getHeight()));
}
}
<VBox xmlns:fx="http://javafx.com/fxml/1" prefHeight="400.0" prefWidth="640.0"
xmlns="http://javafx.com/javafx/17.0.2-ea" fx:controller="ru.itmo.grafix.ui.controllers.MainSceneController">
<MenuBar VBox.vgrow="NEVER">
// Some menu stuff
</MenuBar>
<TabPane fx:id="tabPane" tabClosingPolicy="ALL_TABS" tabDragPolicy="REORDER"/>
</VBox>
The ZoomableScrollPane instance is then anchored to the TabPane as follows
private ImageView setImage(WritableImage img) {
ImageView imageView = new ImageView(img);
ZoomableScrollPane scrP = new ZoomableScrollPane();
scrP.setPrefSize(tabPane.getPrefWidth(), tabPane.getPrefHeight());
getActiveTab().setContent(scrP);
scrP.setTarget(imageView);
return imageView;
}
private Tab getActiveTab() {
return tabPane.getSelectionModel().getSelectedItem();
}
The issue I am encountering is related to the display of zoomed images. When opening small images and subsequently zooming in, a portion of the image at the bottom becomes overlapped by an empty white space that persists (screenshots available). The image looks like this: broken display whereas expected to be like that: Normal image display
- I extended the ScrollPane class and implemented event handlers for zooming.
- The images are displayed using JavaFX's ImageView within the ScrollPane.
- The problem occurs specifically when zooming in on small images; larger images do not exhibit this behavior.
- I have experimented with various properties of the ScrollPane, such as fitToWidth and fitToHeight, but have not been able to resolve the issue.
Has anyone else encountered a similar problem or can provide insights into what might be causing this overlapping empty white space issue?
Javafx components automatically adjust themselves according to the size available to them. Try removing the prefHeight and prefWidth, as it forces the components to use a particular dimension.