JS 二进制 判断图片是否是PNG
js读取文件二进制信息,判断文件是否是PNG文件
Published by xiaoliublog@gmail.com at 05/05/2021.

JS 二进制操作基础

前端要操作二进制,自然避不开这些类型 ArrayBuffer,Uint8Array,DataView,Blob,File等。不常用,所以我们先来了解一下他们的关系。

基本的二进制对象是 ArrayBuffer, 虽然有一个 Array ,但是和 Array 没什么关系。它是对固定长度的连续内存空间的引用。

既然 ArrayBuffer 只是一段内存引用,那么怎么来读写这段内存,就需要用到其他的几个类型,称之为视图(View)。如 Uint8Array,Uint 即为 无符号整型,8即为 8比特(bit)即1字节(byte)。

那么我们声明了一个let buffer = new ArrayBuffer(16)即为一个16字节长度的内存引用。

我们通过 Uint8Array 来“看”它,它就是长度为16的一个数据数组,每个数据长度为1字节;

我们通过 Uint16Array 来“看”它,它就是长度为8的一个数据数组,每个数据长度为2字节;

ArrayBuffer(16)

这些统称为TypedArray,不过不存在TypedArray这个类型。 当然我们还有一种更加灵活的视图 DataView, 他可以读取任意大小的一段内存数据。

具体的用法,我们结合下面的示例来讲。

怎么判断文件是不是PNG格式?

通常,我们判断一个文件是什么格式,是通过文件名后缀来判断的。但是后缀名是很容易被人为的修改的,难道修改了后缀名,文件的格式就变了吗? 这个当然是不会变的,因为文件的二进制数据并未被修改。那么软件是怎么来判断给定的二进制文件是否是自己支持的文件呢? 这个信息肯定是储存在二进制的信息中。

比如我们这里的PNG格式,在 PNG file spec 里面就规定了,PNG 文件的开头必须是这八个字节(byte,Uint8)的数据。

The first eight bytes of a PNG datastream always contain the following (decimal) values:

137 80 78 71 13 10 26 10

This signature indicates that the remainder of the datastream contains a single PNG image, consisting of a series of chunks beginning with an IHDR chunk and ending with an IEND chunk.

所以我们想判断一个文件是否真的是PNG文件,只需要校验文件开头的8个字节即可(当然不绝对,也不排除伪造的可能,但是伪造的成本比起改文件名后缀要高)。

实践一下

在浏览器怎么拿到文件二进制信息?

对于一个FileBlob对象,我们可以通过 FileReader 来读取到一个 ArrayBuffer 中。

async function fileToArrayBuffer(file) {
  return new Promise((res, rej) => {
    let fileReader = new FileReader();

    fileReader.readAsArrayBuffer(file);

    fileReader.onload = function () {
      res(fileReader.result);
    };

    fileReader.onerror = rej;
  });
}

怎么读取前8个字节?

这里就用到了,前面讲的 DataView(当然,用TypedArray也是可以的。

  // DataView的构造函数,参数分别是
  // 1. arraybuffer
  // 2. start index
  // 3. end index
  // 这里我们只读取前8个字节。
  const dataView = new DataView(arrayBuffer, 0, 8);

  // 获取其中一个字节 Uint8
  dataView.getUint8(index)

  // 获取 Uint16 
  // ⚠️注意此时的 index 最大为3 ,长度为4。因为 Uint16长度 等于 2*Uint8的长度。
  dataView.getUint16(index)

比较文件头

OK,文件的前8字节获取到了。那么我们只需要比较一下就可以判断是否是PNG文件了。

const PNG_SIG = [137, 80, 78, 71, 13, 10, 26, 10];

async function checkFile(file) {
  const arrayBuffer = await fileToArrayBuffer(file);

  for (let i = 0; i < 8; i++) {
    console.log(i, PNG_SIG[i], dataView.getUint8(i));
    if (PNG_SIG[i] !== dataView.getUint8(i)) return false;
  }

  return true;
}

完整代码在这里

思考

  • 这里我是每次取出一个字节来比较,那么有没有更直接的简单的方法呢?
    • 提示: Float64Array
  • 你可以写出判断JPEG,GIF等文件的方法吗?

学习资源

豫ICP备17010879号