图片格式转换与压缩的一点理解
本想鸽掉这一篇,因为实在没啥好讲的。然后过了几天想想还是随便记点吧。
1. WEBP
谷歌推出的图片压缩格式,同时支持有损和无损压缩,通常比同品质的jpeg,png,gif小,已经得到主流浏览器的普遍支持。因此把自家网站的图片换成webp成了一个趋势。
一个不错的方案是,提供图片资源的服务器检测客户端的ua是否支持webp,对于支持的客户端重写url为对应的webp资源。通过cdn部署的话可以通过类似cloudfront的lambda钩子来实现。
- 命令行工具
gmagick/imagick
- python库
Pillow
- nodejs库
sharp
/gm
- ...
一般来说,各个语言的图片处理库都会支持webp格式转换,不过要想得到比较好的体验,还是需要研究一下jpg,png的特点来调整对应的参数(如果支持接收参数的话)。转换的初衷肯定是因为webp更小,如果遇到下面问题就得不偿失了。
- jpeg转webp后更大了?
- png转webp后变大了?
2. JPEG
jpeg曾经是一个非常厉害的有损压缩格式,大致原理可以通过这个视频了解:
JPEG不可思议的压缩率——归功于信号处理理论
ps: 评论区也很精彩
大概总结下就是:
- 利用人眼对亮度和色彩的不同敏感特点,将
RGB
转为YCbCr
,然后进行下采样 - 采用
离散余弦变换 DCT
进行维度压缩。虽然说这属于信号处理范畴(当然也是数学),但是好像特别眼熟:- PCA主成分分析。筛选特征有木有--尽量保持图像的品质;数据降维有木有--用更少的比特表示
- VAE自编码器。高维特征压缩成低维向量,再还原成高维的特征,尽量表现得跟原来一样
- 延申一下,为啥是余弦?因为
cos(0)=1
,这样才能作为空间变换的基底。
一旦了解了jpeg的压缩原理,那么就知道为啥jpeg转webp会变大了:原图jpeg的压缩率已经很高了,转换的时候指定的webp的压缩品质却比较高。举个简单例子解释一下:
- jpeg压缩品质50,渲染图片的时候用5个比特解出10比特的像素。
- webp输出品质80,渲染时用8个比特解出10比特的像素。
但其实这个时候有效比特只有5位,多出来的3位都是0无效比特,白白占用空间,反而比原jpeg都大。所以转换之前需要读取jpeg的压缩品质,基于这个品质来决定webp的输出品质。jepg的压缩品质在文件头中,可以通过gmagick
工具很方便的读取。
gm -verbose example.jpg
然而可惜的是目前尝试的各个python/js库都不支持获取这个值(也可能是姿势不对)。
3. PNG
PNG的原理没有去深挖,此处应该留个坑。仅凭印象对png的理解有
- 需要透明像素,上png
- 比jpg大,一般都比较清晰
然而实际上对于某些色彩比较少的图片,png有独门秘技调色板palette,可以在无损的前提下达到非常高的压缩率。原本每个像素点都有RGBA4个通道,理论上有255^4种颜色变化。一张800*600的图片需要8006004字节来存储。但幸运的是这张图片只有256种颜色,那么我们先存储一张调色盘,大小是256*4字节,然后每个像素点都只需要存一个调色盘的索引:800*600字节。压缩率为
(256*4+800*600)/(800*600*4) = 0.25
所以对于某些库,比如sharp,在转换webp的过程中会先把png转成RGBA格式,这样转成webp之后就会变得很大,需要手动指定palette的配置参数。
4. GIF
GIF其实就是连续的png,文件头中定义了帧数,播放间隔。可以理解为数组 PNG[]
。一般来说默认的行为都是只读取gif的第一帧。因此如果需要转换webp,需要读取帧数pages
和循环次数loop
,再传给webp转换处理。
5. 图片resize
我们知道对于不同的屏幕,逻辑像素和物理像素并不是一比一的对应关系(DevicePixelRate)。因此为了在高分屏获得最佳体验,一般会提供2倍甚至3倍尺寸的图片。实际应用中我们可能希望只上传一张高清大图,通过图片处理服务生成更小尺寸的图,以及转换成对应的webp格式。这里就涉及到resize下采样的问题了。
- 对于jpeg图片,一般的下采样算法(临近采样其实就是卷积?)是有效的
- 对于RGB(A)的png,也是有效的
- 对于采用了palette的png,因为原图的色彩数量是有限的(通常是256),通过下采样(卷积)会产生新的颜色。如果要保持palette调色板,那么图片就容易失真,锯齿严重。如果想要得到平滑的图片,图片大小就会不受控制地超过原图。这种情况下直接保持原尺寸的png是最好的。