2

我们的交互式数学应用程序是使用 Swing JFrames 设计的,并且是为 2560p (Mac) 和 1920p (Win) 显示器开发的,即使用原生像素坐标进行图形输出和鼠标输入处理。

iMac Retina 5K 测试——大惊喜!应用程序窗口的正常宽度是屏幕宽度的 1/2。在高分辨率(5120p)iMac 上运行时,我完全预料到会出现问题——1/4 屏幕宽度。但是不,它呈现 1/2 屏幕宽。

查看程序在哪里轮询系统以获取显示设备边界:

    GraphicsDevice[] deviceList = getLocalGraphicsEnvironment.getScreenDevices()

    GraphicsConfiguration[] gc = deviceList[0].getConfigurations();

    Rectangle deviceBounds = gc[0].getBounds(); 

我看到它返回为 [ 0, 0, 2560, 1440 ],即使 MacOS 系统报告显示 [5120 x 2880 ]。

此外,作为应用程序输入的 MouseEvent 坐标被“减半”,以便与 d​​eviceBounds 一致。

我找不到这些测试结果的任何解释。不用说,我很高兴该应用程序无需修改即可在 5K Retina iMac 上完美运行。

但是,我真的需要了解谁在自动缩小原生图形环境。这是 MacOS 所做的吗?Java16 为 Swing 应用程序做了什么?或者,Java16 MacOS JRE 对 Swing 应用程序的作用?第四种可能?

4

1 回答 1

4

tl;博士

您问:

我真的需要了解谁在自动缩小原生图形环境。

  • macOS 缩放屏幕,为应用程序提供逻辑与物理屏幕分辨率。
  • Java 2DJava AWTJava Swing尊重并利用了这种扩展性。

细节

警告:不是Swing 图形工作原理方面的专家,但我或许可以为您提供一个关于正在发生的事情的基本图片(双关语!)。


为了简化故事,您可以认为存在逻辑像素与物理像素。

在过去,例如最初的 Mac,72 个像素分布在一英寸的实际屏幕空间中。选择这个 72 PPI 的数字是因为它几乎与的印刷单位对齐,大约 72 点对应于英寸。(实际上 1 点等于 0.013836 英寸,所以 72 点等于 0.996264 英寸。)

几十年来,屏幕越来越大,像素密度随着越来越多的小像素挤在一起而增加。最终,我们有一个尴尬的财富,足够的像素,我们可以“浪费”它们在更精细的细节上,填充锯齿以获得更平滑的字体和线条。Apple 将他们的实现称为Retina屏幕;其他操作系统也有类似的方案(质量有争议)。如今,显示器每英寸有超过 200 到近 500 个像素。

为了留出额外的物理像素以进行更平滑的处理,我们需要应用程序认为它们正在绘制的逻辑像素、虚构像素。Apple 曾经为此使用每英寸 72 像素 (PPI),以便与不知道额外平滑问题的旧软件兼容。情况可能仍然如此,或者现在 PPI 是可变的(我不知道)。但确切的数字是无关紧要的。关键是每英寸的逻辑像素比物理的少得多。

➥ 与您的问题密切相关的是,现代 Java Swing 图形技术尊重这些逻辑像素,同时允许额外的物理像素进行额外的平滑处理。

我隐约记得,在过渡初期可能并非如此,多余的像素被浪费了,没有用于平滑/细节处理。因此,有一段时间,在那个时候,Sw​​ing 应用程序在屏幕上出现在其他熟悉 Retina 的应用程序旁边时会显得“矮胖”/“锯齿状”。最终,Java 2D 和 Java Swing 被增强为支持 Retina。而且,,现代 Swing 应用程序在高分辨率屏幕上看起来很棒。

(顺便说一句,在Java 17 中,Java 图形技术正在重新设计以与 Apple 的Metal框架而不是OpenGL集成,以实现更快、更丰富的图形。)

为额外平滑保留多少额外物理像素的比例由用户控制。在 macOS 上,您可以控制System Preferences > Display > Resolution中的设置。在那里您确定逻辑像素数。

这是我自己的系统的屏幕截图,一台 MacBook Pro(13 英寸,M1,2020),外接 BenQ 4K 显示器(顺便说一下,我强烈推荐它,认证为真正无闪烁)。

请注意显示器是如何在物理上构建的,水平方向为 3,840 像素,垂直方向为 2,160 像素。这块 3840×2160 就是俗称的4K 显示器

但我有视网膜缩放参与。因此,操作系统和应用程序的行为就好像我只有 3,008 x 1,692 像素,即逻辑像素数。我使用这种平滑,以便 (a) 内容看起来更大,更重要的是 (b) 内容看起来更平滑,填充更多细节。

报告逻辑屏幕分辨率的自定义 Java Swing 应用程序、确认逻辑屏幕分辨率的系统报告和显示实际物理像素数的系统信息应用程序的屏幕截图。

左上角是我编写的自定义 Java 程序的白色应用程序窗口。此应用程序报告 Java Swing 看到的屏幕。绿色标注线用于我的内部显示器,而橙色线用于我上面讨论的外部 4K 显示器。

➥ 注意 Java Swing 如何报告与我们在缩放中设置的相同的逻辑像素分辨率。因此,如果您将 Java SwingJFrame窗口的大小调整为屏幕宽度的一半,那么在这种情况下,您将调整为 3008 的一半而不是 3840 的一半。

您可以尝试使用此应用程序。更改 Mac 的缩放系数,然后点按“更新屏幕信息”按钮以查看最新数字。

➥ 鉴于您似乎对这种行为感到惊讶,我怀疑您在以前的 Mac 上取消了 Retina 缩放。如果不进行缩放,您的软件使用的是“原生”物理像素而不是逻辑像素。您可以说,由于物理像素等于虚拟像素,因此一对一映射。

这是该应用程序的源代码。

package work.basil.screen;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.util.ArrayList;
import java.util.List;

// Example app to show current logical screen resolutions.
// By Basil Bourque. http://www.Basil.work/
public class Screener extends JFrame
{
    public static void main ( String[] args )
    {
        Screener frame = new Screener();
        frame.setVisible( true );
    }

    // Constructor
    public Screener ()
    {
        // Layout
        this.setDefaultCloseOperation( JFrame.DISPOSE_ON_CLOSE );
        this.setPreferredSize( new Dimension( 600 , 200 ) );
        this.setLayout( new BorderLayout() );

        // Widgets
        JTextArea report = new JTextArea( "report goes here" );
        report.setLineWrap( true );
        report.setWrapStyleWord( true );

        JButton update = new JButton( "Update screen info" );
        update.addActionListener( ( ActionEvent e ) -> report.setText( reportScreenInfo() ) );

        // Arrange
        this.add( report , BorderLayout.CENTER );
        this.add( update , BorderLayout.PAGE_END );
        this.pack();
    }

    private String reportScreenInfo ()
    {
        List < String > reportItems = new ArrayList <>();

        GraphicsDevice[] deviceList = GraphicsEnvironment.getLocalGraphicsEnvironment().getScreenDevices();
        for ( GraphicsDevice graphicsDevice : deviceList )
        {
            reportItems.add( "graphicsDevice ID: " + graphicsDevice.getIDstring() );
            GraphicsConfiguration[] graphicsConfigurations = graphicsDevice.getConfigurations();
            for ( GraphicsConfiguration gc : graphicsConfigurations )
            {
                reportItems.add( "bounds: " + gc.getBounds() );
            }
        }

        String result = String.join( System.lineSeparator() , reportItems );
        return result;
    }
}
于 2021-08-21T01:03:03.280 回答