我们是如何记录图片的?

语言: CN / TW / HK

作为 Web 开发者,我们日常需要与各式各样的图片格式打交道,以至于有些知识几乎已经成为常识,比如我们应该都知道 PNG 可以支持透明度,jpg 可以压缩到较低的质量,而 gif 则可以显示动图……但是,你知道这些不同的图片格式是如何产生的、并且演进至今的吗?

起源

最早的图片格式如今已经不可考证,但可以肯定的是,从计算机诞生之初,数字图片就已经存在了,正如早在没有计算机时我们就创造了“杨辉三角”这样的图形。不过在互联网诞生之后,由于信息(biao qing bao)传递的效率飞速提升,我们才有了更多机会看到数字图片格式的发展。

1982 年,随着网络社交的开始,互联网上开始出现了一种被称为 ASCII Art 的艺术形式。人们开始使用一些字符来构成一些复杂的图形:

██████╗ ██████╗ ███╗   ███╗███╗   ███╗ █████╗ ███████╗
██╔════╝██╔═══██╗████╗ ████║████╗ ████║██╔══██╗██╔════╝
██║     ██║   ██║██╔████╔██║██╔████╔██║███████║███████╗
██║     ██║   ██║██║╚██╔╝██║██║╚██╔╝██║██╔══██║╚════██║
╚██████╗╚██████╔╝██║ ╚═╝ ██║██║ ╚═╝ ██║██║  ██║███████║
 ╚═════╝ ╚═════╝ ╚═╝     ╚═╝╚═╝     ╚═╝╚═╝  ╚═╝╚══════╝

这实际上就是一种非常直观的计算机图片存储方式:在上面这张图里,我们可以把每一个单元格看成是显示屏幕的一个光学元件。当这些光学元件展示一张图片时,有些可能不发光(对应上面的空白部分),有些可能具有不同的颜色(对应上面的 █ 或者 ║)。通过这种方式,我们用一种非常符合计算机直觉的“编码”保存了这张图片——这里我不妨将其命名为 「Commas 编码」 吧。

通过 Commas 编码,「我们只需要保存一个二维数组,对应图中的不同位置,在数组的每一项记录对应的字符(颜色)」。这就是我们现在看到的绝大多数图片的保存方式:「点阵图」,或者用术语来描述:「位图」。

如果使用我们上面描述的方式保存这张图片,它的存储效率是怎样的呢?

没错,实际上上面的代码块就是图片文件本身!因为我们用了 Unicode 而非二进制的方式保存,它的像素数是 55*6,每一个像素需要用一个 Unicode 字符存储。假设我们使用 UCS-2 编码,则这张图片使用我们的编码方式需要 「660B」 的存储空间。

PNM: 色彩与二进制

在真实的计算机世界里,真的有像 Commas 编码这种图片格式吗?事实上还真有。在万维网还没有诞生的年代,就已经有了一种用于电子邮件传输的图片格式,它叫做 PBM(Portable BitMap)。在现在,它使用 .pbm​ 后缀和 image/x‑portable‑bitmap MIME 类型。

PBM 是一种单色图片,这意味着它只有黑色和白色两种颜色,在游戏美术中这通常被称为 1-bit。在存储时,我们可以使用 0 和 1 代表黑白两种颜色。比如上面的 COMMAS 图片,如果用 PBM 它的编码方式如下:

P1
# This is comment
55 6
0 1 1 1 1 1 1 0 0 1 1 1 1 1 1 0 0 1 1 1 0 0 0 0 1 1 1 0 1 1 1 0 0 0 0 1 1 1 0 0 1 1 1 1 1 0 0 1 1 1 1 1 1 1 0
1 1 0 0 0 0 0 0 1 1 0 0 0 0 1 1 0 1 1 1 1 0 0 1 1 1 1 0 1 1 1 1 0 0 1 1 1 1 0 1 1 0 0 0 1 1 0 1 1 0 0 0 0 0 0
1 1 0 0 0 0 0 0 1 1 0 0 0 0 1 1 0 1 1 0 1 1 1 1 0 1 1 0 1 1 0 1 1 1 1 0 1 1 0 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 0
1 1 0 0 0 0 0 0 1 1 0 0 0 0 1 1 0 1 1 0 0 1 1 0 0 1 1 0 1 1 0 0 1 1 0 0 1 1 0 1 1 0 0 0 1 1 0 0 0 0 0 0 1 1 0
0 1 1 1 1 1 1 0 0 1 1 1 1 1 1 0 0 1 1 0 0 0 0 0 0 1 1 0 1 1 0 0 0 0 0 0 1 1 0 1 1 0 0 0 1 1 0 1 1 1 1 1 1 1 0

你可以将它保存为一个 `.pbm` 文件,并利用 MacOS 的图片查看器查看它。

比起 Commas 编码,PBM 失去了表达颜色的能力,但多了注释结构。不过单色显然是无法满足我们发送表情包的需要的,至少连黑白电视也是支持不同亮度的黑色的。为了让 PBM 支持灰度,我们可以将 0 和 1 扩展为 0-255,这就是 PGM;更进一步地,我们还可以用 255 255 255 来将每个像素扩展为真彩色,也就是我们熟悉的 RGB 编码,这就是 PPM。在 PBM 开头的 P1 就起到了描述编码的作用,P1 P2 和 P3 分别代表 PBM、PGM 和 PPM 三种格式,他们被统称为 PNM。

现在,我们来考虑一下存储空间吧。PBM 使用 ASCII 明文存储,上面的图片文本长度是 575,因此它占用 575B。当然我们还可以进一步压缩,在去除注释后,它的长度是 557B,但也仅限于此了;如果使用支持色彩的 PPM,每个像素可能要占 11 个文本长度,于是一张简单的 55*6 的 COMMAS Logo 图需要使用 「3,857B」 来保存!

有什么方法可以进一步压缩图片呢?聪明的你一定可以想到,我们可以使用二进制。PBM 的编码可以使用 P4-P6 表示它使用二进制保存。于是我们可以用两个字节表示编码,两个 Int32 也就是 8 个字节表示尺寸,后面的 330 个像素可以将每一个真彩色转换成 3 个字节的色彩空间,这样我们一共只需要 1,000B 就能够存储上面的 COMMAS Logo 图了,而且还是支持真彩色的!

GIF: 空间与编码压缩

在 20 世纪 80 年代,1K 的尺寸依然是无法想象的 —— 只是一张小小的 Logo,竟然比 Commas 的使用文档还要大!有什么办法能够继续优化尺寸呢?

在那个年代,在我们甚至很难购买到支持真彩色的显示屏的时候,使用真彩色编码图片无疑是一种浪费。我们也可以缩小色彩空间来针对每个像素减少其占用的空间。在 1-bit 之外,同样有一种常见于像素游戏的画风叫做 8-bit,意思就是用 8bit 也就是 1 个字节来保存颜色,我们只需要将颜色限制为 256 种,就能将用于色彩的存储进一步压缩。假设我们将 PPM 的 Commas Logo 缩小到 256 种颜色的范围,每一个像素就能用 1 个字节来保存了,这样上面的 1,000B 的 COMMAS Logo 图就能压缩到 340B,甚至比文本格式的 PBM 图还要小!

玻璃的光影只需要使用四种颜色即可完成。

另一方面,学过数据结构的同学能够想到一种常见的压缩方式:「霍夫曼编码」。简单地来说就是我们可以记录一份字典,用更小的比特序列来记录更常出现的字符。比如 Commas,如果用 ASCII 的话,每一个字母都要 1 个字节也就是 8bit,但如果用 1 表示 m,10 表示 c,11 表示 o……我们只需要 12 bit 就能表达这个字符串了!只要字典占用的空间小于压缩减少的空间,霍夫曼编码就能有效减少文件尺寸。

沿着这样的思路,一种主流的图片格式终于诞生了,它就是 GIF。尽管我们现在提起 GIF 就会想到动图,但实际上,GIF 是最早流行起来的图片的编码方式。GIF 的全称是 「Graphics Interchange Format」,也就是“图像交换格式”的意思,显然它就是为了高效传输而诞生的。

GIF 使用了一种叫做 LZW 的压缩技术,它与几乎所有现代压缩软件师出同源。比起标准的霍夫曼编码,它的字典是动态生成的,因此需要传输的尺寸将会进一步减小。所以 GIF 就相当于用压缩文件来编码图片,这能不厉害嘛!

另外,在推出后不久,Netscape Navigator 支持了 GIF 的多帧动画,以及定义动画重复能力的功能——这下表情包王者们发挥的空间来了!GIF 很快风靡互联网,包含 Netscape 定义的动画格式变成了事实标准。

那么,为什么现在除了动图之外,我们都不再使用 GIF 了呢?一方面是由于 GIF 仅支持 256 种颜色,对于摄影图片来说这几乎是无法忍受的;另一方面则是因为一家公司的骚操作:

GIF 是历史上首个提供电子邮件服务的公司 CompuServe 开发并开放给大家使用的,但是另一家公司优利系统却声称自己申请了 「LZW 中 W 的专利」 ,需要大家交纳高额的专利费……于是很快,开源社区开始抵制 GIF 并开始开发新的图片格式。因此尽管 2006 年开始 GIF 不再被专利限制,一个巨人已经悄然站在了 GIF 的前面,它就是—— PNG。

PNG: 开放

PNG 的全称是 「Portable Network Graphics」,即“便携式网络图片”。这意味着从设计之初它就面向:

  • 面向 Web 展示场景。
  • 更小的体积。
  • 取代 GIF。实际上,确实如此!

PNG 的诞生离不开 GIF 的作死,正如 PNG 也被解释为 「PNG is Not GIF」……PNG 从诞生之初就是完全无需许可的,由 IETF 作为 RFC 2083 发布,正如我们所知道的很多互联网基础协议一样(例如 RFC 791 IP、RFC 1034 DNS、RFC 2616 HTTP/1.1~~ 等等),并在发布后不久就成为了 W3C 的推荐标准。PNG 能够取代 GIF 是一件很容易理解的事情:毕竟,谁不喜欢白嫖呢?更何况 PNG 完美解决了 GIF 的另一个痛点:PNG 支持真彩色!!!

其次,PNG 还有一个很大的特点:「向前兼容」。

PNG 格式与 PNM 和 GIF 类似,都由一个协议头+多个内容块构成,但 PNG 在标准制订时就规定了块可以分为两类:「关键数据块」 和 「辅助数据块」。PNG 解析器必须支持关键数据块的解析,而对于辅助数据块则是能识别就识别,不能识别可以忽略。

这个特性有什么用呢?我们可以重新回顾一下,我们会在什么场景下使用 GIF 而不是 PNG 呢?显然,最常见的场景就是上面所说的动图。在 2004 年,Mozilla 推出了 APNG 格式。APNG 将动画的第一帧作为 PNG 的关键数据块,而后续帧和其他动画信息则作为辅助数据块,这样即使是在不支持 APNG 的场景下,这些图片也能展示为静态的图片了。

注意哦,这是一张 PNG 格式的图!

另一个你可能感兴趣的问题或许是,我们经常使用 TinyPNG 这样的工具来对 PNG 做压缩,那么一张 PNG 图片是如何被压缩的呢?尤其是在我们已经知道,GIF 为其压缩算法保留了专利的情况下?

实际上 PNG 和 GIF 的算法非常相似。前面我们提到,GIF 为 LZW 中的 W 部分声明了专利,而剩下的 LZ 部分实际上就是 LZW 的原始算法——LZ77,它来自于名字首字母 L 和 Z 的两位大佬在 1977 年提出的压缩算法。PNG 的压缩正是基于 LZ77 的另一种算法:DEFLATE,这也正是 Web 领域常见的 GZIP 的压缩算法。尽管细节不同,但基于动态字典的思路与 GIF 是类似的,这也保证了 PNG 具有高效的压缩效率。

JPEG: 有损压缩

事情已经发展到了这一阶段:PNG 几乎就要一统江湖了,但是为什么没有呢?最主要的原因依然是 LZ77——它的压缩效率相比原始数据已经很高了,但是还远远不够。像 LZ77 这样的压缩算法与霍夫曼编码类似:数据多样性越差,压缩效率就越高。这意味着对于早期互联网上的那种剪贴画,PNG 是很有优势的;但随着图像复杂程度升高,PNG 能够压缩的空间也越来越有限了。有什么办法能够压缩那些复杂的图片,例如摄影作品呢?

JPEG 就是这样诞生的。JPEG 全称 「Joint Photographic Experts Group」,也就是“联合图像专家组”的意思,我们通常所说的 JPEG 就是这个专家组提出的第一种编码标准,在 JPEG 之后实际上还有 JPEG 2000 这样更高级的标准,但并没有能够撼动 JPEG 的地位(后文会提到)。事实上,JPEG 比 PNG 的出现还要更早,但比起 GIF 粗暴的 256 色化和 PNG 偏执的无损压缩,JPEG 采取了一种更实用主义的策略:「面向人眼识别的有损压缩」。

不同于我们在 PNM 中使用的 RGB 色彩空间,JPEG 使用的是一种叫做 YUV 的色彩空间。YUV 三个字母分别代表亮度、色调和饱和度,这和现代更流行的 HSL 很接近。正如设计师们推崇 HSL 的主要原因是 HSL 更接近于人眼对色彩的感受方式,因此更适合做渐变处理,YUV 也被广泛应用电视色彩调频领域,原因就在于:「人眼对于亮度的感受要高于色调和饱和度」。你可以试试开启或关闭手机的夜览模式和原彩显示,再试试调整亮度,就会发现,我们对亮度的敏感程度是非常高的。

不仅如此,「人眼对于在一定范围内的亮度差异较为敏感」,例如我们会明显感觉到 10% 和 20% 比 20% 和 25% 更接近,但却难以区分 10% 和 90% 与 20% 和 80% 两种对比的差异。通过一些矩阵变换的技巧,我们可以将频域上的一些细节舍弃,这个过程被称为「量化」。这是 JPEG 有损压缩的最主要来源,通常 JPEG 压缩时可以选择压缩质量,影响的就是量化过程的系数。

从上面这张图我们就可以看出来,一些亮度变化明显的部分是 JPEG 损失最多的地方,例如鸟喙周围会有明显的噪点。尽管如此,对于我们通常看到的图片,JPEG 都能在合理地保证质量的前提下大幅压缩图像的尺寸,这尤其体现在一些「绘画作品」中,因为这些作品通常都有相对均匀的亮度。

然而,专利问题在 JPEG 身上依然未能幸免,2002 年一家公司宣称自己拥有 JPEG 的专利,这在 PC 领域引发轩然大波,这与 JPEG 专家组创立之初期望的免版税标准背道而驰。此案经过 4 年的扯皮,最终以庭外和解的方式解决了。此事件同时暴露了 JPEG 2000 存在的专利风险,导致尽管 JPEG 2000 在技术上更加先进,却最终没有流行起来。

WebP: 金声玉振

数据压缩是一个非常经典的信息学问题。我们之所以能够压缩数据,往往是因为这些数据「本身存在冗余信息」。例如霍夫曼编码就利用了不同的数据出现的频率不一致,这就是一种统计学冗余。但不论如何,我们都没有办法在 1bit 里面传递 2bit 的信息,因为信息也需要受到熵的限制,

截至此时,也许我们已经把所有能够使用的方式都穷尽了:我们尝试了二进制编码、压缩算法,甚至针对人眼的观感放弃了图像的一部分信息……但是,真的结束了吗?

开动我们的脑筋仔细想想,会不会有一种可能,我们可以将图片信息中更多的部分「变成冗余」呢?

让我们把视线从图片转向其他领域。有一个看起来和 JPEG 很有关系的格式是 MPEG,但是实际上二者的关系就和张飞与王菲差不多。但正如 JPEG 是专家组的名称,MPEG 实际上也是如此,它是一个提出了音视频领域多种编码标准的组织。我们通常所说的 MPEG 实际上是 MPEG-1,是一种音频标准,它最经典的应用是 MPEG-1 第 3 音频层,俗称 MP3。而 MPEG 发布的最具影响力的标准当属 MPEG-4,其中包含了 27 个子部分。每一个部分分别规定的视频的编码、控制、示例、优化方式等等。我们最熟悉的应当是第 14 部分,俗称 MP4。

在 MPEG-4 中,视频编码的部分是由第二部分(XVID)和第十部分(AVC)组成的,其中 AVC 更加高级;同时,由于 AVC 是 MPEG 和国际电信联盟共同制定的,因此这个规范在国际电信联盟也有一个别名,它就是 「H.264」。

H.264 通过只记录帧与帧之间的变化,得以将视频大幅压缩,也就是我们通常所说的 「运动补偿」。显然比起 GIF 对图片的每帧都进行保存,这种方式的存储效率要高出几个数量级!除了运动补偿外,还有一些高级的压缩手段,例如“「帧内预测」”。这意味着,在解码器对某一帧进行解码时,「可以根据某个单元相邻的单元来预测该单元的值,从而使视频文件只需要记录实际值和预测值的差值就足够了」。由于其极高的压缩效率,H.264 很快统治了视频编码领域。

但是,正如 GIF 和 JPEG 面对的一样,MPEG 的标准实际上是包含专利的。因此,很多公司试图开发能够代替 H.264 的视频编码。Google 在 2010 年收购的一家叫做 On2 的公司就是做这件事情的。收购完成不久后,Google 就在当年的 Google I/O 上宣布开源 On2 的最新视频编码 VP8,为什么呢?因为与此同时,Google 推出了一个新的开源视频格式,也就是本章节的主角的兄弟——WebM。

我们现在知道,WebM 实际上并未掀起多大波澜,但在当时仍然是划时代般的进步,尤其是在半年后,Google 基于 VP8 技术再次给出了新的图片格式 WebP 的时候。在经历了那么多版权和专利纠纷后,Web 领域终于又有了「开源+免版税」的新格式,并且还是图片+视频打包的!

正是因为 WebP 技术是从 VP8 衍生而来的,因此它继承了视频领域的有损压缩手段,这就是前面提到的 「帧内预测」。WebP 会使用每个块上方的三个块和左侧的三个块进行预测,并且包含了 H.264 的四种帧内预测模式,这也就是 WebP 能够做到比 PNG 更小的原因——通过帧内预测实现了更多原始信息的冗余化。在此基础之上,WebP 同样使用了字典编码等等无损压缩技术,从而使图片的尺寸降到了尽可能低的程度。

展望

WebP 已经是图片压缩的终结了吗?当然不!

在 WebP 诞生之初实际上不支持无损压缩和透明通道,并且它的有损压缩甚至不如 JPEG;但正是因为开源的力量,让 WebP 逐渐成为了目前最具优势的主流 Web 图片格式。那么现在,除了 WebP 我们还有哪些选择呢?

如果说有哪种格式表现超越 WebP,BPG 一定是其中的一个。前面我们提到,WebP 的实现来自于 VP8,而 VP8 又来自于 H.264。现在你也许知道,H.264 已经不足以满足如今大量的 4k、8k 甚至更高的显示需要,因此 2013 年的时候国际电信联盟发布了新的视频标准 H.265,学名叫做 HEVC —— 「High Efficiency Video Coding」,高效视频编码。HEVC 在 H.264 之上做了诸多改进,例如帧内预测就从 4 种模式上升到了 33 种!

BPG 使用了基于 HEVC 帧内预测算法的有损压缩,这意味着它的性能要显著领先于 JPEG 甚至 WebP。然而,由于 HEVC 与 H.264 一样保留版权,这也成为了 BPG 并没有大规模流行的主要原因。

在无损压缩领域有没有呢?显然也是有的,FLIF(https://github.com/FLIF-hub/FLIF) 就是其中的典型。FLIF 是 2015 年发布的,并且也是完全开源的。FLIF 采用和 H.264 类似的一种动态学习的压缩算法,在无损压缩方面将压缩性能优化到了新高度。但作为一个纯社区项目,FLIF 在发布后不久就逐渐式微,但其核心思路被 JPEG 的新标准 JPEG XL 继承,并且 JPEG XL 最终也在 2020 年作为免版税标准发布;BPG 和 FLIF 也最终启发了 MPEG,发布了基于 HEVC 的图片格式 HEIF。

WebP 的后现代艺术

最后

故事的最后则重新回到我们的卷王 Google 身上。在 VP8 对标 H.264 而国际电信联盟发布了 H.265 之后,Google 推出了 VP9 来对标 H.265;尽管不及预期,但最终促成了 AOMedia(开放标准联盟,由 Google、苹果、微软、Facebook、Mozilla 等创建)的成立。基于 VP10 的 AV1 正是 AOMedia 开发的用以代替 H.265 的最新免版税标准。

2021 年起 Google 开始开发下一代 WebP,被称为 WebP 2,便是基于 AV1 视频编码。或许在不久之后,我们就能看到新的图片格式横空出世,并建立它的天下。