众所周知,图像的二进制数据,我们可以用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的数据块中,有以下数据块的类型
- 文件头数据块:包含图像数据的基本信息,在png当中,出现的第一个数据块便是文件头数据块。它总共有13个字节组成,其结构如下:
名称 | 字节数 | 说明 |
---|---|---|
Width | 4 | 图像宽度(以像素为单位) |
Height | 4 | 图像高度(以像素为单位) |
Bit depth | 1 | 图像深度 |
ColorType | 1 | 颜色类型 |
Compression method | 1 | 压缩方法 |
Filter method | 1 | 滤波器方法 |
Interlace method | 1 | 隔行扫描方法 |
- 调色板数据块(具体结构,这里不再展开)
- 图像数据块(具体结构,这里不再展开)
- 图像结束数据块(具体结构,这里不再展开)
很明显,根据上面的分析,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,而且效率更高。