我们是如何记录图片的?

2022-12-07 08:27:53 浏览数 (1)

来自内部 「孙翛然」 分享,原文链接:https://scrapbook-cyansalt.vercel.app/how-we-record-pictures,这文章写的很细很有逻辑性,真把我看跪了。

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

起源

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

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

代码语言:javascript复制
 ██████╗ ██████╗ ███╗   ███╗███╗   ███╗ █████╗ ███████╗
██╔════╝██╔═══██╗████╗ ████║████╗ ████║██╔══██╗██╔════╝
██║     ██║   ██║██╔████╔██║██╔████╔██║███████║███████╗
██║     ██║   ██║██║╚██╔╝██║██║╚██╔╝██║██╔══██║╚════██║
╚██████╗╚██████╔╝██║ ╚═╝ ██║██║ ╚═╝ ██║██║  ██║███████║
 ╚═════╝ ╚═════╝ ╚═╝     ╚═╝╚═╝     ╚═╝╚═╝  ╚═╝╚══════╝

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

代码语言:javascript复制
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 视频编码。或许在不久之后,我们就能看到新的图片格式横空出世,并建立它的天下。

0 人点赞