根据悬停鼠标位置的Javafx图像裁剪

Javafx Image Crop according to hovering mouse position

我正在通过在 X 射线上的解剖点上放置点来开发追踪程序。我的问题是,我试图放大我的 X 射线图像 (Imageview),而我的鼠标悬停在它上面并显示在另一个 Imageview 中。我希望我的鼠标在它周围创建一个虚拟矩形并在移动时保持在中心并将该实时图像传输到其他小 Imageview,到目前为止我已经这样做了但是有一些小故障。我的矩形没有完全跟随我的鼠标,当我移动到角落时故障加速,你有什么建议?我正在分享我的错误和我的代码来澄清所有这些事情。

也许我应该创建一个矩形并将其放置在鼠标位置周围并实时裁剪图像,适合 zoomedImageview,并一直显示,但我被困在了这里。

主要问题是,鼠标位置和zoomedImageview的中心位置必须始终重合。

Xray Imageview Fitwidth-height 为 800x800 zoomedImageview Fitwidth-height 是 250x250

public class DrawingController {

        @FXML 
    ImageView xrayImage;
        
         @FXML
    ImageView zoomedImage;

   public void initialize()
    {

   xrayImage.setOnMouseMoved(e -> {    //HOVERING ON XRAY
   
PixelReader reader = XrayImageview.getImage().getPixelReader();   
WritableImage newImage = new WritableImage(reader,

(int) e.getX(),  //x and y is picked according to hovering mouse position
   (int) e.getY(),
   (int) 250,     // 250x250 rectangle is requested.
   (int) 250);

zoomedImage.setImage(newImage);  // Displaying zoomed image

        });
}}

我的 FXML 文件是:

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

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

<AnchorPane prefHeight="913.0" prefWidth="1238.0" xmlns="http://javafx.com/javafx/16" xmlns:fx="http://javafx.com/fxml/1" fx:controller="main.java.controllers.DrawingController">
   <children>
      <ImageView fitHeight="800.0" fitWidth="800.0" layoutX="418.0" layoutY="33.0" pickOnBounds="true" preserveRatio="true" fx:id="xrayImage">
         <image>
            <Image url="@threatCare.png" />
         </image>
      </ImageView>
      <ImageView fx:id="zoomedImage" fitHeight="250.0" fitWidth="250.0" layoutX="108.0" layoutY="433.0" pickOnBounds="true" preserveRatio="true" />
   </children>
</AnchorPane>

和主要 class :

package main.java;

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.layout.AnchorPane;
import javafx.stage.Stage;
import main.java.database.DatabaseCreator;

import javax.swing.text.html.ImageView;
import java.awt.*;

public class Runner extends Application {
    @Override
    public void start(Stage primaryStage) throws Exception{
     
        Parent root = FXMLLoader.load(getClass().getResource("../resources/DrawingPanel.fxml"));

        primaryStage.setTitle("TreatCare Software Systems");
         Scene scene = new Scene(root,1200,950);
        primaryStage.setScene(scene);
        primaryStage.setMinWidth(1200);
        primaryStage.setMinHeight(950);
        primaryStage.setMaximized(true);
        primaryStage.setOnCloseRequest( e -> SystemClose());
        primaryStage.show();
    }

    private void SystemClose(){
        System.exit(0);         
    }

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

您正在缩放图像,因此需要根据缩放比例转换鼠标坐标。

来自您的 .fxml 文件:

<ImageView fitHeight="800.0" fitWidth="800.0" layoutX="418.0" layoutY="33.0" pickOnBounds="true" preserveRatio="true" fx:id="xrayImage">

缩放图像的那一行中有三个属性:

  • fitWidth="800.0" - 将图像缩放到 800 像素宽
  • fitHeight="800.0" - 将图像缩放到 800 像素高
  • preserveRatio="true" - 覆盖所有缩放比例,因此图像在两个维度上始终具有相同的比例

所以你需要根据这些乘以你的鼠标坐标:

xrayImage.setOnMouseMoved(e -> {    //HOVERING ON XRAY
    Image image = xrayImage.getImage();

    double x = e.getX();
    double y = e.getY();
    double xScale = (xrayImage.getFitWidth() > 0 ?
        image.getWidth() / xrayImage.getFitWidth() : 1);
    double yScale = (xrayImage.getFitHeight() > 0 ?
        image.getHeight() / xrayImage.getFitHeight() : 1);
    if (xrayImage.isPreserveRatio()) {
        double scale = Math.max(xScale, yScale);
        x *= scale;
        y *= scale;
    } else {
        x *= xScale;
        y *= yScale;
    }

    x = Math.max(0, x - 125);
    y = Math.max(0, y - 125);
    double width = Math.min(image.getWidth() - x, 250);
    double height = Math.min(image.getHeight() - y, 250);

    PixelReader reader = image.getPixelReader();   
    WritableImage newImage = new WritableImage(reader,
        (int) x, (int) y, (int) width, (int) height);

    zoomedImage.setImage(newImage);  // Displaying zoomed image
});

我向 DrawingPanel.fxml 添加了一个窗格。 Pane 的目的是显示裁剪区域的边界(标记):

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

<?import javafx.scene.image.Image?>
<?import javafx.scene.image.ImageView?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.Pane?>
<?import javafx.scene.layout.StackPane?>

<AnchorPane prefHeight="913.0" prefWidth="1238.0" xmlns="http://javafx.com/javafx/16" xmlns:fx="http://javafx.com/fxml/1" 
                                                                            fx:controller="DrawingController">
   <children>
      <StackPane layoutX="418.0" layoutY="33.0">
         <children>
            <ImageView  fitHeight="800.0" fitWidth="800.0" preserveRatio="true" pickOnBounds="true" fx:id="xrayImagePane">
               <image>         
                <Image url="https://www.almanac.com/sites/default/files/image_nodes/cosmos-flower.jpg" />
               </image>
            </ImageView>
            <!-- Stack a Pane over the ImageView. Add the marker to this Pane -->
            <Pane fx:id="markerPane" prefHeight="200.0" prefWidth="200.0" />
         </children>
      </StackPane>
      <ImageView fx:id="croppedImageView" fitHeight="250.0" fitWidth="250.0" layoutX="108.0" layoutY="433.0" pickOnBounds="true" preserveRatio="true" />
   </children>
</AnchorPane>

控制器添加和更新标记并更新裁剪图像。我从 那里借用了一些代码来处理比例,但请注意使用 getLayoutBounds() 来计算比例:

import javafx.fxml.FXML;
import javafx.geometry.Bounds;
import javafx.scene.image.*;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;

public class DrawingController {

    @FXML
    ImageView xrayImagePane;
    private Image fullImage;
    private PixelReader reader;

    @FXML
    Pane markerPane;
    private Rectangle marker;

    @FXML
    ImageView croppedImageView;

    private static int ZOOM_WONDOW_SIZE = 250;
    private static Color MARKER_COLOR  = Color.BLUE;

    @FXML
    public void initialize(){

        marker = new Rectangle(0,0, ZOOM_WONDOW_SIZE, ZOOM_WONDOW_SIZE);
        marker.setStroke(MARKER_COLOR);
        marker.setFill(Color.TRANSPARENT);
        marker.setStrokeWidth(3);
        markerPane.getChildren().add(marker);

        fullImage = xrayImagePane.getImage();
        reader = fullImage.getPixelReader();

        markerPane.setOnMouseMoved(e -> {
            updateMarker(e.getX(), e.getY());
            updateZoomImage();
        });
    }

    //update marker location based on mouse location
    private void updateMarker(double x, double y) {
        //use layout bounds because you do not know if the actual size is xrayImagePane.getFitWidth()
        //or fullImage.getWidth()
        Bounds imageViewBounds = xrayImagePane.getLayoutBounds();
        double imageWidth = imageViewBounds.getWidth();
        double newX= Math.max(0, x - ZOOM_WONDOW_SIZE/2 );
        newX =  newX + ZOOM_WONDOW_SIZE >= imageWidth ?  imageWidth - ZOOM_WONDOW_SIZE : newX;

        double imageHeight = imageViewBounds.getHeight();
        double newY = Math.max(0, y - ZOOM_WONDOW_SIZE/2 );
        newY=  newY + ZOOM_WONDOW_SIZE >= imageHeight ?  imageHeight - ZOOM_WONDOW_SIZE : newY;

        marker.setX(newX);
        marker.setY(newY);
    }

    //update zoomed image based on marker and scale
    private void updateZoomImage() {

        //adapted from VGR's answer 
        double x = marker.getX();
        double y = marker.getY();
        double width = marker.getWidth();
        double height= marker.getHeight();
        //use layout bounds because you do not know if the actual size is xrayImagePane.getFitWidth()
        //or fullImage.getWidth()
        Bounds imageViewBounds = xrayImagePane.getLayoutBounds();

        double xScale = xrayImagePane.getFitWidth() > 0 ?
                            fullImage.getWidth() / imageViewBounds.getWidth() : 1;
        double yScale = xrayImagePane.getFitHeight() > 0 ?
                           fullImage.getHeight() / imageViewBounds.getHeight(): 1;
        if (xrayImagePane.isPreserveRatio()) {
            double scale = Math.min(xScale, yScale);
            x *= scale;
            y *= scale;
            width *= scale;
            height *= scale;
        } else {
            x *= xScale;
            y *= yScale;
            width *= xScale;
            height *= yScale;
        }

        WritableImage croppedImage = new WritableImage(reader,
                (int) x,
                (int) y,
                (int) width,
                (int) height
                );
        croppedImageView.setImage(croppedImage); 
    }
}

使用

进行测试
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.*;
import javafx.stage.Stage;

public class Runner extends Application {
    @Override
    public void start(Stage primaryStage) throws Exception{

        Parent root = FXMLLoader.load(getClass().getResource("DrawingPanel.fxml"));

        primaryStage.setTitle("TreatCare Software Systems");
         Scene scene = new Scene(root);
        primaryStage.setScene(scene);

        primaryStage.setOnCloseRequest( e -> SystemClose());
        primaryStage.show();
    }

    private void SystemClose(){
        System.exit(0);
    }

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