写这篇文章是因为前几天闲着没事折腾自己网站的响应式设计时,突然注意到调试工具里显示 iPhoneX 可视区域宽度才 375px,我一阵迷茫:iPhoneX 屏幕 2436×1125 的分辨率宽度不应该 1125px 才对吗。带着疑问一查,如愿以偿牵扯出本文要写的一堆东西。在深究这几个名词背后的知识之前我从没想过我会被像素这东西难倒。不过想想整个世界都在尽人类想象所能地变得难懂,这也不算丢人(安慰自己
如果在了解这些知识之前你问我什么是像素,我大概会给出一个很模糊的概念,类似“图片的基本元素”之类的,同时我又知道屏幕分辨率好像也用的是像素,仅此而已。还好我之前的理解也没什么大错。突然想起黑客帝国,那咱们就先说代表现实世界的红色药丸,也就是物理像素。
物理像素
这里就不写百科式的定义了,我们知道,我们面前的手机屏幕或电脑屏幕其实就好像是几百万个闪烁的小彩灯,这些彩灯我们称之为 pixel,也即物理像素(显然,物理像素是一个固定大小的物体)。其中每一个像素又由一些红色,绿色和蓝色的 “sub-pixel” 组成。我们的肉眼无法看到 “sub-pixel”,因为三个颜色被混合为一个单一 pixel 的颜色并呈现给我们,但是这些内容并不和程序员直接相关。下面两张图也许有助于你对 pixel 有一个本质的认识:
逻辑像素
我们可以把逻辑像素看成数字世界的像素,也可以说成一个电子图片的最小可寻址单元。当我们说在 PS 里创建一个 300×300 像素的图片时,我们指的就是逻辑像素。需要注意的是,相对于物理像素而言逻辑像素没有固定的物理尺寸,也就是说除非我们指定显示条件,上面那张 300×300 像素图片的物理尺寸是不确定的。
为什么要引入逻辑像素的概念
为了说明引入逻辑像素的必要性,我们假设世界上暂时没有逻辑像素这一概念,像素这个词仅仅指物理像素。然后想象有这么一个 3×5 像素的字母显示在一个 8×8 像素的屏幕上,既然像素仅仅指物理像素那事情就简单多了,我画了个示意图:然后突然有一天屏幕升级了,物理分辨率提升至 16×16 像素,而字母的长宽不变,结果显而易见,我们的字母在屏幕上看起来变小了:目光放到智能手机上,五六年前手机屏幕的物理分辨率至多也就 720P,但随着技术的进步和人们对屏幕要求的增加,现如今有些手机屏幕的物理分辨率甚至能达到 4K。假如我们就像上面的例子一样仍然坚持不引入逻辑像素的话,那么在 720P 分辨率手机上能正常显示的一段文字,放到 4K 分辨率手机上则会小到几乎无法阅读!所以我们需要引入独立于任何物理介质或设备之外的逻辑像素。在引入逻辑像素后,我们仍然用 “3×5 像素” 来描述字母 F 的三围,但这里的像素这时不再指物理像素,而是指逻辑像素。如果厂商为了保证用户体验,希望高分辨率屏幕上的 F 看起来和低分辨率屏幕上的 F 一样大的话,只需让字母 F 的一个逻辑像素对应屏幕上的两个物理像素即可,如图:
dp 与 points
据我了解,逻辑像素这个名字在实际开发中并不常见,同样的概念 Android 管叫做设备独立像素,简称 dp,iOS 则叫做 points,注意,iOS 里用的 points 与印刷行业里的 point(pt) = 1/72inch 无关(存疑,虽然我没有查到权威的资料但只有这样才说得通)。在挨个百度这几个名词前我本以为 dp、points 和逻辑像素是不同的概念,分别学习后仔细一想,发现它们的本质其实是相同的。这我就忍不住开始纳闷为什么 Google 和 Apple 不直接用逻辑像素这个名字,非得再发明一个新概念来描述逻辑像素?想了想可能是逻辑像素更偏向于理论描述一点,并且是逻辑像素用 dp 或 points 另指后,可以让 px 专指物理像素,从而防止歧义(毕竟逻辑像素和物理像素虽然概念不同,但人们却一直用的同一个缩写 px)。
另外,在网页设计里,css 代码中的 px 仅指逻辑像素,这也回答了我第一段的疑问。因为 iPhoneX 的逻辑分辨率是 375×812,所以说调试工具里才显示它的宽度是 375px。
dp 和 points 对于实现密度独立性至关重要,在更深入一点的讨论 Android 和 iOS 的屏幕适配问题之前,我们得先了解一下 PPI 与 DPI。
PPI 和 DPI 的经典定义
PPI 是 Pixels Per Inch(每英寸像素数)的简称,又被称为像素密度 ,是一个表示打印图像或显示器单位面积上像素数量的指数。其计算方法是对角线上的物理像素数量除以对角线的长度(英寸),计算公式为:$$PPi = \sqrt{\dfrac{{n_x}^2+{n_y}^2}{l}}$$其中 $n_x$ 与$n_y$ 分别是打印图像或者显示器的水平垂直分辨率(即水平和垂直能显示的像素数量),l 则表示对角线长度。以对角线上的像素密度为准计算是因为,虽然通常水平和垂直方向上的 PPI 是相同的,因为大多数设备是正方形像素,但也有个别设备是非正方形像素。假如已确定水平和垂直方向上的像素密度相同,可以直接用 $PPi = \dfrac{n_x}{x} $ 或者 $PPi = \dfrac{n_y}{y} $ 来计算 PPI。
DPI(英语:Dots Per Inch,每英寸点数)则是一个量度单位,意思是指每一英寸长度中,取样或可显示或输出点的数目,最开始通常用于打印领域。
下面是一张其名为 “Bits of Bits” 的图片,展示了一幅使用了低 DPI 的低分辨率图片,很好的说明了 PPI 和 DPI 的概念。打印机不能通过纸上通过叠加像素的方式重现一张图片。取而代之的方式是通过散出4种颜色(青色,品红,黄色和黑色,即 Cyan, Magenta, Yellow and Black,简称为CMYK)组合的 dot 来重现一幅图像。这些 dot 之间一定会有空隙,而这就是 DPI 所描述的:这些 dot 的密度。
下图左侧说明了打印机是如何使用 dot 重组一幅图片的,右侧的图片则是打印出的 dot 的近距离观看(两幅均来自 Wikipedia)。
PPI 和 DPI 的知识延伸
事实上,PPI 和 DPI 这两个概念光知道 “经典定义” 的话知道和不知道没一点区别,因为这两个词在现在完全就是混着用的。现在 DPI 中的 dot 天天抢 pixel 生意,如今的 dot 既可以指印刷领域的墨点,又可以指数字图像的逻辑像素,还可以指屏幕的物理像素。所以相应的现在很多地方提到 DPI 其实想表达的意思是 PPI,不过还好 PPI 一直都是 PPI。在如今这个误用是大潮流的情况下,我们也别纠结哪哪哪措辞上用了 DPI 其实应该用 PPI 之类的无聊问题。只用结合上下文去理解某个语境下具体在说什么概念就行。
另外也许有人注意到在 PS 新建窗口里分辨率也使用了 PPI 这个参数(如图)。PPI 为什么会出现在这种地方让我着实迷惑了好长时间。其实除非要把这张图片打印出来,这个参数并没有多大意义。但是,PPI 指的是每英寸有多少个像素,可打印纸没像素啊!其实事情是这样的,在打印的过程中,所有在屏幕上组成图片的物理 pixel 都被转换成不同色调的小正方形(小正方形则是由上一节说的 dot 组成)。也就是说纸上没有传统意义上的像素,于是得造一个方块形元素的 “像素” (以引号标注以示区分)以和屏幕上的像素一一对应。这样的话, 对于最终的打印输出而言,我们把 PPI 输入认为是一种调节物理大小的方式,而不是分辨率。 举个例子,假设你想把一张 300×300 像素的图片打印到 1×1 inch 的纸上,只用把 PPI 设置成 300 即可。如果你减少 PPI 的值的话,相应的就增大了打印出的大小,因为 “像素” 的尺寸更大了,输出的图片看上去质量也比较差。但要一定注意的是,如果你站的足够远,图片又会和原来一样清楚,也就是说图片的绝对分辨率并没有改变,还是 300×300 “像素”。当然了,在 PS 里直接调节图像输出的后长宽和调节 PPI 是等价的。
最后,我觉得应该澄清一点,这里的 PPI 应该属于印刷打印领域,别搞混了。还是那句话,要结合语境搞清楚这些名词究竟指的是什么。
iOS 开发中的 points
iPhone 的屏幕很符合人们的常识,也就是屏幕越大,显示内容越多。points 的概念我就不多解释了,跟逻辑像素的本质是相同的。在开发中为了避免像上文 “F 的显示” 那样的问题,同时为了贯彻屏幕越大,显示内容越多这个直接性的原则。我们要使用密度独立性单位 points 进行布局设计。剩下的工作只管交给设备完成,比如系统检测到我的设备是 iPhone8,然后就可以确定渲染倍率等于 2,也就是一个 points 对应两个物理像素,done。需要提醒的是,我们不需要花太多心思在这些交给设备完成的工作上,作为开发者和设计师我们可以假设设备的分辨率就是逻辑分辨率的值,然后该干啥干啥。下面是截至目前各型号 iPhone 的逻辑分辨率、物理分辨率、渲染倍率之类的信息:附注:
- 为什么在 iOS 中使用逻辑分辨率,可以实现让显示内容多少与屏幕尺寸挂钩,而不是分辨率?我们知道 iPhone 的分辨率一向很奇葩,至于那些奇葩的分辨率是如何确定的咱们无从知晓。我的猜测是,假如苹果真的一直在贯彻屏幕越大,显示内容越多这个原则(截止目前来看,是真的),那么应该是先确定好手机屏幕尺寸,然后再根据尺寸确定屏幕分辨率。比如,iPhone5 相对于 iPhone4 屏幕面积增加了 19% 左右,逻辑分辨率则相应增加了 18% 左右;iPhone 6 相对于 iPhone 5 屏幕面积增加了 37% 左右,逻辑分辨率就也增加了 37% 左右,至于 iPhoneX 和 iPhone 6+ 7+ 8+ 比较特殊另当别论。上面的论证过程对开发人员来说仍然无关紧要,只是用来证明在苹果的手里逻辑分辨率是与尺寸挂钩的,而逻辑分辨率又与显示内容多少挂了钩,所以问题结果就显而易见了。
- 什么是密度独立性单位?顾名思义,也就是与屏幕密度无关的单位。再回忆一下前面那个 “F”,面积没变分辨率扩大一倍,也就是密度扩大了一倍后,如果采用密度独立的逻辑像素的话,前后显示效果仍然一致。
- 渲染倍率以什么为基线?看图,显而易见是 iPhone3GS,在 iPhone3GS 上 points 和物理 pixel 一一对应,也就是渲染倍率等于 1。
至于具体的开发流程,可以参考iOS屏幕适配和设计流程分析 – 胡东东博客。
Android 开发中的 dp 与 DPI
首先注意,这里就是一个经典的混用。在 Android 中的 DPI 指的就是 PPI 的意思——屏幕像素密度,或是每英寸像素数,哪个名字都行。
Android 的基本设计理念是使用户界面具有大致相同物理尺寸。首先我们知道 Android 的碎片化严重的要死,什么物理键盘,实体按键,虚拟按键,美人尖,小刘海,曲面屏五花八门应有尽有。我们显然不可能使用苹果的思路去做屏幕适配,因为无数的 Android 设备具有无数的的屏幕密度,于是 Android 把每个 DPI 范围内的设备组合在一起,统一设置成一个特定的系统内置 DPI。这样,应用程序就只需要为每个密度范围而不是对每个可能的密度来进行优化。查官方文档里有一个图说明了这个问题:这个图并不精确,网上的那些个如何精确的判断哪个设备落在哪个范围里的判断方法也都不对,非要说个经验法则的话就是实际密度四舍五入到最近的特定密度,这些东西作为的应用开发者不需要知道,这些工作是 Android 系统干的。
以下是各个系统内置 DPI,以 mdpi 为基准比例渲染:
系统密度 | 值 | 比例 |
---|---|---|
ldpi | 120 | 0.75 |
mdpi | 160 | 1 |
hdpi | 240 | 1.5 |
xhdpi | 360 | 2 |
xxhdpi | 480 | 3 |
xxxhdpi | 640 | 4 |
Android 与 iOS 一样使用了密度独立性单位,只不过 Android 里不叫 points 而叫 dp。剩下的和 iOS 就很像了,假如设备 检测到自己的系统内置 DPI 是 480,那么渲染比例就是 3,也就是 1dp 对应 3 物理像素。
附注:
- Android 系统允许在各手机厂商在定制 ROM 中指定设备的 DPI。这样有一点好处是,对于类似小米 MAX 这种屏幕变大了但是分辨率没有跟随提升的设备,厂商可以指定一个合适的 DPI 来达到屏幕越大,显示内容越多这个目的。
- 我们说过 Android 的基本设计理念是使用户界面具有大致相同的物理尺寸,如果我们使用 dp 的话,除非屏幕密度和系统密度没有偏差,否则的话 dp 的物理尺寸会有一定的偏差。下表是屏幕密度和系统密度一一对应的情况:
系统密度 | px/dp | 物理尺寸 |
---|---|---|
ldpi – 120 dpi | 0.75 px/dp | 0.75 px / 120 dpi = 1/160 in |
mdpi – 160 dpi | 1.0 px/dp | 1.0 px / 160 dpi = 1/160 in |
hdpi – 240 dpi | 1.5 px/dp | 1.5 px / 240 dpi = 1/160 in |
xhdpi – 360 dpi | 2.0 px/dp | 2.0 px / 360 dpi = 1/160 in |
xxhdpi – 480 dpi | 3.0 px/dp | 3.0 px / 480 dpi = 1/160 in |
mdpi – 640 dpi | 4.0 px/dp | 4.0 px / 640 dpi = 1/160 in |
下表是屏幕密度和系统密度不完全一一对应的情况:
设备密度 | 系统密度 | px/dp | 物理尺寸 |
---|---|---|---|
232 dpi | hdpi – 240 dpi | 1.5 px/dp | 1.5 px / 232 dpi > 1/160 in |
263 dpi | hdpi – 240 dpi | 1.5 px/dp | 1.5 px / 232 dpi < 1/160 in |
314 dpi | xhdpi – 360 dpi | 2.0 px/dp | 2.0 px / 232 dpi > 1/160 in |
336 dpi | xhdpi – 360 dpi | 2.0 px/dp | 2.0 px / 232 dpi < 1/160 in |
可以看出,当设备 dpi 小于系统 dpi 时,每个像素在物理上尺寸上都更大,所以每个 “dp” 也变得更大,反之亦然。这就引出了一个问题:为什么 “dp” 允许这种物理尺寸的变化?有几个原因,一是 Android 牺牲了一些物理尺寸的精度,以保持 “dp” 利用 Android 的渲染比例(0.75:1.0:1.5:2.0:3.0)扩展为 “px”,从而避免复杂的分数可能会产生的伪像和锯齿,毕竟如果要保证物理尺寸的绝对相同,那么就不可避免地要出现诸如 3.14159 px/dp 这种情况。二是由于图片的缩放比例与密度桶比率成比例,因此“dp”将与为每个密度提供的图像资源成比例地呈现。
完
搬砖达人小王
2022年3月10日 — 下午5:20
写的太好了,
sunny
2019年7月30日 — 上午11:47
屏幕密度和系统密度物理尺寸书写有误吧,都是232 dpi