写在前面
计算机中储存数值有 2 种方式,一种是大端存储(Big Endian),一种是小端存储(Little Endian)。
以 uint32 数值的储存为例来举例:
所谓大端储存就是指存储的数值是从高位到低位的顺序,,就是数值占用 4 个字节,比如:
0x00 0x00 0x00 0x01 转为十进制,结果是 1
小端储存就不同了,同样的数,如果是小端储存,高位存储在低地址,低位存储在高地址,比如:
0x00 0x00 0x00 0x01 转为十进制,结果是 256^3。等于大端储存的 0x01 0x00 0x00 0x00
因为历史原因,也叫 JPG
储存方式有如下的规则,其中一般使用开头和结尾的各 2 个字节来判断是否为 JPEG 格式
开头: 0xFF 0xD8 结尾: 0xFF 0xD9
如下所示:
| start: 2 bytes | block | block | ... | end: 2 bytes |
然后,储存时是按数据块(block)来储存的,每个数据库块的开头都有一个 2 字节的标识符,为 0xFF,type,来标识块的类型。 其中 C0 类型为图片的宽高信息。
数据块分析:
数值 | 偏移量 | 长度 | 说明 |
---|---|---|---|
FF | +0 | 1 | 开始标识符 |
任意 | +1 | 1 | 类型,查看 JPEG 规范 |
n | +2 ~ +3 | 2 | 块长度,包含这两位,但不包含 FF 和类型长度 |
内容 | +4 ~ +(n+2) | n-2 | 内容 |
已知类型标识符
数值 | 说明 | 信息 |
---|---|---|
C0 | 宽度高度 | +5~+6:高度 +7~+8:宽度 |
开头固定为:
89 50 4e 47 0d 0a 1a 0a 00 00 00 0d 49 48 44 52
宽度在 0x00000010 偏移量处, 0~3
位是宽度 4~7
位是高度
按照 PNG 规范,其实表示宽度和高度的字符各占 4 个字节,但实际应用中 2 字节可以表示的无符号整数的最大数值为 256^2-1=65535,而当前实际应用中我没有发现有这么大的图片,所以代码中只解析了 2 字节来表示宽度和高度,如果后续有需要,我会改进。
little endian
开头: 12 字节标识 webp 格式
RIFF(4 字节) + 文件长度(4 字节) + WEBP(4 字节), 文件长度不包含 RIFF 标识头和长度信息, 但包含 WEBP
比如我的图片文件大小为 125,198 字节, 这个文件长度对应的十六进制为 06 E9 01 00 , 这里要注意, 和 png jpg 的长度表示方案不同, 这里是倒序的 0x0001e906 = 125190
VP8 chunk: 找到 VP8 的标识 根据内容不同, 表示也不尽相同
往后偏移 +14 ~ +15
为宽度(倒序), 16~17
为高度(倒序))
开头: 0x47, 0x49, 0x46, 0x38, 0x39, 0x61
结尾: 0x3B
从偏移量 +6~+7
两位标识宽度 +8~+9
标识高度, 这里也是倒序, 示例中图片的宽度对应的十六进制为: [0xB0,0x02] (688)
, 高度为[0x2E,0x05](1326)
储存数字的方式是 little endian
本部分参考 信息来源
bmp 是 windows 的位图格式,但这个格式其实有很多种,通过开头的 2 个字节进行标识,一般情况下,咱们就关注 BM 开头的即可
数据段总体分 4 个部分,其中调色板部分如果在颜色信息为 24 位或者 32 位以上是没有的
而本库中需要的宽高信息集中在第二段中
第二段是如下的表
字节序号 | 数据结构 | 描述 |
---|---|---|
15,16,17,18 | uint | 当前结构体的大小,通常是 40 或 56 |
19,20,21,22 | int | 图像宽度(像素) |
23,24,25,26 | int | 图像高度(像素) |
27,28 | word | 这个字的值永远是 1 |
29,30 | word | 每像素占用的位数,即 bpp |
31,32,33,34 | uint | 压缩方式 |
35,36,37,38 | uint | 图像的尺寸(字节数) |
39,40,41,42 | int | 水平分辨率,pixels-per-meter |
43,44,45,46 | int | 垂直分辨率,pixels-per-meter |
47,48,49,50 | uint | 引用色彩数 |
51,52,53,54 | uint | 关键色彩数 |
所以,我们通常读取前 2 个字节来判断是否是 BM,19 开始的四个字节是宽度,23 开始的四个字节是高度