JavaFx zooming and panning an image with overlaid fixed scale labels

863 Views Asked by At

I am a doctor at a University and I am developing X-ray analysis software in which a user defines anatomical points with small circles by placing them on an X-ray image. I'm satisfied so far, but I need to implement zooming and panning using a mouse wheel, according to the mouse position.

I simplified my code to be clear, the original one is way complicated than that.

In this algorithm, points should NOT move according to the image while zooming and panning so their position is maintained and the Label near the point has to stay in the same font size while zooming (we don't want to see gigantic Label). I am stuck at this point.

Please help can you just generate this algorithm?

Here is the sample:

image

Here is my java code:

package main.java;

import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.image.ImageView;
import javafx.scene.input.MouseButton;
import javafx.scene.layout.AnchorPane;
import javafx.scene.shape.Circle;    

public class ZoomClass {

    @FXML
    AnchorPane xRayAnchorPane;

    @FXML
    ImageView xRayImage;

    @FXML
    ScrollPane xRayScrollPane;

    @FXML
    Label pointsLabel;    

    public void initialize() {    
        xRayImage.setOnMouseClicked(mouseEvent ->
        {
            if(mouseEvent.getButton().equals(MouseButton.PRIMARY)) // ADD POINTS
            {
                Circle circle = new Circle();
                circle.setCenterX(mouseEvent.getX()+58);   // Some Offset values to proper positioning
                circle.setCenterY(mouseEvent.getY()+75);   // Some Offset values to proper positioning
                circle.setRadius(2);

                xRayAnchorPane.getChildren().add(circle);  // Adding circle to mainFrame

                pointsLabel.setText("A");     // Defining the point's name
                pointsLabel.setLayoutX(circle.getCenterX()+5);  // Some Offset values to proper positioning
                pointsLabel.setLayoutY(circle.getCenterY());  // Some Offset values to proper positioning

            }
        });
    }
}

And my FXML file:

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.Label?>
<?import javafx.scene.control.ScrollPane?>
<?import javafx.scene.image.Image?>
<?import javafx.scene.image.ImageView?>
<?import javafx.scene.layout.AnchorPane?>

<AnchorPane prefHeight="650.0" prefWidth="851.0" xmlns="http://javafx.com/javafx/16" xmlns:fx="http://javafx.com/fxml/1" fx:controller="main.java.ZoomClass" fx:id="xRayAnchorPane">
   <children>
      <ScrollPane layoutX="56.0" layoutY="71.0" prefHeight="533.0" prefWidth="743.0" fx:id="xRayScrollPane">
         <content>
            <ImageView fitHeight="736.0" fitWidth="739.0" pickOnBounds="true" preserveRatio="true" fx:id="xRayImage">
               <image>
                  <Image url="@ceph2.jpg" />
               </image>
            </ImageView>
         </content>
      </ScrollPane>
      <Label fx:id="pointsLabel" layoutX="14.0" layoutY="138.0" />
   </children>
</AnchorPane>
2

There are 2 best solutions below

0
AudioBubble On

According to this question, you need to create a group element for the elements you want to zoom in. When you create new circles, you should add to zoom group. Finally, apply zoom on the group element you created

2
swpalmer On

Does this help?

The scale factor is set only on the ImageView by using the mouse wheel with CTRL held down. The labels are placed in the AnchorPane outside of the ScrollPane, so they are not effected by the pan and zoom as you require.

package com.stackoverflow.zoom;

import javafx.application.Application;
import javafx.geometry.Point2D;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.ScrollEvent;
import javafx.scene.input.ZoomEvent;
import javafx.scene.layout.AnchorPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;

public class Zoomer extends Application {

    private AnchorPane ap;
    private ImageView imgView;

    private char markTxt = 'A';

    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage primaryStage) throws Exception {
        ap = new AnchorPane();
        Image img = new Image("https://upload.wikimedia.org/wikipedia/commons/d/dc/Medical_X-Ray_imaging_SEQ07_nevit.jpg");
        imgView = new ImageView(img);

        ScrollPane sp = new ScrollPane(imgView);
        AnchorPane.setTopAnchor(sp, 0.0);
        AnchorPane.setLeftAnchor(sp, 0.0);
        AnchorPane.setBottomAnchor(sp, 0.0);
        AnchorPane.setRightAnchor(sp, 0.0);

        ap.getChildren().add(sp);

        imgView.setOnMouseClicked(this::onClick);
        imgView.setOnScroll(this::imageScrolled);
        Scene scene = new Scene(ap);
        primaryStage.setTitle("Zoom Image");
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    public void onClick(MouseEvent event) {
        if (event.getButton() == MouseButton.PRIMARY) {
            placeMarker(event.getSceneX(), event.getSceneY());
        }
    }

    private void imageScrolled(ScrollEvent event) {
        // When holding CTRL mouse wheel will be used for zooming
        if (event.isControlDown()) {
            double delta = event.getDeltaY();
            double adjust = delta / 1000.0;
            double zoom = Math.min(10, Math.max(0.1, imgView.getScaleX() + adjust));
            setImageZoom(zoom);
            event.consume();
        }
    }

    private void placeMarker(double sceneX, double sceneY) {
        Circle circle = new Circle(2);
        circle.setStroke(Color.RED);
        circle.setTranslateY(-12);
        Label marker = new Label(String.valueOf(markTxt), circle);
        marker.setTextFill(Color.RED);
        markTxt++;
        Point2D p = ap.sceneToLocal(sceneX, sceneY);
        AnchorPane.setTopAnchor(marker, p.getY());
        AnchorPane.setLeftAnchor(marker, p.getX());
        ap.getChildren().add(marker);
    }

    private void setImageZoom(double factor) {
        imgView.setScaleX(factor);
        imgView.setScaleY(factor);
    }

}