tl;博士
您问:
我真的需要了解谁在自动缩小原生图形环境。
细节
警告:我不是Swing 图形工作原理方面的专家,但我或许可以为您提供一个关于正在发生的事情的基本图片(双关语!)。
为了简化故事,您可以认为存在逻辑像素与物理像素。
在过去,例如最初的 Mac,72 个像素分布在一英寸的实际屏幕空间中。选择这个 72 PPI 的数字是因为它几乎与点的印刷单位对齐,大约 72 点对应于英寸。(实际上 1 点等于 0.013836 英寸,所以 72 点等于 0.996264 英寸。)
几十年来,屏幕越来越大,像素密度随着越来越多的小像素挤在一起而增加。最终,我们有一个尴尬的财富,足够的像素,我们可以“浪费”它们在更精细的细节上,填充锯齿以获得更平滑的字体和线条。Apple 将他们的实现称为Retina屏幕;其他操作系统也有类似的方案(质量有争议)。如今,显示器每英寸有超过 200 到近 500 个像素。
为了留出额外的物理像素以进行更平滑的处理,我们需要应用程序认为它们正在绘制的逻辑像素、虚构像素。Apple 曾经为此使用每英寸 72 像素 (PPI),以便与不知道额外平滑问题的旧软件兼容。情况可能仍然如此,或者现在 PPI 是可变的(我不知道)。但确切的数字是无关紧要的。关键是每英寸的逻辑像素比物理的少得多。
➥ 与您的问题密切相关的是,现代 Java Swing 图形技术尊重这些逻辑像素,同时允许额外的物理像素进行额外的平滑处理。
我隐约记得,在过渡初期可能并非如此,多余的像素被浪费了,没有用于平滑/细节处理。因此,有一段时间,在那个时候,Swing 应用程序在屏幕上出现在其他熟悉 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 程序的白色应用程序窗口。此应用程序报告 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;
}
}