谈谈 HiDPI —— 是什么,为什么,怎么做
如果你在搜索引擎中输入「HiDPI」,那么你只会找到一堆和黑苹果有关的内容,和一堆注入 EDID 的「一键开启 HiDPI 脚本」。但是,到底什么是 HiDPI?什么是「用四个像素渲染一个像素」?我凭自己的拙见水一篇文章,粗浅地介绍一下 HiDPI 的定义、macOS「缩放」的原理,以及相关的逻辑。
HiDPI 的定义 —— 什么是 HiDPI?
HiDPI 其实是一个缩写,全称是 High Dots Per Inch,字面意思就是「每英寸包含数量更多的像素」,通俗点讲也就是 分辨率特别高但是尺寸并不大的屏幕,在市场上被称为 Retina(视网膜)屏幕。所以 HiDPI 的意思就是像素密度特别大的屏幕,好了,全文完(被打)。
实际上人们在说 HiDPI 时,他们说的其实是图片里的东西,也就是 macOS 中的「缩放(Scaled)」。本文要讨论的「HiDPI」也就是 —— 苹果是如何在分辨率更高的 Retina 视网膜屏幕上渲染和传统屏幕看起来大小相同、却更加清晰的图象的。
名词解释
为了接下来的描述,首先要定义几个名词:
- 硬件分辨率:由你的显示器物理硬件决定的分辨率。比如我的 ThinkPad E480 有一块 14 寸的 FHD 屏幕,分辨率是 1920x1080,那么我说这块屏幕的硬件分辨率是 1920x1080。同理,我们可以说 2019 年发布的 16 寸 MacBook Pro 的硬件分辨率是 3072x1920、2019 年发布的 13 寸 MacBook Pro 的硬件分辨率是 2560x1600。
- 硬件像素:你的显示器上的一个一个实际存在的像素点,在 LCD 屏上一组三个液晶构成一个硬件像素,OLED 屏幕上一组 3~4 个三色的发光二极管构成一个硬件像素。硬件像素组成了硬件分辨率,如 1920x1080 个硬件像素构成了一块 FHD 屏幕。
- 逻辑分辨率:我们的大脑觉得画面的分辨率是多少。比如说在一块 14 寸的 UHD 屏幕(3840x2160)的屏幕上显示的窗口和内容数量,和在一块 14 寸硬件分辨率为 1920x1080 的的 FHD 屏幕上的同一个窗口的大小以及内容数量是一样的,那么我们说这块 UHD 屏幕的逻辑分辨率是 1920x1080。
- 逻辑像素:逻辑像素组成了逻辑分辨率,正如硬件像素组成了硬件分辨率。一块 UHD 屏幕的逻辑分辨率是 1920x1080,意味着这块屏幕在显示图象时包括了 1920 乘以 1080(也就是 2073600)个逻辑像素。
需要注意的是,「逻辑分辨率」根本不存在,一切都是「我觉得」——「我觉得这块 4K 屏幕能显示的内容和 1080P 的屏幕是一样多的,不过 4K 屏幕上的画面看起来要更清晰」。在 macOS 界面中相关的说明文字用的词语也是「Looks like」(看起来像是)。
看到这张图的「Looks like 1680x1050」(看起来像 1680x1050)了么?这里的 1680x1050 就是刚才定义的「逻辑分辨率」。
「逻辑分辨率」是不存在的,那么基于「逻辑分辨率」而定义出来的名词 「逻辑像素」自然也是不存在 的。
什么是「用四个像素渲染一个像素」?
相信很多人都看过上面这张图,也有不少人听说过「用四个像素渲染一个像素」这种说法。那么,这句话到底是什么意思呢?
下图是一台 MacBook Pro,上面显示了一个大大的红色的圆。现在让我们简化一下模型,假设这个红色的圆只能用 8x8 个硬件像素来渲染,那么四分之一个圆则要用到 4x4 个硬件像素。
如上图所示,如果只用 4x4 个像素显示一个四分之一圆,圆在每个像素中占据的面积是不同的,在显示时,需要根据圆在每个逻辑像素中占据的面积、通过算法决定该像素的颜色、对边缘进行「虚化」以补偿视觉效果。
macOS 的 Smooth Font 和 Windows 的 ClearType,依然是基于这种「虚化边缘」的方式试图改善字体显示的清晰度。
然后有一天,你买了一台带 Retina 屏幕的 MacBook Pro,新的屏幕大小不变、但硬件像素的数量翻了四倍 —— 也就是说,之前那块屏幕上 4x4 个硬件像素的面积、在新的屏幕上已经可以容纳 8x8 个硬件像素了。要是在新的 Retina 屏幕上还用 16 个硬件像素渲染一个四分之一圆、那么圆的直径就只有之前的一半、面积就只有四分之一了。
为了确保相同的屏幕上显示内容的密度(在这个例子中,就是这个圆的面积不发生改变),我们可以让新屏幕的四个硬件像素显示旧屏幕一个硬件像素的内容(此时 Retina 屏幕上的这四个硬件像素显示的颜色完全相同),也就是让一个逻辑像素 直接拉伸对应 四个硬件像素,如 中间的网格图所示。现在圆的面积的确和之前一样大了,但是显示效果(清晰度)没有得到任何提升。
如果要在 Retina 屏幕上达成 右边那张网格图 的显示效果,我们还需要一些别的操作。
如果要提高画面清晰度,画面在渲染时的逻辑像素应该和硬件像素一一对应起来。原来的四分之一圆对应了 4x4 个逻辑像素(如上图 左边的网格图 所示)。如果我们先将画面放大 4 倍,这个四分之一圆就要对应 8x8 个逻辑像素了。再将画面渲染到 8x8 的逻辑像素上(如上图 中间的的网格图 所示)、最后一一对应的显示到 8x8 个硬件像素上(如上图 右边的网格图所示)。现在,相比直接将 4x4 拉伸到 8x8,搭配 HiDPI 的 Retina 屏幕还是用 8x8 个硬件像素渲染一个四分之一圆,圆的面积依然没有发生改变,但是轮廓处「虚化」像素的数量变少了、圆的边缘显得更锐利了。
在传统屏幕上 1 个硬件像素中显示的内容,在 Retina 屏幕上需要用 4 个硬件像素,这就是 Retina 屏幕「用四个像素渲染一个像素」的含义。我们在使用 macOS 截图的时候,截取的正是图象放大四倍后这 8x8 个逻辑像素(而不是放大前的 4x4 个逻辑像素),因此 我们在截图的时得到的图片大小比屏幕显示的分辨率要大。
等等,那么非整数倍的缩放呢?
如果你还记得上面那张图的话 —— 什么,你忘了?那我把那张图再放一遍:
这是 2019 年 13 寸 MacBook Pro 的「系统偏好设置」界面中的显示相关设置。2019 年 13 寸 MacBook Pro 的屏幕的硬件分辨率是 2560x1600。如果四个硬件像素渲染一个逻辑像素,那么这台 MacBook Pro 进行「HiDPI 缩放」后的逻辑分辨率应该只有 1280x800。但是这张截图却直接告诉你,macOS 有能力在 2560x1600 硬件分辨率的屏幕上上显示 1680x1050 逻辑分辨率的画面。
毫无疑问,硬件像素是一个一个独立存在、不能拆开的 —— 没有人能点亮 1.5 个硬件像素。所以非整数倍的缩放是怎么实现的呢?让我们用逆向思维来思考这个问题。我们的最终目标是 将硬件分辨率 2560x1600 全部都利用起来,这是大前提;为了完全利用 2560x1600 的硬件分辨率,那么原始逻辑分辨率放大 4 倍后应该包含 2560x1600 个逻辑像素,所以 放大四倍前的原始逻辑分辨率应该是 1280x800 个像素。但是为了能容纳 1680x1050 的逻辑分辨率,需要 将 1680x1050 线性缩放到 1280x800,即需要先将原始画面缩小到原来的 76.2%。
通过逆向思维,我们把数字算出来了,现在让我们理一理这个流程。假设我们在 1680x1050 的逻辑分辨率下有一个边长为 500 个逻辑像素的正方形窗口,macOS 的 WindowServer 会首先将这个正方形窗口缩小到原来的 76.2%,也就是 381 个逻辑像素。接着将这个边长 381 个逻辑像素窗口的边长放大到原来的两倍(即面积放大到原来的四倍),也就是正方形窗口边长变成了 762 个逻辑像素。窗口面积放大四倍后,这 762x762 个逻辑像素和硬件像素是一一对应进行显示的。所以,在这台 2019 年的 13 寸 MacBook Pro 上,边长 500 个逻辑像素的窗口实际上需要用到 762x762 个硬件像素,硬件像素的数目比逻辑像素的数量更多、硬件分辨率高于原始逻辑分辨率,我们看到的窗口画面更清晰了。
你理解了吗?你以为你好像理解了,不就是「先按照一定百分比缩小、经过缩小后的画面再放大四倍、就和硬件像素一一对应」了嘛。那我现在把数字换一下,还是这台 2019 年的 13 寸 MacBook Pro,不过现在正方形窗口的边长是 100 个逻辑像素而不是 500 个,乘以 76.2% 得到 76.2、再把边长放大两倍(也就是面积放大四倍)得到 152.4 个像素。如果要和硬件像素一一对应的话,这 0.4 个像素怎么办,难道要扔掉吗?
所以要注意一点,我之前举的例子中使用了「缩小到原来的 76.2%」这种说法,纯粹是为了便于大家理解「先缩小再放大四倍渲染」的流程,实际上 这个说法是完全错误的。
如果你接触过 iOS、macOS 应用开发的话,应该会注意到,在 macOS、iOS 应用中的图片和图标资源的大小使用的单位并不是 Pixels(px,像素),而是 Points。和像素 Pixels 必须是正整数不同,Points 可以是浮点数、也就是允许拥有小数。Points 的单位只代表一种 比例关系、和现实世界中(或者显示器屏幕上)占据的实际大小 没有任何关系,macOS、iOS 会自行计算一个 Points 应该对应的像素数量。所以所谓「缩小 76.2%」,本质上是一个 Points 需要对应的 Pixels 数量减少了而已。除此以外,macOS、iOS 在渲染图象时还会通过 vector 方法保留画面的线条、并得到更加锐利的边缘。
参考资料
- 「HiDPI - ArchWiki - Arch Linux」
- 「High Resolution Explained: Features and Benefits - Apple Developer Documentation Archive」
- 「有关 retina 和 HiDPI 那点事 - 赵勃的文章 - 知乎专栏」
- 「Windows 和 macOS 在高 DPI 支持上的差距,真的只是对生态的把控力问题吗? - DreamPiggy的回答 - 知乎」— 这一回答介绍了 macOS 和 Windows 在 API 设计上的根本区别:Win32 API 里使用的是硬件像素数量、而苹果一开始就用强硬的手腕在 API 中引入了抽象的 Points 的概念。