分析webp图片结构和规范

阅读 2k

对于互联网应用来说,图片的大小会影响网页加载速度,早期网站上大部分用的是jpeg格式的图片,但最近几年,WebP格式的图片越来越多,WebP 是一种新型的图片格式,在2010年由Google公司发布,目的是为了减少图片大小。

WebP支持有损和无损压缩,支持透明度(也称为 Alpha 通道),另外,还可以制作动图(和GIF一样),可谓功能强大。

据Google公司分析,WebP 无损图片的大小比 PNG 图片小 26% 。WebP 有损图片比采用等效 SSIM 质量索引的同类 JPEG 图片缩小 25-34% 。

可见它的优点还是很明显的,当然缺点也有,它出现的比较晚,加上是Google公司推出(并非通用规范),很多图片编辑器(比如PhotoShop 2018等)并不支持该格式(可能也有一部分原因是出于商业利益)。

规范解读

WebP 文件由图片数据(VP8VP8L)和基于 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 '的十六进制值,所以,该图片是有损编码的。

参考

最后编辑于: 2023-10-09

评论(0条)

(必填)
复制成功