JavaFX高分辨率副屏截图位置错误问题排查
高分辨率显示器截图位置偏移问题修复
问题描述
使用JavaFX实现的截图工具,在高分辨率第二显示器上截图时,捕获的区域位置不正确。工具逻辑为:创建与屏幕尺寸匹配的透明窗口,用户通过拖拽选择截图区域。
问题原因
JavaFX使用逻辑坐标(受屏幕DPI缩放影响),而JavaFX的Robot.getScreenCapture()方法接收的是物理屏幕坐标。当显示器启用DPI缩放(如125%、150%)时,逻辑坐标与物理坐标不匹配,导致截图位置偏移。原代码中使用localToScreen()转换的是逻辑坐标到屏幕逻辑坐标,而非物理坐标,这是核心问题。
修复方案
- 获取目标屏幕的输出缩放因子(即DPI缩放比例),通过
Screen.getOutputScaleX()和Screen.getOutputScaleY()获取。 - 将用户选择的逻辑坐标转换为物理坐标,再传入
Robot.getScreenCapture()方法。 - 修正截图区域的宽高计算,避免因缩放导致的区域偏差。
修改后的关键代码
核心修正:takeSnapshot方法
private boolean takeSnapshot() { double x = Math.min(clickX, dragX); double y = Math.min(clickY, dragY); double width = Math.abs(dragX - clickX); double height = Math.abs(dragY - clickY); if (width > 0 && height > 0) { // 获取屏幕缩放因子 double scaleX = screen.getOutputScaleX(); double scaleY = screen.getOutputScaleY(); // 将逻辑坐标转换为物理坐标 Rectangle2D screenVisualBounds = screen.getVisualBounds(); double physicalX = (screenVisualBounds.getMinX() + x) * scaleX; double physicalY = (screenVisualBounds.getMinY() + y) * scaleY; double physicalWidth = width * scaleX; double physicalHeight = height * scaleY; // 使用物理坐标进行截图 final WritableImage image = new javafx.scene.robot.Robot().getScreenCapture( null, physicalX, physicalY, physicalWidth, physicalHeight, false); File file; int i = 0; while ((file = new File(storeURI.resolve("snapshot_" + i + ".png"))).exists()) { i++; } try { ImageIO.setUseCache(false); ImageIO.write(SwingFXUtils.fromFXImage(image, null), "png", file); snapshotFile = file; } catch (IOException ex) { Log.error(ex); new Alert$(Alert.AlertType.ERROR) .withOwner(getScene()) .withTitle("错误") .withContextText(ex.toString()) .withException(ex) .show(); } return true; } return false; }
额外优化:repaintCanvas方法
原代码中截图区域的绘制逻辑存在坐标判断漏洞,修正后确保选中区域的高亮框与用户拖拽范围完全匹配。
完整修改后代码
import com.wisecoders.dbs.sys.Log; import com.wisecoders.dbs.sys.fx.Alert$; import javafx.application.Platform; import javafx.embed.swing.SwingFXUtils; import javafx.geometry.Rectangle2D; import javafx.scene.Scene; import javafx.scene.canvas.Canvas; import javafx.scene.canvas.GraphicsContext; import javafx.scene.control.Alert; import javafx.scene.image.WritableImage; import javafx.scene.input.KeyCode; import javafx.scene.input.KeyEvent; import javafx.scene.input.MouseButton; import javafx.scene.layout.StackPane; import javafx.scene.media.Media; import javafx.scene.media.MediaPlayer; import javafx.scene.paint.Color; import javafx.stage.Screen; import javafx.stage.Stage; import javafx.stage.StageStyle; import javax.imageio.ImageIO; import java.io.File; import java.io.IOException; import java.net.URI; class SnapshotStage extends Stage { private final Screen screen; private final Canvas mainCanvas = new Canvas(); private double clickX, clickY, dragX, dragY; private enum Status { waitForSelection, selectArea }; private Status status = Status.waitForSelection; private File snapshotFile; private final URI storeURI; SnapshotStage(Screen screen, URI storeURI ){ this.screen = screen; this.storeURI = storeURI; this.initStyle(StageStyle.TRANSPARENT); setAlwaysOnTop(true); final StackPane stackPane = new StackPane(); stackPane.setStyle("-fx-background-color:transparent;"); stackPane.getChildren().add( mainCanvas ); Rectangle2D screenBounds = screen.getVisualBounds(); final Scene scene = new Scene(stackPane, screenBounds.getWidth(), screenBounds.getHeight(), Color.TRANSPARENT); scene.setFill(Color.TRANSPARENT); setScene(scene); setX(screenBounds.getMinX()); setY(screenBounds.getMinY()); scene.addEventHandler( KeyEvent.KEY_PRESSED, e -> { if ( e.getCode() == KeyCode.ESCAPE ){ SnapshotStage.this.close(); } }); mainCanvas.setWidth( screenBounds.getWidth() ); mainCanvas.setHeight( screenBounds.getHeight() ); mainCanvas.setOnMouseMoved( e -> { clickX = e.getX(); clickY = e.getY(); repaintCanvas(); }); mainCanvas.setOnMousePressed( e -> { if (e.getButton() == MouseButton.PRIMARY) { clickX = e.getX(); clickY = e.getY(); } }); mainCanvas.setOnMouseDragged( m -> { if ( m.getButton() == MouseButton.PRIMARY ) { status = Status.selectArea; dragX = m.getX(); dragY = m.getY(); repaintCanvas(); } }); mainCanvas.setOnMouseReleased( m -> { if ( status == Status.selectArea ) { try { if ( takeSnapshot()) { final Media sound = new Media( String.valueOf( SnapshotStage.class.getResource("/icons/sounds/camera_shooter.mp3") )); final MediaPlayer mediaPlayer = new MediaPlayer(sound); mediaPlayer.play(); } } catch ( Throwable ex ){ Log.error( ex ); } SnapshotStage.this.close(); } }); Platform.runLater( this::repaintCanvas ); } private boolean takeSnapshot() { double x = Math.min( clickX, dragX ); double y = Math.min( clickY, dragY ); double width = Math.abs(dragX - clickX); double height = Math.abs(dragY - clickY); if( width > 0 && height > 0 ) { // 获取屏幕缩放因子 double scaleX = screen.getOutputScaleX(); double scaleY = screen.getOutputScaleY(); // 将逻辑坐标转换为物理坐标 Rectangle2D screenVisualBounds = screen.getVisualBounds(); double physicalX = (screenVisualBounds.getMinX() + x) * scaleX; double physicalY = (screenVisualBounds.getMinY() + y) * scaleY; double physicalWidth = width * scaleX; double physicalHeight = height * scaleY; // 使用物理坐标进行截图 final WritableImage image = new javafx.scene.robot.Robot().getScreenCapture( null, physicalX, physicalY, physicalWidth, physicalHeight, false); File file; int i = 0; while ((file = new File( storeURI.resolve("snapshot_" + i + ".png"))).exists()) { i++; } try { ImageIO.setUseCache(false); ImageIO.write( SwingFXUtils.fromFXImage( image, null ), "png", file); snapshotFile = file; } catch (IOException ex) { Log.error(ex); new Alert$(Alert.AlertType.ERROR) .withOwner(getScene()) .withTitle("错误") .withContextText(ex.toString()) .withException(ex) .show(); } return true; } return false; } private void repaintCanvas(){ if ( mainCanvas.isVisible() && mainCanvas.getScene() != null ) { GraphicsContext gc = mainCanvas.getGraphicsContext2D(); gc.clearRect(0, 0, mainCanvas.getWidth(), mainCanvas.getHeight()); gc.setFill(Color.rgb(0, 0, 0, 0.2)); gc.fillRect(0, 0, mainCanvas.getWidth(), mainCanvas.getHeight()); if (status == Status.waitForSelection) { gc.setStroke( Color.web("#672824")); gc.setFill( Color.web("#672824")); gc.setLineWidth(1d); gc.strokeLine(clickX, 0, clickX, mainCanvas.getHeight()); gc.strokeLine(0, clickY, mainCanvas.getWidth(), clickY); gc.fillText("拖拽选择截图区域", clickX + 10, clickY - 15 ); } else { double rectX = Math.min(clickX, dragX); double rectY = Math.min(clickY, dragY); double rectWidth = Math.abs(dragX - clickX); double rectHeight = Math.abs(dragY - clickY); gc.clearRect(rectX, rectY, rectWidth, rectHeight); gc.setStroke(Color.RED); gc.setLineWidth(1d); gc.strokeRect(rectX - 1, rectY - 1, rectWidth + 2, rectHeight + 2); } } } File getSnapshotFile(){ return snapshotFile; } }
内容的提问来源于stack exchange,提问作者DbSchema
相关产品推荐
相关产品推荐

