分析JPEG图片结构和规范

阅读 2.7k
标签: JavaScript

JPEG(Joint Photographic Experts Group)是一种图像压缩技术标准。在网页上浏览的图片,绝大部分都是这种格式,其后缀名为.jpg.jpeg因为它支持极高的压缩率,这导致它本身体积小,特别适合在互联网上进行展示和传输。当然,从另一方面来说,高压缩率导致了图片质量有所损失,所以,它不太适合需要高清晰度的场景。

在本篇中,我们分析一下JPEG格式的规范,由于规范总是相对冗长和枯燥的,我们带着两个问题来解读规范吧。

问题一:判断是否为JPEG格式

给你一个文件,如何判断它是否为JPEG格式呢?首先,我们需要解读JPEG格式的结构。

如下所示,每一个JPEG图片都是由SOI开头,由EOI结尾,并且至少包含一个Frame


SOI: Start of image marker,其值为十六进制 FFD8

EOI: End of image marker, 其值为十六进制 FFD9

而Frame定义如下:


一共有16种 Frame,其中 Huffman codingarithmetic coding 各有8种,我们接触到的最常见的是SOF0,其十六进制标志为 FFC0

比如,我从网上随便从下一张图片factor.jpg,通过如下代码分析:

import fs from 'fs/promises'

try {
  const buff = await fs.readFile('image/factor.jpg', { encoding: '' }) 

  console.log(buff, buff.length)

  console.log(buff[buff.length - 2].toString(16), buff[buff.length - 1].toString(16))
} catch (err) {
  console.log(err)
}

得到输出:

<Buffer ff d8 ff e0 00 10 4a 46 49 46 00 01 01 00 00 01 00 01 00 00 ff db 00 43 00 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 ... 57442 more bytes> 57492

ff d9

可以看到,该文件大小为:57492 bytes,它的前两个字节为 ff 和 d8, 最后两个字节为 ff 和 d9,这和上面SOI和EOI的值相同,基本可以判断为是JPEG图片,为什么说是基本呢?因为中间还必须找到至少一个 Frame 部分,才能100%确定是jpeg格式的图片。

问题二:获取JPEG图片的高宽

图片的实际高宽像素值是存于 Frame 中的,所以,我们需要对 Frame 进行解析,Frame 组成如下:


每个Frame由 SOFn 开头(两个bytes),接着是LfFrame header length,两个bytes),再接着是PSample precision,一个bytes),再接着是YNumber of lines,两个bytes),再接着是XNumber of samples per line),最后是一个NfNumber of image components in frame, 一个bytes),这些一起组成了 Frame headerFrame header 之后是 Frame component-specification parameters。

网页上的大多数 jpeg 图片的Frame都是用 Baseline DCT,也就是SOF0,我们只需要找到 SOF0的位置,再解析Y和X值,就能得到该jpeg图片的高宽信息了:

import fs from 'fs/promises'

try {
  const buff = await fs.readFile('image/factor.jpg', { encoding: '' }) // Buffer

  const SOF0 = [0xff, 0xc0]
  const u8 = new Uint8Array(buff)

  u8.forEach((v, i) => {
    // SOF0
    if (v === SOF0[0] && u8[i + 1] === SOF0[1]) {
      console.log('SOF0: ', i)
      let hBuff = Buffer.from([u8[i + 5], u8[i + 6]])
      let wBuff = Buffer.from([u8[i + 7], u8[i + 8]])

      console.log(wBuff[0], wBuff[1], wBuff)

      console.log('width: ', wBuff[0] * 256 + wBuff[1])
      console.log('height: ', hBuff[0] * 256 + hBuff[1])
    }
  })
} catch (err) {
  console.log(err)
}

得到的信息如下:

SOF0:  158
3 70 <Buffer 03 46>
width:  838
height:  498

可以看到,该图片的SOF0在索引158的位置,X所在的两个字节值为 0x0346,那么图片的宽度就是为3 * 256 + 70 = 838。同理,可以得到图片高度为498px。

参考

最后编辑于: 2024-07-21

评论(0条)

(必填)
复制成功