分析webp图片结构和规范
对于互联网应用来说,图片的大小会影响网页加载速度,早期网站上大部分用的是jpeg格式的图片,但最近几年,WebP格式的图片越来越多,WebP 是一种新型的图片格式,在2010年由Google公司发布,目的是为了减少图片大小。
WebP支持有损和无损压缩,支持透明度(也称为 Alpha 通道),另外,还可以制作动图(和GIF一样),可谓功能强大。
据Google公司分析,WebP 无损图片的大小比 PNG 图片小 26% 。WebP 有损图片比采用等效 SSIM 质量索引的同类 JPEG 图片缩小 25-34% 。
可见它的优点还是很明显的,当然缺点也有,它出现的比较晚,加上是Google公司推出(并非通用规范),很多图片编辑器(比如PhotoShop 2018等)并不支持该格式(可能也有一部分原因是出于商业利益)。
规范解读
WebP 文件由图片数据(VP8
或VP8L
)和基于 RIFF 的容器组成。
我们先来看VP8格式。
有损编码(VP8)
我们在网页上常见的WebP图片基本上是有损编码(Lossy)
的,有损编码的图片格式如下:
它由两部分组成,第一部分是 12字节长度 的 WebP file header
,它的组成如下:
通过这个header,我们可以判断一张图片是否是webp格式。
第二部分是'VP8 ' Chunk
,它的结构如下:
首先是4 bytes的Chunk Header,它的值定义为十六进制:
const VP8 = [0x56, 0x50, 0x38, 0x20]
通过匹配该值,我们可以找到它所在的位置。
VP8 data中包含图片的高宽信息,怎么获得高宽数据呢?
根据规范,对于VP8 data中,首先是4个字节(我暂时没弄明白它的用途),接下来是3个字节的 frame tag
,再接着是7字节的未压缩数据,它的格式如下:
可以看到,其宽度信息就在0x2a
后面的两个字节中,高度信息在宽度信息的后面两个字节中,整体结构如下(每个灰色块是8bits):
因此,在Node.js中,可以通过如下代码获得图片的高宽:
let width = buff.readUIntLE(offset, 2)
let height = buff.readUIntLE(offset + 2, 2)
这是有损编码的webp规范。
无损编码(VP8L)
另外,还有一种无损编码(Lossless)
,它的结构如下:
和有损编码不一样,无损编码是'VP8L' Chunk,占4个字节,其十六进制值为:
const VP8L = [0x56, 0x50, 0x38, 0x4c]
'VP8L' Chunk
的格式如下:
其和VP8很像,那怎么获得它的高宽信息呢?
通过阅读VP8L规范,发现其高宽信息存在如下格式中(存放在红色的wh四个字节中):
首先,我们需要找到VP8L所在位置,这可以通过匹配其ACSII码值来定位,它占4bytes,接着是无损流所占空间大小(4bytes),再接着是一个字节的signature,值为0x2f
,之后的四个字节中就存放图片的高宽信息。
在这4bytes高宽信息中,分别使用了14bits来存放高宽数值,可以使用Node.js中来解析:
const vp8l = Buffer.from([
0x56, 0x50, 0x38, 0x4c, 0xc5, 0x6d, 0x00, 0x00, 0x2f, 0xd9, 0x41, 0x4a, 0x00, 0x0d
])
// Read the first 28 bits to get the width and height
const widthAndHeightBits = vp8l.readUIntLE(9, 4)
const width = (widthAndHeightBits & 0x3fff) + 1
const height = (widthAndHeightBits >> 14) + 1
分析发现,图片的高宽信息就存储在[0xd9,0x41,0x4a,0x00]这四个字节中,它的二进制结构如下:
红色部分(14bits)是宽度,蓝色部分(14bits)是高度。
注意:解析时,要使用little-endian
方式。
例子验证
我从网页上下载了一个webp图片,通过Node.js的readFile()读取后,获得了Buffer数据,其如下:
可以看到,第一行最后四位正是'VP8 '的十六进制值,所以,该图片是有损编码的。
参考
- 网站的图片格式
- 对于WebP格式入门解读
- WebP 容器规范
- VP8 Data Format and Decoding Guide
- Specification for WebP Lossless Bitstream