二进制数组的应用

大量的 Web API 用到了ArrayBuffer对象和它的视图对象。

AJAX

传统上,服务器通过 AJAX 操作只能返回文本数据,即responseType属性默认为textXMLHttpRequest第二版XHR2允许服务器返回二进制数据,这时分成两种情况。如果明确知道返回的二进制数据类型,可以把返回类型(responseType)设为arraybuffer;如果不知道,就设为blob

  1. let xhr = new XMLHttpRequest();
  2. xhr.open('GET', someUrl);
  3. xhr.responseType = 'arraybuffer';
  4. xhr.onload = function () {
  5. let arrayBuffer = xhr.response;
  6. // ···
  7. };
  8. xhr.send();

如果知道传回来的是 32 位整数,可以像下面这样处理。

  1. xhr.onreadystatechange = function () {
  2. if (req.readyState === 4 ) {
  3. const arrayResponse = xhr.response;
  4. const dataView = new DataView(arrayResponse);
  5. const ints = new Uint32Array(dataView.byteLength / 4);
  6. xhrDiv.style.backgroundColor = "#00FF00";
  7. xhrDiv.innerText = "Array is " + ints.length + "uints long";
  8. }
  9. }

Canvas

网页Canvas元素输出的二进制像素数据,就是 TypedArray 数组。

  1. const canvas = document.getElementById('myCanvas');
  2. const ctx = canvas.getContext('2d');
  3. const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
  4. const uint8ClampedArray = imageData.data;

需要注意的是,上面代码的uint8ClampedArray虽然是一个 TypedArray 数组,但是它的视图类型是一种针对Canvas元素的专有类型Uint8ClampedArray。这个视图类型的特点,就是专门针对颜色,把每个字节解读为无符号的 8 位整数,即只能取值 0 ~ 255,而且发生运算的时候自动过滤高位溢出。这为图像处理带来了巨大的方便。

举例来说,如果把像素的颜色值设为Uint8Array类型,那么乘以一个 gamma 值的时候,就必须这样计算:

  1. u8[i] = Math.min(255, Math.max(0, u8[i] * gamma));

因为Uint8Array类型对于大于 255 的运算结果(比如0xFF+1),会自动变为0x00,所以图像处理必须要像上面这样算。这样做很麻烦,而且影响性能。如果将颜色值设为Uint8ClampedArray类型,计算就简化许多。

  1. pixels[i] *= gamma;

Uint8ClampedArray类型确保将小于 0 的值设为 0,将大于 255 的值设为 255。注意,IE 10 不支持该类型。

WebSocket

WebSocket可以通过ArrayBuffer,发送或接收二进制数据。

  1. let socket = new WebSocket('ws://127.0.0.1:8081');
  2. socket.binaryType = 'arraybuffer';
  3. // Wait until socket is open
  4. socket.addEventListener('open', function (event) {
  5. // Send binary data
  6. const typedArray = new Uint8Array(4);
  7. socket.send(typedArray.buffer);
  8. });
  9. // Receive binary data
  10. socket.addEventListener('message', function (event) {
  11. const arrayBuffer = event.data;
  12. // ···
  13. });

Fetch API

Fetch API 取回的数据,就是ArrayBuffer对象。

  1. fetch(url)
  2. .then(function(response){
  3. return response.arrayBuffer()
  4. })
  5. .then(function(arrayBuffer){
  6. // ...
  7. });

File API

如果知道一个文件的二进制数据类型,也可以将这个文件读取为ArrayBuffer对象。

  1. const fileInput = document.getElementById('fileInput');
  2. const file = fileInput.files[0];
  3. const reader = new FileReader();
  4. reader.readAsArrayBuffer(file);
  5. reader.onload = function () {
  6. const arrayBuffer = reader.result;
  7. // ···
  8. };

下面以处理 bmp 文件为例。假定file变量是一个指向 bmp 文件的文件对象,首先读取文件。

  1. const reader = new FileReader();
  2. reader.addEventListener("load", processimage, false);
  3. reader.readAsArrayBuffer(file);

然后,定义处理图像的回调函数:先在二进制数据之上建立一个DataView视图,再建立一个bitmap对象,用于存放处理后的数据,最后将图像展示在Canvas元素之中。

  1. function processimage(e) {
  2. const buffer = e.target.result;
  3. const datav = new DataView(buffer);
  4. const bitmap = {};
  5. // 具体的处理步骤
  6. }

具体处理图像数据时,先处理 bmp 的文件头。具体每个文件头的格式和定义,请参阅有关资料。

  1. bitmap.fileheader = {};
  2. bitmap.fileheader.bfType = datav.getUint16(0, true);
  3. bitmap.fileheader.bfSize = datav.getUint32(2, true);
  4. bitmap.fileheader.bfReserved1 = datav.getUint16(6, true);
  5. bitmap.fileheader.bfReserved2 = datav.getUint16(8, true);
  6. bitmap.fileheader.bfOffBits = datav.getUint32(10, true);

接着处理图像元信息部分。

  1. bitmap.infoheader = {};
  2. bitmap.infoheader.biSize = datav.getUint32(14, true);
  3. bitmap.infoheader.biWidth = datav.getUint32(18, true);
  4. bitmap.infoheader.biHeight = datav.getUint32(22, true);
  5. bitmap.infoheader.biPlanes = datav.getUint16(26, true);
  6. bitmap.infoheader.biBitCount = datav.getUint16(28, true);
  7. bitmap.infoheader.biCompression = datav.getUint32(30, true);
  8. bitmap.infoheader.biSizeImage = datav.getUint32(34, true);
  9. bitmap.infoheader.biXPelsPerMeter = datav.getUint32(38, true);
  10. bitmap.infoheader.biYPelsPerMeter = datav.getUint32(42, true);
  11. bitmap.infoheader.biClrUsed = datav.getUint32(46, true);
  12. bitmap.infoheader.biClrImportant = datav.getUint32(50, true);

最后处理图像本身的像素信息。

  1. const start = bitmap.fileheader.bfOffBits;
  2. bitmap.pixels = new Uint8Array(buffer, start);

至此,图像文件的数据全部处理完成。下一步,可以根据需要,进行图像变形,或者转换格式,或者展示在Canvas网页元素之中。