Javascript でバイナリデータを扱うまとめ

ブラウザのjavascriptでバイナリデータを扱う方法まとめ。
以下ES6以上前提です。IEなどというブラウザはもう無い。

##バイナリデータを扱うObject

### ArrayBuffer

バイト列を扱う配列。メモリの連続領域に確保される。
そのままでは要素にアクセスできない。

```javascript 
const buf = new ArrayBuffer(length)
buf.byteLength //バイト長
buf.slice(ofs,len) //ofsからlenバイトの別ArrayBufferを返す
```

参照:[ArrayBuffer MDN](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer)

### DataView

ArrayBufferを操作するためのビュー。エンディアンを指定して数値データとして操作できるset/getメソッドが各種用意されている。

```javascript
const buffer = new ArrayBuffer(8)
const dataview = new DataView(buffer)
dataview.setInt16(byteOffset, value [, littleEndian])
dataview.getInt16(byteOffset [, littleEndian])
```

参照:[DataView MDN](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/DataView)

### TypedArray

全ての要素が同じ型の、ArrayBufferに対するビュー。
以下の種類がある。

Int8Array Uint8Array Uint8ClampedArray 
Int16Array Uint16Array 
Int32Array Uint32Array 
Float32Array Float64Array
BigInt64Array BigUint64Array

Uint8ClampedArrayは、0未満は0に、256以上は255に丸める。

コンストラクタに様々な形式をとって初期化できる。 

```javascript
new TypedArray() //空の配列
new TypedArray(length) //サイズ指定0初期化
new TypedArray(typedArray) //他のTypedArrayからの変換
new TypedArray(object)  //iterableなobjectからの変換
new TypedArray(buffer [, byteOffset [, length]]) //ArrayBufferのビューとして生成(実体はコピーされない)
```

[index]で要素にアクセスできる。
forEach,map等のArrayと同様のメソッドが使える。
ただし、push,popのような要素数の増減はできない。

bufferプロパティでArrayBufferとして参照できる。

部分をコピーして取り出すときはslice。
別なTypedArrayにコピーするときはset methodが高速。

```javascript
function memcpy(dst, dstOffset, src, srcOffset, length) {
  var dstU8 = new Uint8Array(dst, dstOffset, length)
  var srcU8 = new Uint8Array(src, srcOffset, length)
  dstU8.set(srcU8)
}
```

参照:[TypedArray MDN](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/TypedArray)

### Blob
blobはファイルを抽象化したインターフェイス。中身をArrayBufferとして非同期的に取り出せ、ファイルタイプ(mimetype)を指定することで、ファイルの形式を指定することができる。

コンストラクタ。配列で渡されたArrayBufferを結合する。

```javascript
const newBlob = new Blob([buffer], {type:"mimetype"})
```
blobの中身を取り出すのは、FileReaderを使う。

```javascript
const fr = new FileReader()
fr.onloadend = function() {
  fr.result //ArrayBuffer
}
fr.readAsArrayBuffer(newBlob)
```

[Blob MDN](https://developer.mozilla.org/ja/docs/Web/API/Blob)

## 変換

### 文字列

ArrayBufferから文字列を取り出す。
ASCIIの場合、配列にバラして1文字づつfromCharCodeする。

```javascript
let str = Array.from(buf.slice(ofs,size), e => String.fromCharCode(e)).join("")
```

Javascriptの文字列は内部表現がUTF-16なので、上の方法ではUTF-8のマルチバイト文字が化ける。UTF-8文字列を変換するには以下のようにいったん%u形式にURIEncodeしてからdecodeする。

```javascript
function arrayBufferUTF8ToString(arrayBuffer) {
	const escape = (str) => {
		return str.replace(/[^a-zA-Z0-9@*_+\-./]/g, function(m) {
			const code = m.charCodeAt(0);
			if (code <= 0xff) {
				return '%' + ('00' + code.toString(16)).slice(-2).toUpperCase()
			} else {
				return '%u' + ('0000' + code.toString(16)).slice(-4).toUpperCase()
			}
		})
	}
	const uint8Array = new Uint8Array(arrayBuffer)
	const binStr = String.fromCharCode(...uint8Array)
	return decodeURIComponent(escape(binStr))
}

```

逆に文字列をUTF-8としてArrayBufferに格納する場合。
Blobを使ってUTF-8に変換できる。

```javascript
function str2bytes(str) {
  return new Promise((resolve,reject)=>{
    const fr = new FileReader()
    fr.onloadend = function() {
      resolve(fr.result)
    }
    fr.readAsArrayBuffer(new Blob([str]))
  }
}
```

### 画像

ArrayBufferの画像ファイルデータを画像として表示。
Blobにfile typeを指定して取り込み、それをcreateObjectURLでdeta:uriに変換。

```javascript
const imgblob = new Blob([buffer],{type:"image/jpeg"})
const img = new document.createElement("img")
img.src = URL.createObjectURL(imgblob) 
```

非圧縮のRGBAデータをArrayBufferで作ってcanvasで扱う場合。
ImageDataで縦横サイズを指定して、canvasに書き込む。

```javascript
const buffer = new ArrayBuffer(width*height*4) 
const imageData = new ImageData(buffer, width , height)
ctx.putImageData(imageData,0, 0)
```

## 読み書き

### urlから読み込み
XHRを使ってArrayBufferに読み込み、responseTypeを"arraybuffer"にする。

```javascript
const req = new XMLHttpRequest()
req.open("get",url,true)
req.responseType = "arraybuffer" 
req.onload = () => {
	if(req.status==200) {
		req.response //ArrayBuffer
	} else {
		alert("file cannot load")					
	}
}
req.send()
```



### local fileから読み込み

input type=fileでユーザが指定したファイルから、FileReaderを使って読みこむ。

```javascript
const files = document.getElementById("fileinput").files
const reader = new FileReader()
reader.onload = (ev) =>{
	reader.result //ArrayBuffer
}
reader.readAsArrayBuffer(files[0])
```

### ファイルとしてダウンロード
Blobでファイルタイプを指定してから、createObjectURLでURIに変換して、リンクからダウンロードさせる

```javascript
const datablob = new Blob([buffer],{type:"application/octet-stream"})
const a = document.getElementById("downloadlink") 
a.href = URL.createObjectURL(datablob)
a.download = "filename"
```