如何从base64中获取图像的宽度和高度

众所周知,图像的二进制数据,我们可以用base64来表示。但是在某些特殊的场景,我们可能会需要得到它的宽度和高度信息,那该怎么办?

在前端Web开发中,有一个非常简单的方法

1
2
3
4
5
const image = new Image();
image.onload = () => {
    console.log([image.width, image.height]);
};
image.src = base64;

但如果是在后端或者其他领域,这个方法很明显就不能用了,况且这个方法看起来并不优雅,需要base64转化成图片,再从图片句柄中获取其宽度和高度信息。那有没有更加直接的方式呢?换句话说,能否直接从图像二进制数据中获取其尺寸呢?

答案肯定是可以的,既然Image能从base64中解析出像素信息和尺寸信息,那我们也可以做到,只是这需要我们对图像二进制数据的格式有一定的了解。

在这里,我们拿最常见的png格式举例

PNG文件结构

png图像格式的文件是由文件署名和数据块组成。

文件署名

png的文件署名是占8个字节,具体为

1
89 50 4e 47 0d 0a 1a 0a

数据块格式

png文件除了开头8字节的文件署名,剩下的都是一系列的数据块,而数据块的格式如下:

名称 字节数 说明
长度 4 指定数据块中数据域的长度
数据块类型码 4 不同的数据块类型码表示不同类型的数据块
数据块实际内容 可变长度 储存数据块的实际内容
循环冗余检测(CRC) 4 用来检测是否有错误的

数据块类型

在png的数据块中,有以下数据块的类型

  1. 文件头数据块:包含图像数据的基本信息,在png当中,出现的第一个数据块便是文件头数据块。它总共有13个字节组成,其结构如下:
名称 字节数 说明
Width 4 图像宽度(以像素为单位)
Height 4 图像高度(以像素为单位)
Bit depth 1 图像深度
ColorType 1 颜色类型
Compression method 1 压缩方法
Filter method 1 滤波器方法
Interlace method 1 隔行扫描方法
  1. 调色板数据块(具体结构,这里不再展开)
  2. 图像数据块(具体结构,这里不再展开)
  3. 图像结束数据块(具体结构,这里不再展开)

很明显,根据上面的分析,png文件的宽度信息是出现在第17个字节处(其前面有8 + 4 + 4 = 16字节),高度信息出现第21个字节处(其前面有8 + 4 + 4 + 4 = 20字节)。

所以,获取base64图像的尺寸信息还可以这么写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
function get_size(base64) {
    //确认处理的是png格式的数据
    if (base64.substring(0,22) === 'data:image/png;base64,') {
        // base64 是用四个字符来表示3个字节
        // 我们只需要截取base64前32个字符(不计开头那22个字符)便可(24 / 3 * 4)
        // 这里的data包含12个字符,9个字节,除去第1个字节,后面8个字节就是我们想要的宽度和高度
        const data = base64.substring(22 + 20, 22 + 32);
        const base64Characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
        const nums = [];
        for (const c of data) {
            nums.push(base64Characters.indexOf(c));
        }
        const bytes = [];
        for(let i = 0; i < nums.length; i += 4) {
            bytes.push((nums[i] << 2) + (nums[i+1] >> 4));
            bytes.push(((nums[i+1] & 15) << 4) + (nums[i+2] >> 2));
            bytes.push(((nums[i+2] & 3) << 6) + nums[i+3]);
        }
        const width = (bytes[1] << 24) + (bytes[2] << 16) + (bytes[3] << 8) + bytes[4];
        const height = (bytes[5] << 24) + (bytes[6] << 16) + (bytes[7] << 8) + bytes[8];
        return {
            width,
            height,
        };
    }
    throw Error('unsupported image type');
}

两种方式对比可看出,第两种方式明显更好,不依赖前端Web中特定的API,而且效率更高。