有没有办法通过代码或命令行(JDK 9+)禁用 swing 中的原生 DPI 缩放?

Is there a way to disable native DPI scaling in swing via code or command line (JDK 9+)?

我最近决定从我使用的 JDK 切换到更新版本。 (特别是从 jdk1.8.0_261jdk-14.0.2)。这次升级非常顺利,因为大多数功能都按照我预期的方式工作,而且我能够找到如何适应任何不一样的地方。

但是,如标题所示,我在使用 Swing 编写的特定应用程序中遇到了一个问题。我的主显示器是 4k 显示器,我已将该显示器的 windows 系统缩放设置为 200%。虽然每个其他 Swing 应用程序都被缩放以匹配该 DPI 设置很好,但对于这个特定的应用程序,我想禁用该缩放并绘制像素 1:1,尤其是在分发此应用程序时。

有没有办法通过 VM 参数禁用这种自动缩放?或者有没有办法在对 swing 库进行任何调用之前在运行时禁用缩放? (类似于必须在对库进行任何其他调用之前对外观进行任何更改。)

供参考:

这是我用来创建我的 JFrame 和创建图形的代码 object 我将所有内容绘制到上面,它发生在这个 class 和这个问题的范围之外的定时循环中.

public class ScreenManager {
    private GraphicsDevice device;
    private final JFrame frame;
    
    private static ScreenManager instance;
    
    public ScreenManager() {
        instance = this;
        
        GraphicsEnvironment env = GraphicsEnvironment.getLocalGraphicsEnvironment();
        this.device = env.getDefaultScreenDevice();
        
        this.frame = new JFrame(game.getGame().gameName());
        this.frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        this.frame.setUndecorated(true);
        this.frame.setIgnoreRepaint(true);
        this.frame.setResizable(false);
        
        this.device.setFullScreenWindow(frame);
        this.device.setDisplayMode(device.getDisplayMode());
        
        this.frame.createBufferStrategy(2);
    }

    public Graphics2D getRenderGraphics() {
        Window window = device.getFullScreenWindow();
        if (window != null) {
            BufferStrategy strategy = window.getBufferStrategy();
            Graphics2D g = (Graphics2D) strategy.getDrawGraphics();
            g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
            g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
            return g;
        }
        return null;
    }

    public void updateDisplay() {
        Window window = device.getFullScreenWindow();
        if (window != null) {
            BufferStrategy strategy = window.getBufferStrategy();
            if (!strategy.contentsLost()) {
                strategy.show();
            }
        }
        Toolkit.getDefaultToolkit().sync();
    }
    // other methods go here
}

使用 AffineTransforms 会,虽然欢迎建议,但并不能真正帮助解决这个问题,因为我需要能够以任何显示器的原始分辨率显示并且已经在图形上广泛使用 AffineTransforms object。

EDIT:我曾尝试将 System.setProperty("sun.java2d.dpiaware", "false"); 作为 main 中的第一行,但没有成功。 属性错了吗?

编辑 2:增加了更清晰的内容:

我的渲染方法如下所示:

private void render(Graphics2D g) {
    // Black out the screen to prevent old stuff from showing
    g.setColor(Color.BLACK);
    g.fillRect(0, 0, ScreenManager.getScreenWidth(), ScreenManager.getScreenHeight());
    
    // Set the transform for zoom to draw the zoomed stuffs
    AffineTransform saveState = g.getTransform();
    AffineTransform cameraTransform = ScreenManager.getInstance().getCamera().getCurrentTransform();
    g.transform(cameraTransform);
    
    // RENDER UPDATEABLE
    this.game.getController().renderGame(g);
    
    // REDNER STATIC GUI
    g.setTransform(saveState);
    // draw mouse cords
    Point mouse = this.mouseHandler.getFrameMousePoint();
    if (mouse != null) {
        Font f = new Font("Consolas", 0, 40);
        Point2D scaledPos;
        try {
            scaledPos = cameraTransform.inverseTransform(mouse, null);
        } catch (NoninvertibleTransformException e) {
            scaledPos = mouse;
            e.printStackTrace();
        }
        String s1 = mouse.toString();
        String s2 = scaledPos.toString();
        Rectangle2D r = f.getMaxCharBounds(g.getFontRenderContext());
        int yOff = (int) r.getHeight();
        Color c = new Color(100, 100, 100, 191);
        g.setColor(c);
        g.fillRect(mouse.x, mouse.y, (int) (r.getWidth() * (s1.length() > s2.length() ? s1.length() : s2.length())), yOff + yOff + yOff);
        g.setColor(Color.WHITE);
        g.setFont(f);
        g.drawString(s1, mouse.x, mouse.y + yOff);
        g.drawString(s2, mouse.x, mouse.y + yOff + yOff);
    }
}

这里调用了这个方法:

Graphics2D g = screenManager.getRenderGraphics();
render(g);
g.dispose();
screenManager.updateDisplay(); // SYNCS SCREEN WITH VSync

我已经打印出图形 object 首次创建时的 AffineTransform,它打印成这样 AffineTransform[[2.0, 0.0, 0.0], [0.0, 2.0, 0.0]] 这确实让我得出结论,缩放是在代码中完成的.然而,问题是,使用我的自定义变换 ScreenManager.getInstance().getCamera().getCurrentTransform(); 渲染的东西也被放大了 2, 即使我没有联系我的变换并将其设置为我的。(实际上没关系,我意识到我在我的转换上做 g.transform 而不是 g.setTransform。不过我仍然将其包含在 write-up 中)。

解决我的渲染问题的解决方法是 g.setTransform(AffineTransform.getScaleInstance(1, 1); 然而,这超出了我想要做的,并且它(在我的代码上尝试之后)没有解决 awt 坐标的问题 space。意思是,渲染按预期工作,但鼠标位置没有。当鼠标位于我屏幕的右下角时,它会给出一个位置,该位置是我显示器原始分辨率的一半。此解决方案 对我不起作用 。处理时间很宝贵,并且必须完成更多计算才能 UNDO 添加此功能。 Here is a link discussing how this would be implemented. 我 link 指出其中提到开发人员可以做到的部分,但开发人员需要做很多工作。

我还是想知道是否有办法:

经过一段时间的其他工作后回到这个问题后,我遇到了我正在寻找的解决方案,这主要是偶然的。

我正在寻找的解决方案是 Java 系统 属性 或 JVM 参数,它会禁用 Swing 组件的 DPI 感知,这意味着在具有 200% 缩放比例的系统上,用户输入space 和组件渲染将匹配屏幕的原始分辨率,而不是 200% 缩放分辨率(例如,期望从 3840x2160 屏幕读取鼠标输入并渲染到 3840x2160 屏幕,而不是发生的情况,鼠标输入的上限为 1920x1080 ).

to a 中讨论了此解决方案。为了完整起见,我将在这里重述。

解决方案是将 -Dsun.java2d.uiScale=1 作为 VM 参数传递给 VM。这强制 UI 比例为 1,忽略任何系统缩放和渲染,并以原始分辨率收集鼠标输入。我还可以确认在调用任何 swing class 之前调用 System.setProperty("sun.java2d.uiScale", "1"); 也会产生我正在寻找的结果。