五面腾讯后,我悟到安卓中图片的正确打开方式

96 阅读18分钟

经历了腾讯的五轮面试,问到了较多音视频和图片的例子,因此决定把一些难搜集到的知识点都总结成文章。

本篇文章要写的是图片的压缩,在腾讯的五面中,问到我Webp的压缩比率为什么比PNG更大,这里涉及不同图片格式的压缩方式。一般人都知道,PNG是无损压缩,JPG是有损压缩,Webp是谷歌研发的压缩格式,其图片大小比PNG小20-30%。矢量图则是无损压缩的格式,无论放多大清晰度都一样,其大小一般也小于前面所有格式。但是面试的时候会问到他们的原理,如果是不了解的网友可以从中入门,了解的网友也可以指出我的错误。

一些更基础的知识点,诸如:图片原始数据的组成(位深度、像素等),就不一一介绍了,可以轻松找到对应的文章,还请见谅。

本篇文章分为以下几个部分:

  1. PNG、JPG、Webp的压缩方式区别,为什么JPG有损?为什么Webp的压缩率更高?
  2. Webp和SVG分别是如何描述图片的?SVG能描述所有图片吗?
  3. Android中资源文件的图片是如何索引的?为什么无法直接像PNG一样索引SVG?
  4. Android中资源图片的选择,如何选择格式以减小包体积?

1. PNG、JPG、Webp的压缩方式区别,为什么JPG有损?为什么Webp的压缩率更高?

PNG、JPG、Webp的压缩方式区别

PNG的压缩

png以块来组织数据,每一块的数据分布如下:

  1. Length(块长度):4个字节标识数据块的长度
  2. Chunk Type(块类型):4个字节标识数据块类型,每个字节是一个ASCII码
  3. Chunk Data(块数据):若干个数据,这一部分的总长度在1. 块长度中指定了
  4. CRC(循环冗余检测块):4个字节,用于检测是否存在错误冗余码,不了解CRC的可以到计算机网络的书上或者网上技术博客搜一下,CRC可以检测数据块是否出错,并存在一定程度的纠错能力。注意PNG的CRC只对块类型进行计算,且多项式固定为0xedb88320,这是CRC的32位标准多项式,被广泛应用。

块的具体类型如下:

2c510538f14a0d1c0fa6e2021bc5fe28.png

此图片来源:PNG编解码算法详解_png编码原理-CSDN博客

其中主要用到标红色部分,头块、数据块和尾块

每个PNG图片包含一个固定的签名,头块和尾块:

  1. 签名不是一个正常的块,不像常规块的分布,他没有长度,也没有块名,就是固定的八个字节用于标识本文件是PNG类型。这八个字节是0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A,AI解释如下:第一个字符是非ASCII字符,用于跟其他文档格式区分开来,因为如果是文档的话一般不会出现ASCII字符。其后的0x50, 0x4E, 0x47是PNG的ASCII码,然后0x0D, 0x0A是\r\n,0x1A是是ASCII的'SUB'字符,早期文本文件用这个标识文件的结束,PNG沿用了这个习惯。

  2. 头块IHDR,13个字节,包含如下内容:

69e0b8c65d4e9dc89bf11ada3d2146f5.png

此图片来源:PNG编解码算法详解_png编码原理-CSDN博客

这里对一些疑点解析:

   1.  ColorType中索引彩色图像是什么?

    一般我们图像是3-4通道,3通道是rgb,第4个通道是透明度Alpha通道,每个通道有0-255的值,这是真彩色图像,而索引彩色图像则是每个像素索引到调色板上,调色板相当于一个map,指定了第一个位置是某个颜色,第二个位置是另一个颜色等,而图片的像素则直接索引到调色板上。

    这么做的好处,我猜测是对于一些范围比较小的像素,直接通过调色板指定,比如只包含0和255的图片,调色板把255放在第一个索引,然后图片的实际数据中就只包含0和1了,有利于更好地压缩。

   2.  Filter method五种滤波方法

    (1)None(2)Sub(3)Up(4)Average(5)Paeth。本文后面会给出详细的解析。

   3.  隔行扫描

    隔行扫描是在显示图像的时候,先隔行扫描,显示部分图像,比如先扫描奇数行/偶数行,把这些行先展示,然后再继续扫描把剩下的部分展示。这么做的好处是在图像未加载完的时候可以让图像先展示大致轮廓,优化观看者的体验,这样分阶段加载图像的方式,也能降低对带宽、解码速度的要求。

   4.  Deflate和Inflate

    Deflate表示压缩,Inflate表示解压缩,Deflate依次使用LZ77和Huffman编码,Inflate则相反。本文后面会给出解析。

3. 尾块IEND是正常的块,一般情况如下:00 00 00 00 49 45 4E 44 AE 42 60 82,前面四个字节代表长度为0,一般尾部是不携带数据的,故长度为0。中间四个字节分别是IEND的ASCII字节码,从这里可以看出块类型的四个字节其实就是把对应类型的字母字节码作为块类型。最后的四个字节是纠错码,由于尾块类型就是IEND,故纠错码固定为AE 42 60 82。

PNG的Filter

Filter指的是差分编码,对原始数据进行差分编码后,数据以向量数据的差值的形式表示。具体的差分方法如下五种:

None(无过滤器):原始数据直接输入,没有差分编码。

Sub(子过滤器):每个像素值与其左侧像素的差值。

Up(上过滤器):每个像素值与其上方像素的差值。

Average(平均过滤器):每个像素值与左侧像素和上方像素的平均值的差值。

Paeth(Paeth过滤器):基于左侧、上方和左上像素计算一个预测值,然后取差值。

下面是GPT给出一个示例:

image-20250222150230294.png

从中看出,经过差分编码后数据可能变化范围更小,数据的变化也可能更小了。如100-150的原始数据,变成100和4个10,对于规律的数据,我们是否可以压缩呢,这是肯定的,这是PNG的Deflate压缩部分的工作,而Filter部分则是对PNG的原始数据转换,以使得数据变化更规律、范围更小,便于后续压缩得更小。

PNG的Deflate

PNG的压缩需要先经过Filter差分变化,得到的数据的变化范围可能较小,或变化较规律。然后使用LZ77和Huffman编码来进一步压缩。下面简略介绍这两种编码:

  1. LZ77

LZ77通过使用一个字典来查找和替换重复的字节序列。在图像数据中,相邻的像素或经过过滤处理后的数据往往具有相似性,因此会出现很多重复的字节序列。LZ77通过查找重复的字节并用指向字典中该字节序列的指针来代替,极大地减少了数据量。

  • 字典查找:LZ77会在数据流中查找重复的字节串,并使用一对 (偏移量, 长度) 来表示这些重复字节。偏移量 是重复字节在字典中的位置,长度 是重复字节的长度。
  • 字典存储:重复的字节序列会被替换为对字典中该字节序列的引用(偏移量和长度),从而节省存储空间。

例如在索引100位置处重复出现了123123123123...,就可以在字典中记录下123,100,n,可以极大减少数据量。

  1. 哈夫曼(霍夫曼)编码(Huffman、Hoffman)

哈夫曼编码会将最常出现的字节或符号用更短的编码表示,而出现较少的字节或符号用较长的编码表示。这样,常见的数据序列就会变得更加紧凑,进一步提高压缩效率。

  • 频率表:首先,统计数据中每个字节或符号出现的频率。
  • 生成哈夫曼树:根据频率表构建哈夫曼树,使得频率高的符号在树中的路径较短。
  • 编码:通过哈夫曼树生成每个字节的哈夫曼编码,使用较短的编码来表示频繁出现的字节。

这个就比较常见了,具体可以搜索。

到这里,大家就能理解为什么PNG能对图片进行压缩了。了解了PNG编码原理,或许在了解每一部分的算法后就可以手写PNG编解码器了,这个有空会做的。。。

那么我们是否能对PNG进行优化呢?在PNG的基础上可以使用调色板,也可以自定义一种图片格式,使用效果更好的自定义Filter,还可以对Deflate算法优化或替换为压缩更好的其他算法。于是便应运而生其他图片压缩算法。

JPG、JPEG的压缩

JPG和JPEG的关系

JPEG和JPG的关系

JPEG全称Joint Photographic Experts Group,中文名为联合图像专家组,是一个研究图像的组织,JPEG是该组织提出的标准,而JPG是该标准下的一种图像压缩格式。不那么专业地说,JPG是JPEG的儿子~

JPG的目的是,以尽可能高的压缩率达到尽可能人眼难以分辨的画质下降,换句话说,比起PNG,JPG不关注压缩的无损性,更关注压缩后是否对人的感知造成影响。JPG提出以下观点:

  1. 人的感知中,图片是分块的,一小块以内的区域清晰度变化对整体清晰度的感知影响不大
  2. 人眼对亮度变化的感知比色彩变化更敏感

故JPG把图片从原来的RGB三色通道,改为YCrCb三通道,其中Y代表亮度通道,Cr、Cb分别代表红色通道和蓝色通道的差分量。JPG不会对Y处理,而是对CrCb通道压缩,常见的压缩比率有4:4:4,4:2:2,4:2:0(最常用),其中:

  1. 4:4:4代表不做缩减
  1. 4:2:2代表在水平方向上对于Cr和Cb每2个像素做缩减,也就是水平方向上每2个像素共享一个Cr和Cb值
  1. 4:2:0代表在水平和垂直方向对Cb、Cr每2个像素做缩减,也就是水平垂直方向上4个像素共享一个Cr和Cb值
压缩流程

其压缩流程分为以下步骤:

  1. 下采样:对原图的Cr、Cb通道进行下采样以缩减这两个通道的规模,通常以4:2:0的比率下采样

  2. DCT变换:对原图像素做离散余弦变换,将数据转换为频域,通过DCT算法(以下公式)转换

{\displaystyle \ G_{u,v}={\frac {1}{4}}\alpha (u)\alpha (v)\sum _{x=0}^{7}\sum _{y=0}^{7}g_{x,y}\cos \left[{\frac {(2x+1)u\pi }{16}}\right]\cos \left[{\frac {(2y+1)v\pi }{16}}\right]}

转换后,8*8矩阵的左上角是频率较低的值,右下角是频率较高的值

  1. 量化:转换为频域后会有浮点数,同时其存在负数,要将其转换到0-255的整数域,使用预矩阵,将8*8的矩阵一一除以矩阵对应位置的值并四舍五入。由于人眼对低频区的辨识度更强(因为低频区更简单、面积大,比如一个图片的纯色块),因此预矩阵处理后要让高频区的数据尽可能减小,低频区的数据尽可能保留。如下图预矩阵Q,预矩阵处理后的矩阵B

{\displaystyle Q={\begin{bmatrix}16&11&10&16&24&40&51&61\\12&12&14&19&26&58&60&55\\14&13&16&24&40&57&69&56\\14&17&22&29&51&87&80&62\\18&22&37&56&68&109&103&77\\24&35&55&64&81&104&113&92\\49&64&78&87&103&121&120&101\\72&92&95&98&112&100&103&99\end{bmatrix}}.}

{\displaystyle B=\left[{\begin{array}{rrrrrrrr}-26&-3&-6&2&2&-1&0&0\\0&-2&-4&1&1&0&0&0\\-3&1&5&-1&-1&0&0&0\\-3&1&2&-1&0&0&0&0\\1&0&0&0&0&0&0&0\\0&0&0&0&0&0&0&0\\0&0&0&0&0&0&0&0\\0&0&0&0&0&0&0&0\end{array}}\right].}

  1. 熵编码:现在一块区域中大部分面积都变成了0或较低的数值,就可以进行编码以压缩体积了,可以使用Huffman编码。JPG也有做这种算法:从左上角以蛇形遍历B,遍历后大面积的0转换为

220px-JPEG_ZigZag.svg.png

image-20250223122853547.png

其中最后连续的0编码为EOB,就压缩了大部分数据了。

质量参数(为什么JPG有损)

JPG有一个概念叫质量参数,在使用JPG进行压缩的时候可以选择压缩的质量0-100,质量参数越高,JPG图片的质量越高,压缩后大小也越大,可以根据实际情景选择不同压缩质量。

质量参数实际上影响的是量化部分预矩阵的计算,会根据不同的质量参数计算出不同的预矩阵,来间接决定最后的图片质量。

值得一提的是,鉴于DCT的计算总会有浮点数产生,而量化部分不可避免进行舍入,因此JPG总是有损的压缩,即使选择的质量是100。

Webp的压缩(为什么Webp的压缩率更高)

这部分算法知识过于庞大,本处进行概括,并给出详细解析的地址,有兴趣的网友可以自行观看学习。等我有时间去详细学习后再出一篇文章讲讲。

WebP是如何帮助你的应用/网页减少图片大小的? -- WebP压缩原理简述WebP是如何帮助你的应用/网页减少图片大小 - 掘金

压缩技术 | WebP | Google for Developers

Webp结合了PNG和JPG算法,使用YUV的格式,支持有损压缩和无损压缩。

  1. 分块处理和预测

同样对图片分块处理,但是每一块根据图片的细节丰富程度自适应块大小,分为4X4,8X8,16X16块。不同的块之间会通过预测算法进行计算,在丰富的区域(4X4大小),预测方法多达9种,在细节不那么丰富的区域(16X16大小),预测方法则只有4种。

  1. 颜色索引

借鉴了PNG,对于唯一颜色值出现较少的图片,使用调色板颜色索引,同样使用LZ77算法减少冗余编码。

更多细节详见上方链接。由于预测算法和分块编码的使用,Webp的大小要比PNG小得多,使用Alpha有损压缩的Webp,其大小通常比透明的PNG减小69%,这是很惊人的。

2. Webp和SVG分别是如何描述图片的?SVG能描述所有图片吗?

上面提到了Webp的编码,来看看SVG,以下是一个SVG的图片及其文件实际内容。

image-20250223131856903.png

image-20250223131743314.png

可以看到SVG是使用XML描述的,包含类似circle、line这样的元素,使用过canvas的会挺熟悉,显然SVG的原理是给出图片的描述信息,把绘制图片的任务交给使用方,由使用方根据实际大小来决定绘制的大小、颜色。一般来说,常见的图片都用SVG直接绘制,在一些设计软件Figma就可以直接生成。

SVG是否可以描述复杂的图片?如果可以,描述复杂的图片的时候其大小跟图片压缩格式相比有优势吗?

我这里没有查阅资料,从朴素的对图片编码的理解即可思考出结果,并且我自信地认为结果一定对。

SVG可以描述复杂的图片,可以描述任意图片

SVG的每个元素可以单独控制颜色和位置,如此,我们可以用一个个大小为1的元素去构筑图片的某一个像素点,直到构筑出整个图片。理论上来说SVG可以描述任何图片。

SVG描述复杂图片的时候其大小比绝大多数图片压缩格式都大

常见的压缩格式中,图片原始数据三通道,每个通道用一个字节表示,一个点的大小为3字节,但是用SVG描述一个点的话,既要描述点的位置,还要描述颜色,每个文字都占至少一个字节。除非图片绝大部分区域由同色的规则区域组成,这样SVG可以用一个元素描述很大的区域,否则SVG描述的每个点都比原始数据的大小大,更不用提后续压缩格式还会进一步优化大小。

即使在渐变的场景下,SVG的大小也未必比普通图片小,因为SVG本身不支持渐变。

3. Android中资源文件的图片是如何索引的?为什么无法直接像PNG一样索引SVG?

Android的资源文件如何索引

资源文件在打包后都在res文件夹的不同分级文件夹下,会生成一个R类,使用生成的R类快速索引到资源文件的位置。比如一个图片,R.drawable.cat,就生成了一个类R,包含字段drawble,drawble进一步包含字段cat,通过这样的字段可以迅速索引到某个文件。

为什么不能像PNG一样索引SVG

要使用SVG我们需要在drawable文件夹中导入SVG为vector xml

image-20250223133719812.png 这是因为绘制SVG要托管给使用方,在安卓中的绘制引擎对xml的支持本身就有矢量图,干脆就把SVG转化为它的xml格式,而不单独设置SVG的绘制了。转换的过程中,由于安卓原生xml和SVG的xml元素描述格式不同,因此会进行一些修改。

PNG直接存储为图片数据,使用的时候不需要使用引擎渲染,通过索引到文件的位置,直接解析使用即可。而如果SVG导入后,索引到后没有对应的解析方式,因此直接像drawble的资源一样导入SVG格式的图片是无法看见的。

4. Android中资源图片的选择,如何选择格式以减小包体积?

SVG的使用场景

首先把SVG和[PNG、JPG、Webp]区分开来。

考虑SVG在描述简单图片的大小极小,故在一些规则的纯色icon使用SVG。

图片压缩格式的使用场景

无损

首先PNG和Webp支持无损,显然在一些用户需要放大查看细节的场景,比如一些场馆、遗迹、艺术品的实景预览图,用户可能要用一个安卓屏幕放大几倍,使用JPG是不太恰当的

透明度

JPG不支持透明度,如果在堆叠场景下,比如多个图片需要在一起堆叠显示,不建议使用JPG,建议使用PNG和Webp

机器性能

在一些及其老旧的安卓机,可能不适配矢量图格式,遇到这种场景需要注意适配。

同时解压缩的性能要求,Webp>JPG>PNG,Webp的解码消耗可能是PNG的8倍。在海量图片同步加载的场景下,比如相册,此时需要充分考虑机器性能来选择压缩格式,在解码消耗和图片大小之间找到最佳平衡点。

更详细的考虑,可以参考这篇文章:Web 开发必知必会:WebP、JPG、PNG 和 SVG-CSDN博客

参考资料

PNG编解码算法详解_png编码原理-CSDN博客

JPG的压缩原理_jpg压缩原理-CSDN博客

JPEG - Wikipedia

WebP是如何帮助你的应用/网页减少图片大小的? -- WebP压缩原理简述WebP是如何帮助你的应用/网页减少图片大小 - 掘金

压缩技术 | WebP | Google for Developers

[译] 深入浅出 SVGSVG 是优秀且令人难以置信的强大图像格式。本教程通过简单地解释所有需要了解的知识,为您提供 S - 掘金

Web 开发必知必会:WebP、JPG、PNG 和 SVG-CSDN博客

GPT4-mini